跳转到内容

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

除非另有说明,本页面上的所有示例源代码均授予公有领域。
华夏公益教科书