GLSL 程式設計/Blender/平滑鏡面高光
外觀


本教程涵蓋了逐畫素光照(也稱為Phong 著色)。
它基於鏡面高光教程。如果您還沒有閱讀過該教程,您應該先閱讀它。逐頂點光照(即為每個頂點計算表面光照,然後插值頂點顏色)的主要缺點是質量有限,特別是對於鏡面高光,如左側的圖形所示。解決方法是逐畫素光照,它根據插值的法向量為每個片段計算光照。雖然生成的影像質量明顯更高,但效能成本也很高。
逐畫素光照也稱為 Phong 著色(與逐頂點光照形成對比,逐頂點光照也稱為 Gouraud 著色)。這不能與 Phong 反射模型(也稱為 Phong 光照)混淆,Phong 反射模型透過環境項、漫射項和鏡面項計算表面光照,如鏡面高光教程中所述。
逐畫素光照的關鍵思想很容易理解:法向量和位置為每個片段插值,並在片段著色器中計算光照。
除了最佳化之外,基於逐頂點光照的著色器程式碼實現逐畫素光照非常簡單:光照計算從頂點著色器移動到片段著色器,頂點著色器必須將光照計算所需的屬性寫入 varying。然後,片段著色器使用這些 varying 來計算光照(而不是頂點著色器使用的屬性)。就是這樣。
在本教程中,我們調整了鏡面高光教程中的著色器程式碼以進行逐畫素光照。生成的頂點著色器如下所示
varying vec4 position;
// position of the vertex (and fragment) in view space
varying vec3 varyingNormalDirection;
// surface normal vector in view space
void main()
{
position = gl_ModelViewMatrix * gl_Vertex;
varyingNormalDirection =
normalize(gl_NormalMatrix * gl_Normal);
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
大部分工作現在都在片段著色器中完成
varying vec4 position;
// position of the vertex (and fragment) in view space
varying vec3 varyingNormalDirection;
// surface normal vector in view space
void main()
{
vec3 normalDirection = normalize(varyingNormalDirection);
vec3 viewDirection = -normalize(vec3(position));
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 positionToLightSource =
vec3(gl_LightSource[0].position - position);
float distance = length(positionToLightSource);
attenuation = 1.0 / distance; // linear attenuation
lightDirection = normalize(positionToLightSource);
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);
}
gl_FragColor = vec4(ambientLighting
+ diffuseReflection + specularReflection, 1.0);
}
請注意,頂點著色器將歸一化向量寫入 varyingNormalDirection,以確保所有方向在插值中都得到同等權重。片段著色器再次對其進行歸一化,因為插值後的方向不再是歸一化的。
恭喜,現在您知道逐畫素 Phong 光照是如何工作的。我們已經看到了
- 為什麼逐頂點光照提供的質量有時不夠(特別是由於鏡面高光)。
- 逐畫素光照的工作原理以及如何基於逐頂點光照的著色器實現它。
如果您仍然想了解更多
- 關於逐頂點光照的著色器版本,您應該閱讀鏡面高光教程。
除非另有說明,否則本頁面上的所有示例原始碼均已授予公有領域。