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

本教程介紹了使用Phong 反射模型的逐頂點光照(也稱為Gouraud 著色)。
它在漫反射教程的基礎上擴充套件了著色器程式碼,增加了兩個額外的項:環境光和鏡面反射。這三個項共同構成了 Phong 反射模型。如果你還沒有閱讀漫反射教程,現在是一個很好的機會。
仔細觀察左側卡拉瓦喬的這幅畫。雖然白色襯衫的大部分割槽域都在陰影中,但沒有一個部分是完全黑色的。顯然,始終有一些光從牆壁和其他物體反射出來,照亮場景中的所有物體——至少在一定程度上。在 Phong 反射模型中,這種效應是由環境光來考慮的,它取決於一個普遍的環境光強度 和漫反射的材質顏色 。環境光強度的公式
與漫反射教程中漫反射公式類似,此公式也可以解釋為光線紅、綠、藍分量的向量方程。
在 Blender 中,環境光在屬性視窗的世界選項卡中指定。對於特定材質,該顏色會與屬性視窗的材質選項卡中的著色>環境相乘,以便控制每種材質對環境色的影響。在 Blender 中的 GLSL 著色器中,該乘積以 gl_LightModel.ambient 的形式提供,它是視空間著色教程中提到的 OpenGL 相容性配置檔案的預定義統一變數之一。

如果你仔細觀察卡拉瓦喬的這幅畫,你會看到幾個鏡面高光:在鼻子、頭髮、嘴唇、魯特琴、小提琴、弓、水果等等。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(gl_LightModel.ambient)
* vec3(gl_FrontMaterial.diffuse);
但是,正如在檢視空間著色教程中所提到的,Blender不會設定gl_FrontMaterial.diffuse(也不設定gl_FrontMaterial.ambient);因此,我們使用gl_FrontMaterial.emission
vec3 ambientLighting = vec3(gl_LightModel.ambient)
* vec3(gl_FrontMaterial.emission);
為了實現鏡面反射,我們需要在檢視空間中獲得觀察者方向,我們可以透過計算相機位置和頂點位置(都在檢視空間中)之間的差來獲得。相機在檢視空間中的位置很簡單,因為檢視空間的定義是相機位置位於原點;參見“頂點變換”。頂點位置可以按照漫反射教程中討論的方式轉換為檢視空間。然後,可以在世界空間中像這樣實現鏡面反射項的方程
vec3 viewDirection =
-normalize(vec3(gl_ModelViewMatrix * gl_Vertex));
// == vec3(0.0, 0.0, 0.0) - ...
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(gl_LightSource[0].specular)
* vec3(gl_FrontMaterial.specular)
* pow(max(0.0, dot(reflect(-lightDirection,
normalDirection), viewDirection)),
gl_FrontMaterial.shininess);
}
此程式碼片段使用了與漫反射教程中的著色器程式碼相同的變數,另外還使用了內建的uniform變數gl_FrontMaterial.specular和gl_FrontMaterial.shininess。(正如在檢視空間著色教程中提到的那樣,這些變數由使用者在Blender中指定。)pow(a, b)計算.
如果將環境光照和鏡面反射新增到漫反射教程中的完整頂點著色器中,它將如下所示
varying vec4 color;
void main()
{
vec3 normalDirection =
normalize(gl_NormalMatrix * gl_Normal);
vec3 viewDirection =
-normalize(vec3(gl_ModelViewMatrix * gl_Vertex));
vec3 lightDirection;
float attenuation;
if (0.0 == gl_LightSource[0].position.w)
// directional light?
{
attenuation = 1.0; // no attenuation
lightDirection =
normalize(vec3(gl_LightSource[0].position));
}
else // point light or spotlight (or other kind of light)
{
vec3 vertexToLightSource =
vec3(gl_LightSource[0].position
- gl_ModelViewMatrix * gl_Vertex);
float distance = length(vertexToLightSource);
attenuation = 1.0 / distance; // linear attenuation
lightDirection = normalize(vertexToLightSource);
if (gl_LightSource[0].spotCutoff <= 90.0) // spotlight?
{
float clampedCosine = max(0.0, dot(-lightDirection,
gl_LightSource[0].spotDirection));
if (clampedCosine < gl_LightSource[0].spotCosCutoff)
// outside of spotlight cone?
{
attenuation = 0.0;
}
else
{
attenuation = attenuation * pow(clampedCosine,
gl_LightSource[0].spotExponent);
}
}
}
vec3 ambientLighting = vec3(gl_LightModel.ambient)
* vec3(gl_FrontMaterial.emission);
vec3 diffuseReflection = attenuation
* vec3(gl_LightSource[0].diffuse)
* vec3(gl_FrontMaterial.emission)
* 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(gl_LightSource[0].specular)
* vec3(gl_FrontMaterial.specular)
* pow(max(0.0, dot(reflect(-lightDirection,
normalDirection), viewDirection)),
gl_FrontMaterial.shininess);
}
color = vec4(ambientLighting + diffuseReflection
+ specularReflection, 1.0);
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
片段著色器仍然是
varying vec4 color;
void main()
{
gl_FragColor = color;
}
總結
[edit | edit source]恭喜你,你剛學會了如何實現Phong反射模型。特別是,我們看到了
- Phong反射模型中的環境光照是什麼。
- Phong反射模型中的鏡面反射項是什麼。
- 如何在Blender中使用GLSL實現這些項。
進一步閱讀
[edit | edit source]如果你還想了解更多
- 關於著色器程式碼,你應該閱讀漫反射教程.