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

本教程涵蓋使用Phong 反射模型的逐頂點光照(也稱為Gouraud 著色)。
它擴充套件了“漫反射”部分中的著色器程式碼,增加了兩個額外的項:環境光和鏡面反射。這三個項共同構成了 Phong 反射模型。如果您還沒有閱讀“漫反射”部分,現在是一個很好的機會去閱讀它。
請仔細觀察左側的卡拉瓦喬的畫作。雖然白襯衫的大部分割槽域都處於陰影中,但沒有任何部分是完全黑色的。顯然,總有一些光線從牆壁和其他物體反射出來,照亮場景中的所有東西——至少在一定程度上。在 Phong 反射模型中,這種效果透過環境光來考慮,環境光取決於一般環境光強度 和漫反射材料顏色。在環境光強度 的方程中
類似於“漫反射”部分中漫反射方程,這個方程也可以解釋為光的紅、綠、藍分量的向量方程。
在 Unity 中,環境光可以透過從主選單中選擇編輯 > 渲染設定(在 Unity5 中選擇視窗 > 光照)來指定。在 Unity 中的 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(_Color);
為了實現鏡面反射,我們需要在世界空間中獲取觀察者的方向,我們可以透過世界空間中相機位置和頂點位置之間的差值來計算。世界空間中的相機位置由 Unity 在 uniform _WorldSpaceCameraPos 中提供;頂點位置可以轉換為世界空間,如 “漫反射”部分所述。然後可以像這樣在世界空間中實現鏡面反射項的方程
vec3 viewDirection = normalize(vec3(
vec4(_WorldSpaceCameraPos, 1.0)
- modelMatrix * gl_Vertex));
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(_LightColor0)
* vec3(_SpecColor) * pow(max(0.0, dot(
reflect(-lightDirection, normalDirection),
viewDirection)), _Shininess);
}
此程式碼片段使用與 “漫反射”部分中著色器程式碼相同的變數,另外還包括使用者指定的屬性 _SpecColor 和 _Shininess。(名稱特意選擇為 fallback 著色器可以訪問它們;請參閱 “漫反射”部分中的討論。)pow(a, b) 計算 .
如果環境光照新增到第一遍(我們只需要一次),並且鏡面反射新增到 “漫反射”部分中完整著色器的兩遍,它看起來像這樣
Shader "GLSL per-vertex lighting" {
Properties {
_Color ("Diffuse Material Color", Color) = (1,1,1,1)
_SpecColor ("Specular Material Color", Color) = (1,1,1,1)
_Shininess ("Shininess", Float) = 10
}
SubShader {
Pass {
Tags { "LightMode" = "ForwardBase" }
// pass for ambient light and first light source
GLSLPROGRAM
// User-specified properties
uniform vec4 _Color;
uniform vec4 _SpecColor;
uniform float _Shininess;
// The following built-in uniforms (except _LightColor0)
// are also defined in "UnityCG.glslinc",
// i.e. one could #include "UnityCG.glslinc"
uniform vec3 _WorldSpaceCameraPos;
// camera position in world space
uniform mat4 _Object2World; // model matrix
uniform mat4 _World2Object; // inverse model matrix
uniform vec4 _WorldSpaceLightPos0;
// direction to or position of light source
uniform vec4 _LightColor0;
// color of light source (from "Lighting.cginc")
varying vec4 color;
// the Phong lighting computed in the vertex shader
#ifdef VERTEX
void main()
{
mat4 modelMatrix = _Object2World;
mat4 modelMatrixInverse = _World2Object; // unity_Scale.w
// is unnecessary because we normalize vectors
vec3 normalDirection = normalize(vec3(
vec4(gl_Normal, 0.0) * modelMatrixInverse));
vec3 viewDirection = normalize(vec3(
vec4(_WorldSpaceCameraPos, 1.0)
- modelMatrix * gl_Vertex));
vec3 lightDirection;
float attenuation;
if (0.0 == _WorldSpaceLightPos0.w) // directional light?
{
attenuation = 1.0; // no attenuation
lightDirection = normalize(vec3(_WorldSpaceLightPos0));
}
else // point or spot light
{
vec3 vertexToLightSource = vec3(_WorldSpaceLightPos0
- modelMatrix * gl_Vertex);
float distance = length(vertexToLightSource);
attenuation = 1.0 / distance; // linear attenuation
lightDirection = normalize(vertexToLightSource);
}
vec3 ambientLighting =
vec3(gl_LightModel.ambient) * vec3(_Color);
vec3 diffuseReflection =
attenuation * vec3(_LightColor0) * vec3(_Color)
* 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(_LightColor0)
* vec3(_SpecColor) * pow(max(0.0, dot(
reflect(-lightDirection, normalDirection),
viewDirection)), _Shininess);
}
color = vec4(ambientLighting + diffuseReflection
+ specularReflection, 1.0);
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif
#ifdef FRAGMENT
void main()
{
gl_FragColor = color;
}
#endif
ENDGLSL
}
Pass {
Tags { "LightMode" = "ForwardAdd" }
// pass for additional light sources
Blend One One // additive blending
GLSLPROGRAM
// User-specified properties
uniform vec4 _Color;
uniform vec4 _SpecColor;
uniform float _Shininess;
// The following built-in uniforms (except _LightColor0)
// are also defined in "UnityCG.glslinc",
// i.e. one could #include "UnityCG.glslinc"
uniform vec3 _WorldSpaceCameraPos;
// camera position in world space
uniform mat4 _Object2World; // model matrix
uniform mat4 _World2Object; // inverse model matrix
uniform vec4 _WorldSpaceLightPos0;
// direction to or position of light source
uniform vec4 _LightColor0;
// color of light source (from "Lighting.cginc")
varying vec4 color;
// the diffuse lighting computed in the vertex shader
#ifdef VERTEX
void main()
{
mat4 modelMatrix = _Object2World;
mat4 modelMatrixInverse = _World2Object; // unity_Scale.w
// is unnecessary because we normalize vectors
vec3 normalDirection = normalize(vec3(
vec4(gl_Normal, 0.0) * modelMatrixInverse));
vec3 viewDirection = normalize(vec3(
vec4(_WorldSpaceCameraPos, 1.0)
- modelMatrix * gl_Vertex));
vec3 lightDirection;
float attenuation;
if (0.0 == _WorldSpaceLightPos0.w) // directional light?
{
attenuation = 1.0; // no attenuation
lightDirection = normalize(vec3(_WorldSpaceLightPos0));
}
else // point or spot light
{
vec3 vertexToLightSource = vec3(_WorldSpaceLightPos0
- modelMatrix * gl_Vertex);
float distance = length(vertexToLightSource);
attenuation = 1.0 / distance; // linear attenuation
lightDirection = normalize(vertexToLightSource);
}
// vec3 ambientLighting =
// vec3(gl_LightModel.ambient) * vec3(_Color);
vec3 diffuseReflection =
attenuation * vec3(_LightColor0) * vec3(_Color)
* 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(_LightColor0)
* vec3(_SpecColor) * pow(max(0.0, dot(
reflect(-lightDirection, normalDirection),
viewDirection)), _Shininess);
}
color = vec4(diffuseReflection + specularReflection, 1.0);
// no ambient lighting in this pass
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif
#ifdef FRAGMENT
void main()
{
gl_FragColor = color;
}
#endif
ENDGLSL
}
}
// The definition of a fallback shader should be commented out
// during development:
// Fallback "Specular"
}
總結
[edit | edit source]恭喜,您剛剛學習瞭如何實現 Phong 反射模型。具體來說,我們已經看到了
- Phong 反射模型中的環境光照是什麼。
- Phong 反射模型中的鏡面反射項是什麼。
- 如何在 Unity 的 GLSL 中實現這些項。
進一步閱讀
[edit | edit source]如果您還想了解更多
- 關於著色器程式碼,您應該閱讀 “漫反射”部分.