跳轉到內容

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

來自華夏公益教科書,自由的教科書,來自自由的世界
“演奏魯特琴的阿波羅”(巴明頓莊園版本)由米開朗基羅·梅里西·達·卡拉瓦喬創作,約 1596 年。

本教程涵蓋使用Phong 反射模型逐頂點光照(也稱為Gouraud 著色)。

它擴充套件了“漫反射”部分中的著色器程式碼,增加了兩個額外的項:環境光和鏡面反射。這三個項共同構成了 Phong 反射模型。如果您還沒有閱讀“漫反射”部分,現在是一個很好的機會去閱讀它。

環境光

[編輯 | 編輯原始碼]

請仔細觀察左側的卡拉瓦喬的畫作。雖然白襯衫的大部分割槽域都處於陰影中,但沒有任何部分是完全黑色的。顯然,總有一些光線從牆壁和其他物體反射出來,照亮場景中的所有東西——至少在一定程度上。在 Phong 反射模型中,這種效果透過環境光來考慮,環境光取決於一般環境光強度 和漫反射材料顏色。在環境光強度 的方程中

類似於“漫反射”部分中漫反射方程,這個方程也可以解釋為光的紅、綠、藍分量的向量方程。

在 Unity 中,環境光可以透過從主選單中選擇編輯 > 渲染設定(在 Unity5 中選擇視窗 > 光照)來指定。在 Unity 中的 GLSL 著色器中,此顏色始終可用作gl_LightModel.ambient,它是“世界空間中的著色”部分中提到的 OpenGL 相容性配置檔案的預定義制服之一。

鏡面反射的計算需要表面法線向量 N,光源方向 L,反射光源方向 R 和觀察者方向 V。

鏡面高光

[編輯 | 編輯原始碼]

如果你仔細觀察卡拉瓦喬的畫作,你會看到幾個鏡面高光:在鼻子上、在頭髮上、在嘴唇上、在魯特琴上、在小提琴上、在弓上、在水果上等等。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 反射模型中,RV 之間角度的餘弦被用來生成不同光澤度的高光,該角度被提升到 次方。類似於漫反射的情況,我們應該將負餘弦鉗位到 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]

如果您還想了解更多


< GLSL 程式設計/Unity

除非另有說明,否則本頁上的所有示例原始碼均授予公有領域。
華夏公益教科書