跳轉到內容

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

來自華夏公益教科書,開放世界開放書籍
使用逐頂點光照渲染的環面網格。
使用逐畫素光照渲染的環面網格。

本教程介紹了逐畫素光照(也稱為Phong 著色)。

它基於“鏡面高光”部分。如果您還沒有閱讀過本教程,請先閱讀。逐頂點光照(即為每個頂點計算表面光照,然後插值頂點顏色)的主要缺點是質量有限,特別是對於鏡面高光,如左側圖所示。解決方法是逐畫素光照,它根據插值的法線向量為每個片段計算光照。雖然產生的影像質量明顯更高,但效能成本也很高。

逐畫素光照(Phong 著色)

[編輯 | 編輯原始碼]

逐畫素光照也稱為 Phong 著色(與逐頂點光照形成對比,逐頂點光照也稱為 Gouraud 著色)。這與 Phong 反射模型(也稱為 Phong 光照)不同,Phong 反射模型透過環境、漫射和鏡面項計算表面光照,如“鏡面高光”部分中所述。

逐畫素光照的關鍵思想很容易理解:法線向量和位置為每個片段插值,光照在片段著色器中計算。

著色器程式碼

[編輯 | 編輯原始碼]

除了最佳化之外,基於逐頂點光照的著色器程式碼實現逐畫素光照非常簡單:光照計算從頂點著色器移動到片段著色器,頂點著色器必須將光照計算所需的屬性寫入 varyings。然後片段著色器使用這些 varyings 來計算光照(而不是頂點著色器使用的屬性)。就這麼簡單。

在本教程中,我們調整了“鏡面高光”部分中的著色器程式碼以進行逐畫素光照。結果如下所示

Shader "GLSL per-pixel 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 position; 
            // position of the vertex in world space 
         varying vec3 varyingNormalDirection; 
            // surface normal vector in world space

         #ifdef VERTEX
         
         void main()
         {				
            mat4 modelMatrix = _Object2World;
            mat4 modelMatrixInverse = _World2Object; // unity_Scale.w 
               // is unnecessary because we normalize vectors
            
            position = modelMatrix * gl_Vertex;
            varyingNormalDirection = normalize(vec3(
               vec4(gl_Normal, 0.0) * modelMatrixInverse));

            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
         
         #endif

         #ifdef FRAGMENT
         
         void main()
         {
            vec3 normalDirection = normalize(varyingNormalDirection);

            vec3 viewDirection = 
               normalize(_WorldSpaceCameraPos - vec3(position));
            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 - position);
               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);
            }

            gl_FragColor = vec4(ambientLighting + diffuseReflection 
               + specularReflection, 1.0);
         }
         
         #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 position; 
            // position of the vertex in world space 
         varying vec3 varyingNormalDirection; 
            // surface normal vector in world space

         #ifdef VERTEX
         
         void main()
         {				
            mat4 modelMatrix = _Object2World;
            mat4 modelMatrixInverse = _World2Object; // unity_Scale.w 
               // is unnecessary because we normalize vectors
            
            position = modelMatrix * gl_Vertex;
            varyingNormalDirection = normalize(vec3(
               vec4(gl_Normal, 0.0) * modelMatrixInverse));

            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
         
         #endif

         #ifdef FRAGMENT
         
         void main()
         {
            vec3 normalDirection = normalize(varyingNormalDirection);

            vec3 viewDirection = 
               normalize(_WorldSpaceCameraPos - vec3(position));
            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 - position);
               float distance = length(vertexToLightSource);
               attenuation = 1.0 / distance; // linear attenuation 
               lightDirection = normalize(vertexToLightSource);
            }
            
            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);
            }

            gl_FragColor = 
               vec4(diffuseReflection + specularReflection, 1.0);
         }
         
         #endif

         ENDGLSL
      }
   } 
   // The definition of a fallback shader should be commented out 
   // during development:
   // Fallback "Specular"
}

請注意,頂點著色器將歸一化向量寫入 varyingNormalDirection 以確保在插值中所有方向的權重相同。片段著色器再次將其歸一化,因為插值後的方向不再歸一化。

恭喜,現在您瞭解了逐畫素 Phong 光照的工作原理。我們已經看到了

  • 為什麼逐頂點光照提供的質量有時不夠(特別是由於鏡面高光)。
  • 逐畫素光照的工作原理以及如何基於逐頂點光照的著色器實現它。

進一步閱讀

[編輯 | 編輯原始碼]

如果您還想了解有關逐頂點光照的著色器版本的更多資訊


< GLSL 程式設計/Unity

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