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


本教程涵蓋逐畫素光照(也稱為Phong 著色)。
它基於鏡面高光教程。如果您還沒有閱讀過該教程,建議您先閱讀。逐頂點光照(即為每個頂點計算表面光照,然後插值頂點顏色)的主要缺點是質量有限,特別是對於鏡面高光而言,如左側圖所示。解決方法是逐畫素光照,它根據插值的法線向量為每個片段計算光照。雖然生成的影像質量明顯更高,但效能成本也很高。
逐畫素光照也稱為 Phong 著色(與逐頂點光照形成對比,逐頂點光照也稱為 Gouraud 著色)。不要與 Phong 反射模型(也稱為 Phong 光照)混淆,後者使用環境光、漫反射光和鏡面光來計算表面光照,如鏡面高光教程中所述。
逐畫素光照的核心思想很容易理解:法線向量和位置為每個片段插值,並在片段著色器中計算光照。
除了最佳化之外,基於逐頂點光照的著色器程式碼實現逐畫素光照非常簡單:光照計算從頂點著色器移至片段著色器,頂點著色器必須將光照計算所需的屬性寫入 varyings。然後,片段著色器使用這些 varyings 來計算光照(而不是頂點著色器使用的屬性)。就是這樣。
在本教程中,我們根據鏡面高光教程中的著色器程式碼,將逐畫素光照改造成逐頂點光照。最終的頂點著色器如下所示
attribute vec4 v_coord;
attribute vec3 v_normal;
varying vec4 position; // position of the vertex (and fragment) in world space
varying vec3 varyingNormalDirection; // surface normal vector in world space
uniform mat4 m, v, p;
uniform mat3 m_3x3_inv_transp;
void main()
{
position = m * v_coord;
varyingNormalDirection = normalize(m_3x3_inv_transp * v_normal);
mat4 mvp = p*v*m;
gl_Position = mvp * v_coord;
}
現在大部分工作都在片段著色器中完成
varying vec4 position; // position of the vertex (and fragment) in world space
varying vec3 varyingNormalDirection; // surface normal vector in world space
uniform mat4 m, v, p;
uniform mat4 v_inv;
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 frontMaterial = 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()
{
vec3 normalDirection = normalize(varyingNormalDirection);
vec3 viewDirection = normalize(vec3(v_inv * vec4(0.0, 0.0, 0.0, 1.0) - position));
vec3 lightDirection;
float attenuation;
if (0.0 == light0.position.w) // directional light?
{
attenuation = 1.0; // no attenuation
lightDirection = normalize(vec3(light0.position));
}
else // point light or spotlight (or other kind of light)
{
vec3 positionToLightSource = vec3(light0.position - position);
float distance = length(positionToLightSource);
lightDirection = normalize(positionToLightSource);
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, 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(frontMaterial.ambient);
vec3 diffuseReflection = attenuation
* vec3(light0.diffuse) * vec3(frontMaterial.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(frontMaterial.specular)
* pow(max(0.0, dot(reflect(-lightDirection, normalDirection), viewDirection)), frontMaterial.shininess);
}
gl_FragColor = vec4(ambientLighting + diffuseReflection + specularReflection, 1.0);
}
請注意,頂點著色器將歸一化的向量寫入 varyingNormalDirection,以確保所有方向在插值中都具有同等權重。片段著色器再次歸一化它,因為插值後的方向不再是歸一化的。
恭喜,現在您知道逐畫素 Phong 光照是如何工作的。我們已經看到了
- 為什麼逐頂點光照提供的質量有時不夠(特別是由於鏡面高光)。
- 逐畫素光照是如何工作的,以及如何基於逐頂點光照的著色器實現它。
如果您還想了解更多
- 有關逐頂點光照的著色器版本的資訊,請閱讀鏡面高光教程。
除非另有說明,否則本頁面上的所有示例原始碼均歸屬公有領域。
| 返回OpenGL 程式設計 - 光照部分 | 返回GLSL 程式設計 - GLUT 部分 |