跳转至内容

GLSL 编程/Blender/双面曲面

来自维基教科书,开放世界中的开放书籍
一个代数曲面,其中包含一个中心为环面的曲面。渲染使用不同的颜色表示曲面的两侧。

本教程介绍了双面逐顶点光照

它是关于 Blender 中基本光照的系列教程的一部分。在本教程中,我们将扩展镜面高光教程来渲染双面曲面。如果您还没有阅读镜面高光教程,现在是一个很好的时机来阅读它。

曲面法向量 N 和观察者方向 V 通常位于曲面的同一侧,但也存在例外情况;因此,不应该依赖它。

双面光照

[编辑 | 编辑源代码]

如左侧图所示,有时将不同的颜色应用于曲面的两侧很有用。在截面教程中,我们已经了解了片段着色器如何使用内置变量gl_FrontFacing来确定片段是属于正面三角形还是背面三角形。顶点着色器也可以确定它是属于正面三角形还是背面三角形吗?答案是明确的:不能!其中一个原因是同一个顶点可以同时是正面三角形和背面三角形的一部分;因此,无论顶点着色器做出何种决定,它都可能对某些三角形是错误的。如果你想记住一个简单的规则:“片段要么是正面,要么是背面。顶点是双重的。”

因此,双面逐顶点光照必须让片段着色器确定应该应用正面还是背面材质颜色。例如,使用以下片段着色器

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

另一方面,这意味着顶点着色器必须计算两次曲面光照:一次用于正面,一次用于背面。幸运的是,这通常仍然比为每个片段计算曲面光照的工作量少。

顶点着色器代码

[编辑 | 编辑源代码]

用于双面逐顶点光照的顶点着色器代码是对镜面高光教程中代码的直接扩展。但是,我们必须按照透明度教程中所述禁用背面剔除。此外,着色器需要两组材质参数(正面和背面),它们可以作为gl_FrontMaterialgl_BackMaterial使用。使用这两组参数,顶点着色器可以计算出两种颜色,一种用于正面,一种用于背面,其中对于背面必须使用取反的法向量。两种颜色都传递给片段着色器,由它决定应用哪种颜色。不幸的是,Blender 似乎没有办法为正面和背面指定不同的材质。相反,gl_FrontMaterialgl_BackMaterial始终包含相同的数据。因此,在 Blender 中,您将不得不引入新的统一变量或常量来为正面和背面材质指定不同的参数。但是,在本教程中,我们只是假设 Blender 在gl_FrontMaterialgl_BackMaterial中使用不同的参数。顶点着色器代码如下

         varying vec4 frontColor; // the lighting of front faces 
            // that was computed in the vertex shader
         varying vec4 backColor; // the lighting of back faces 
            // that was computed in the vertex shader
 
         void main()
         {                              
            vec3 normalDirection = 
               normalize(gl_NormalMatrix * gl_Normal);
            vec3 viewDirection = 
               -normalize(vec3(gl_ModelViewMatrix * gl_Vertex)); 
            vec3 lightDirection;
            float attenuation;
 
            if (0.0 == gl_LightSource[0].position.w) 
               // directional light?
            {
               attenuation = 1.0; // no attenuation
               lightDirection = 
                  normalize(vec3(gl_LightSource[0].position));
            } 
            else // point light or spotlight (or other kind of light) 
            {
               vec3 vertexToLightSource = 
                  vec3(gl_LightSource[0].position 
                  - gl_ModelViewMatrix * gl_Vertex);
               float distance = length(vertexToLightSource);
               attenuation = 1.0 / distance; // linear attenuation 
               lightDirection = normalize(vertexToLightSource);
 
               if (gl_LightSource[0].spotCutoff <= 90.0) // spotlight?
               {
                  float clampedCosine = max(0.0, dot(-lightDirection, 
                     gl_LightSource[0].spotDirection));
                  if (clampedCosine < gl_LightSource[0].spotCosCutoff) 
                     // outside of spotlight cone?
                  {
                     attenuation = 0.0;
                  }
                  else
                  {
                     attenuation = attenuation * pow(clampedCosine, 
                        gl_LightSource[0].spotExponent);
                  }
               }
            }

            // Computation of lighting for front faces
           
            vec3 ambientLighting = vec3(gl_LightModel.ambient) 
               * vec3(gl_FrontMaterial.emission);
              
            vec3 diffuseReflection = attenuation 
               * vec3(gl_LightSource[0].diffuse) 
               * vec3(gl_FrontMaterial.emission)
               * 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(gl_LightSource[0].specular) 
                  * vec3(gl_FrontMaterial.specular) 
                  * pow(max(0.0, dot(reflect(-lightDirection, 
                  normalDirection), viewDirection)), 
                  gl_FrontMaterial.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(gl_BackMaterial.emission);
              
            vec3 backDiffuseReflection = attenuation 
               * vec3(gl_LightSource[0].diffuse) 
               * vec3(gl_BackMaterial.emission)
               * 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(gl_LightSource[0].specular) 
                  * vec3(gl_BackMaterial.specular) 
                  * pow(max(0.0, dot(reflect(-lightDirection, 
                  -normalDirection), viewDirection)), 
                  gl_BackMaterial.shininess);
            }

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

            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }

片段着色器代码如上所述。

恭喜您完成了本篇简短教程,其中包含一个很长的着色器。我们已经了解到

  • 为什么顶点着色器无法区分正面顶点和背面顶点(因为同一个顶点可能既是正面三角形的一部分,又是背面三角形的一部分)。
  • 如何在顶点着色器中计算正面和背面的光照。
  • 如何让片段着色器决定应用哪种颜色。

进一步阅读

[编辑 | 编辑源代码]

如果您还想了解更多


< GLSL 编程/Blender

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