GLSL 程式設計/GLUT/鏡面高光

本教程涵蓋逐頂點光照(也稱為Gouraud 著色)使用Phong 反射模型。
它擴充套件了漫反射教程中的著色器程式碼,添加了兩個附加項:環境光和鏡面反射。這三個項共同構成了 Phong 反射模型。如果你還沒有閱讀漫反射教程,現在是一個很好的機會去閱讀它。
考慮左側卡拉瓦喬的畫作。雖然白襯衫的大部分都在陰影中,但沒有一部分是完全黑色的。光線從牆壁、桌子和其他物體反射,間接照亮了場景中的所有東西。在 Phong 反射模型中,這種效果透過環境光來考慮,它依賴於一般的環境光強度和漫反射的材質顏色。在環境光強度的公式中
類似於漫反射教程中的漫反射方程,該方程也可以解釋為光線紅、綠、藍分量的向量方程。
在 OpenGL 相容性配置檔案中,此顏色可用作gl_LightModel.ambient,它是檢視空間著色教程中提到的預定義統一變數之一。我們將把它指定為scene_ambient。

如果你仔細觀察卡拉瓦喬的畫作,你會看到幾個鏡面高光:在鼻子上、頭髮上、嘴唇上、魯特琴上、小提琴上、弓上、水果上等等。Phong 反射模型包含一個鏡面反射項,可以模擬光滑表面上的這種高光;它甚至包含一個引數來指定材料的光澤度。光澤度指定高光有多小:光澤度越高,高光越小。
一個完全光滑的表面只會反射來自光源的光線,方向為幾何反射方向R。對於不太光滑的表面,光線會反射到R周圍的方向:光澤度越低,散射越廣。在數學上,歸一化的反射方向R定義為
對於歸一化的表面法向量N和歸一化的光源方向L。在 GLSL 中,函式vec3 reflect(vec3 I, vec3 N)(或vec4 reflect(vec4 I, vec4 N))計算相同的反射向量,但對於從光源到表面上的點的方向I。因此,我們必須取反我們的方向L才能使用此函式。
鏡面反射項計算觀察者方向V上的鏡面反射。如上所述,如果V接近R,則強度應該很大,其中“接近”由光澤度 引數化。在Phong反射模型中,R和V之間角度的餘弦被提升到 次方來生成不同光澤度的亮點。類似於 漫反射 的情況,我們應該將負餘弦鉗制到 0。此外,鏡面項需要鏡面反射的材質顏色 ,它通常是白色,這樣所有亮點都具有入射光 的顏色。例如,卡拉瓦喬的所有繪畫中的亮點都是白色的。Phong反射模型的鏡面項如下
類似於 漫反射 的情況,如果光源位於表面的“錯誤”一側,則應忽略鏡面項;即如果點積 N·L 為負。
著色器程式碼
[edit | edit source]環境光照的著色器程式碼很簡單,只需進行逐分量向量-向量乘積
vec3 ambientLighting = vec3(scene_ambient) * vec3(mymaterial.ambient);
為了實現鏡面反射,我們需要世界空間中的觀察者方向,我們可以將其計算為相機位置和頂點位置(都位於世界空間)之間的差。視空間中的相機位置相當簡單:它位於視空間中的原點 ,並可以透過應用逆檢視矩陣將其轉換為世界空間;見 “頂點變換”。頂點位置可以如 漫反射教程 中所述轉換為世界空間。世界空間中鏡面項的方程可以這樣實現
vec3 viewDirection = normalize(vec3(v_inv * vec4(0.0, 0.0, 0.0, 1.0) - m * v_coord));
vec3 specularReflection;
if (dot(normalDirection, lightDirection) < 0.0) // light source on the wrong side?
{
specularReflection = vec3(0.0, 0.0, 0.0); // no specular reflection
}
else
{
specularReflection = attenuation * vec3(light0.specular) * vec3(mymaterial.specular)
* pow(max(0.0, dot(reflect(-lightDirection, normalDirection), viewDirection)),
mymaterial.shininess);
}
此程式碼片段使用了與 漫反射教程 中的著色器程式碼相同的變數,以及變數 mymaterial.specular 和 mymaterial.shininess。pow(a, b) 計算 。
如果環境光照和鏡面反射被新增到 漫反射教程 的完整頂點著色器中,它看起來像這樣
attribute vec4 v_coord;
attribute vec3 v_normal;
uniform mat4 m, v, p;
uniform mat3 m_3x3_inv_transp;
uniform mat4 v_inv;
varying vec4 color;
struct lightSource
{
vec4 position;
vec4 diffuse;
vec4 specular;
float constantAttenuation, linearAttenuation, quadraticAttenuation;
float spotCutoff, spotExponent;
vec3 spotDirection;
};
lightSource light0 = lightSource(
vec4(0.0, 1.0, 2.0, 1.0),
vec4(1.0, 1.0, 1.0, 1.0),
vec4(1.0, 1.0, 1.0, 1.0),
0.0, 1.0, 0.0,
180.0, 0.0,
vec3(0.0, 0.0, 0.0)
);
vec4 scene_ambient = vec4(0.2, 0.2, 0.2, 1.0);
struct material
{
vec4 ambient;
vec4 diffuse;
vec4 specular;
float shininess;
};
material mymaterial = material(
vec4(0.2, 0.2, 0.2, 1.0),
vec4(1.0, 0.8, 0.8, 1.0),
vec4(1.0, 1.0, 1.0, 1.0),
5.0
);
void main(void)
{
mat4 mvp = p*v*m;
vec3 normalDirection = normalize(m_3x3_inv_transp * v_normal);
vec3 viewDirection = normalize(vec3(v_inv * vec4(0.0, 0.0, 0.0, 1.0) - m * v_coord));
vec3 lightDirection;
float attenuation;
if (light0.position.w == 0.0) // directional light
{
attenuation = 1.0; // no attenuation
lightDirection = normalize(vec3(light0.position));
}
else // point or spot light (or other kind of light)
{
vec3 vertexToLightSource = vec3(light0.position - m * v_coord);
float distance = length(vertexToLightSource);
lightDirection = normalize(vertexToLightSource);
attenuation = 1.0 / (light0.constantAttenuation
+ light0.linearAttenuation * distance
+ light0.quadraticAttenuation * distance * distance);
if (light0.spotCutoff <= 90.0) // spotlight
{
float clampedCosine = max(0.0, dot(-lightDirection, normalize(light0.spotDirection)));
if (clampedCosine < cos(radians(light0.spotCutoff))) // outside of spotlight cone
{
attenuation = 0.0;
}
else
{
attenuation = attenuation * pow(clampedCosine, light0.spotExponent);
}
}
}
vec3 ambientLighting = vec3(scene_ambient) * vec3(mymaterial.ambient);
vec3 diffuseReflection = attenuation
* vec3(light0.diffuse) * vec3(mymaterial.diffuse)
* max(0.0, dot(normalDirection, lightDirection));
vec3 specularReflection;
if (dot(normalDirection, lightDirection) < 0.0) // light source on the wrong side?
{
specularReflection = vec3(0.0, 0.0, 0.0); // no specular reflection
}
else // light source on the right side
{
specularReflection = attenuation * vec3(light0.specular) * vec3(mymaterial.specular)
* pow(max(0.0, dot(reflect(-lightDirection, normalDirection), viewDirection)),
mymaterial.shininess);
}
color = vec4(ambientLighting + diffuseReflection + specularReflection, 1.0);
gl_Position = mvp * v_coord;
}
片元著色器仍然是
varying vec4 color;
void main(void)
{
gl_FragColor = color;
}
在您的 C++ 程式碼中,更新 v_inv 統一變數
glm::mat4 v_inv = glm::inverse(world2camera);
glUniformMatrix4fv(uniform_v_inv, 1, GL_FALSE, glm::value_ptr(v_inv));
總結
[edit | edit source]恭喜,您剛剛學習瞭如何實現Phong反射模型。特別是,我們已經看到了
- Phong反射模型中的環境光照是什麼。
- Phong反射模型中的鏡面反射項是什麼。
- 如何在 GLSL 中實現這些項。
進一步閱讀
[edit | edit source]如果您還想了解更多
| 返回 OpenGL 程式設計 - 光照部分 | 返回 GLSL 程式設計 - GLUT 部分 |