跳转到内容

GLSL 编程/Blender/平滑镜面高光

来自维基教科书,开放世界中的开放书籍
使用逐顶点光照渲染的环面网格。
使用逐像素光照渲染的环面网格。

本教程涵盖了逐像素光照(也称为Phong 着色)。

它基于镜面高光教程。如果您还没有阅读过该教程,您应该先阅读它。逐顶点光照(即为每个顶点计算表面光照,然后插值顶点颜色)的主要缺点是质量有限,特别是对于镜面高光,如左侧的图形所示。解决方法是逐像素光照,它根据插值的法向量为每个片段计算光照。虽然生成的图像质量明显更高,但性能成本也很高。

逐像素光照 (Phong 着色)

[编辑 | 编辑源代码]

逐像素光照也称为 Phong 着色(与逐顶点光照形成对比,逐顶点光照也称为 Gouraud 着色)。这不能与 Phong 反射模型(也称为 Phong 光照)混淆,Phong 反射模型通过环境项、漫射项和镜面项计算表面光照,如镜面高光教程中所述。

逐像素光照的关键思想很容易理解:法向量和位置为每个片段插值,并在片段着色器中计算光照。

着色器代码

[编辑 | 编辑源代码]

除了优化之外,基于逐顶点光照的着色器代码实现逐像素光照非常简单:光照计算从顶点着色器移动到片段着色器,顶点着色器必须将光照计算所需的属性写入 varying。然后,片段着色器使用这些 varying 来计算光照(而不是顶点着色器使用的属性)。就是这样。

在本教程中,我们调整了镜面高光教程中的着色器代码以进行逐像素光照。生成的顶点着色器如下所示

         varying vec4 position; 
            // position of the vertex (and fragment) in view space 
         varying vec3 varyingNormalDirection; 
            // surface normal vector in view space

         void main()
         {                              
            position = gl_ModelViewMatrix * gl_Vertex; 
            varyingNormalDirection = 
               normalize(gl_NormalMatrix * gl_Normal);             

            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }

大部分工作现在都在片段着色器中完成

         varying vec4 position; 
            // position of the vertex (and fragment) in view space 
         varying vec3 varyingNormalDirection; 
            // surface normal vector in view space

         void main()
         {
            vec3 normalDirection = normalize(varyingNormalDirection);
            vec3 viewDirection = -normalize(vec3(position)); 
            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 positionToLightSource = 
                  vec3(gl_LightSource[0].position - position);
               float distance = length(positionToLightSource);
               attenuation = 1.0 / distance; // linear attenuation 
               lightDirection = normalize(positionToLightSource);
 
               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);   
                  }
               }
            }

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

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

请注意,顶点着色器将归一化向量写入 varyingNormalDirection,以确保所有方向在插值中都得到同等权重。片段着色器再次对其进行归一化,因为插值后的方向不再是归一化的。

恭喜,现在您知道逐像素 Phong 光照是如何工作的。我们已经看到了

  • 为什么逐顶点光照提供的质量有时不够(特别是由于镜面高光)。
  • 逐像素光照的工作原理以及如何基于逐顶点光照的着色器实现它。

进一步阅读

[编辑 | 编辑源代码]

如果您仍然想了解更多


< GLSL 编程/Blender

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