跳轉到內容

GLSL 程式設計/Unity/雙面表面

來自華夏公益教科書
一個代數曲面,包括中心的一個迴轉體。渲染使用不同的顏色來表示表面的兩個面。

本教程涵蓋了雙面逐頂點光照

它是關於 Unity 中基本光照的一系列教程的一部分。在本教程中,我們擴充套件了“鏡面高光”部分以渲染雙面表面。如果你還沒有閱讀“鏡面高光”部分,現在是一個很好的時機。

表面法線向量 N 和觀察者方向 V 通常位於表面的同一邊,但也有例外,因此不應依賴於此。

雙面光照

[編輯 | 編輯原始碼]

正如代數曲面圖所示,有時將不同的顏色應用於表面的兩面是有用的。在“剖檢視”部分,我們已經看到了片段著色器如何使用內建變數gl_FrontFacing來確定一個片段是屬於正面三角形還是背面三角形。頂點著色器也可以確定它是否屬於正面三角形或背面三角形嗎?答案很明確:不能!一個原因是同一個頂點可以同時屬於正面背面三角形;因此,無論在頂點著色器中做出什麼決定,它對於某些三角形都有可能是錯誤的。如果你想記住一個簡單的規則:“片段要麼是正面,要麼是背面。頂點是雙面的。”

因此,雙面逐頂點光照必須讓片段著色器來確定應該應用正面材質顏色還是背面材質顏色。例如,使用此片段著色器

         #ifdef FRAGMENT

         varying vec4 frontColor; // color for front face
         varying vec4 backColor; // color for back face

         void main()
         {
            if (gl_FrontFacing) // is the fragment part of a front face?
            {
               gl_FragColor = frontColor;
            }
            else // fragment is part of a back face
            {
               gl_FragColor = backColor;
            }
         }

         #endif

另一方面,這意味著頂點著色器必須計算兩次表面光照:一次用於正面,一次用於背面。幸運的是,這通常仍然比為每個片段計算表面光照工作量少。

著色器程式碼

[編輯 | 編輯原始碼]

雙面逐頂點光照的著色器程式碼是對“鏡面高光”部分中程式碼的簡單擴充套件。它需要兩組材質引數(正面和背面)並停用剔除。頂點著色器計算兩種顏色,一種用於正面,一種用於背面,使用取反的法線向量和第二組材質引數。然後片段著色器決定應用哪種顏色。

Shader "GLSL two-sided per-vertex lighting" {
   Properties {
      _Color ("Front Material Diffuse Color", Color) = (1,1,1,1) 
      _SpecColor ("Front Material Specular Color", Color) = (1,1,1,1) 
      _Shininess ("Front Material Shininess", Float) = 10
      _BackColor ("Back Material Diffuse Color", Color) = (1,1,1,1) 
      _BackSpecColor ("Back Material Specular Color", Color) 
         = (1,1,1,1) 
      _BackShininess ("Back Material Shininess", Float) = 10
   }
   SubShader {
      Pass {	
         Tags { "LightMode" = "ForwardBase" } 
            // pass for ambient light and first light source
         Cull Off // render front faces and back faces

         GLSLPROGRAM

         // User-specified properties
         uniform vec4 _Color; 
         uniform vec4 _SpecColor; 
         uniform float _Shininess;
         uniform vec4 _BackColor; 
         uniform vec4 _BackSpecColor; 
         uniform float _BackShininess;

         // 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 frontColor; 
            // lighting of front faces computed in the vertex shader
         varying vec4 backColor; 
            // lighting of back faces 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);
            }
            
            // Computation of lighting for front faces

            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);
            }

            frontColor = vec4(ambientLighting + diffuseReflection 
               + specularReflection, 1.0);

            // Computation of lighting for back faces 
            // (uses negative normalDirection and back material colors)

            vec3 backAmbientLighting = 
               vec3(gl_LightModel.ambient) * vec3(_BackColor);

            vec3 backDiffuseReflection = 
               attenuation * vec3(_LightColor0) * vec3(_BackColor) 
               * max(0.0, dot(-normalDirection, lightDirection));
            vec3 backSpecularReflection;
            if (dot(-normalDirection, lightDirection) < 0.0) 
               // light source on the wrong side?
            {
               backSpecularReflection = vec3(0.0, 0.0, 0.0); 
                  // no specular reflection
            }
            else // light source on the right side
            {
               backSpecularReflection = 
                  attenuation * vec3(_LightColor0) 
                  * vec3(_BackSpecColor) * pow(max(0.0, dot(
                  reflect(-lightDirection, -normalDirection), 
                  viewDirection)), _BackShininess);
            }

            backColor = vec4(backAmbientLighting + 
               backDiffuseReflection + backSpecularReflection, 1.0);

            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
         
         #endif

         #ifdef FRAGMENT
         
         void main()
         {
            if (gl_FrontFacing) 
               // is the fragment part of a front face?
            {
               gl_FragColor = frontColor;
            }
            else // fragment is part of a back face
            {
               gl_FragColor = backColor;
            }
         }
         
         #endif

         ENDGLSL
      }

      Pass {	
         Tags { "LightMode" = "ForwardAdd" } 
            // pass for additional light sources
         Blend One One // additive blending 
         Cull Off // render front faces and back faces

         GLSLPROGRAM

         // User-specified properties
         uniform vec4 _Color; 
         uniform vec4 _SpecColor; 
         uniform float _Shininess;
         uniform vec4 _BackColor; 
         uniform vec4 _BackSpecColor; 
         uniform float _BackShininess;

         // 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 frontColor; 
            // lighting of front faces computed in the vertex shader
         varying vec4 backColor; 
            // lighting of back faces 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);
            }
            
            // Computation of lighting for front faces

            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);
            }

            frontColor = vec4(diffuseReflection 
               + specularReflection, 1.0);

            // Computation of lighting for back faces 
            // (uses negative normalDirection and back material colors)

            vec3 backDiffuseReflection = 
               attenuation * vec3(_LightColor0) * vec3(_BackColor) 
               * max(0.0, dot(-normalDirection, lightDirection));
            vec3 backSpecularReflection;
            if (dot(-normalDirection, lightDirection) < 0.0) 
               // light source on the wrong side?
            {
               backSpecularReflection = vec3(0.0, 0.0, 0.0); 
                  // no specular reflection
            }
            else // light source on the right side
            {
               backSpecularReflection = 
                  attenuation * vec3(_LightColor0) 
                  * vec3(_BackSpecColor) * pow(max(0.0, dot(
                  reflect(-lightDirection, -normalDirection), 
                  viewDirection)), _BackShininess);
            }

            backColor = vec4(backDiffuseReflection 
               + backSpecularReflection, 1.0);

            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
         
         #endif

         #ifdef FRAGMENT
         
         void main()
         {
            if (gl_FrontFacing) 
               // is the fragment part of a front face?
            {
               gl_FragColor = frontColor;
            }
            else // fragment is part of a back face
            {
               gl_FragColor = backColor;
            }
         }
         
         #endif

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

同樣,此程式碼由兩段組成,第二段與第一段相同,除了加性混合和缺少的環境色。

恭喜你完成了這個包含一個長著色器的簡短教程。我們已經看到了

  • 為什麼頂點著色器無法區分正面和背面頂點(因為同一個頂點可能屬於正面和背面三角形)。
  • 如何在頂點著色器中計算正面和背面的光照。
  • 如何讓片段著色器決定應用哪種顏色。

進一步閱讀

[編輯 | 編輯原始碼]

如果你還想了解更多


< GLSL 程式設計/Unity

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