跳转到内容

GLSL 编程/Blender/光泽纹理

来自维基教科书,开放世界中的开放书籍
从国际空间站 (ISS) 看,太平洋上空的日落,带有镜面高光。

本教程涵盖部分光泽、纹理表面的每个像素的照明

它结合了纹理球体教程平滑镜面高光教程的着色器代码,以计算每个像素的照明,该照明具有用于漫反射的材质颜色,该颜色由纹理的 RGB 分量决定,以及由同一纹理的 A 分量决定的镜面反射强度。如果您还没有阅读纹理球体教程平滑镜面高光教程,这是一个很好的机会。

光泽映射

[编辑 | 编辑源代码]

照明纹理表面教程介绍了通过纹理图像的 RGB 分量确定漫反射的材质常数的概念。这里我们扩展了这种技术,并通过同一纹理图像的 A(alpha)分量确定镜面反射的强度。仅使用一个纹理提供了显著的性能优势,特别是由于在某些情况下,RGBA 纹理查找与 RGB 纹理查找一样昂贵。

如果纹理图像的“光泽”(即镜面反射的强度)编码在 RGBA 纹理图像的 A(alpha)分量中,我们可以简单地将镜面反射的材质常数乘以纹理图像的 alpha 分量。是在镜面高光教程中介绍的,出现在 Phong 反射模型的镜面反射项中

如果乘以纹理图像的 alpha 分量,该项将达到其最大值(即表面光泽)alpha 为 1 的地方,而 alpha 为 0 的地方为 0(即表面根本没有光泽)。

地球地图,透明水,即 alpha 分量对水为 0,对陆地为 1。

每个像素的照明着色器代码

[编辑 | 编辑源代码]

着色器代码是平滑镜面高光教程中每个像素的照明和纹理球体教程中的纹理的组合。类似于照明纹理表面教程textureColor 中纹理颜色的 RGB 分量乘以环境光和漫射光。

在左侧的特定纹理图像中,alpha 分量对水为 0,对陆地为 1。但是,应该是水是光泽的,而陆地不是。因此,对于这个特定的图像,我们应该将镜面材质颜色乘以(1.0 - textureColor.a)。另一方面,通常的光泽贴图需要乘以textureColor.a。(请注意对着色器程序进行此类更改是多么容易。)

顶点着色器如下

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

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

            texCoords = gl_MultiTexCoord0;
            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
         varying vec4 texCoords; // interpolated texture coordinates 
         uniform sampler2D textureUnit;

         void main()
         {
            vec3 normalDirection = normalize(varyingNormalDirection);
            vec3 viewDirection = -normalize(vec3(position)); 
            vec3 lightDirection;
            float attenuation; 

            vec2 longitudeLatitude = vec2(
               (atan(texCoords.y, texCoords.x)/3.1415926+1.0)*0.5, 
               1.0 - acos(texCoords.z) / 3.1415926);
               // unusual processing of texture coordinates

            vec4 textureColor = 
               texture2D(textureUnit, longitudeLatitude);
            
            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(textureColor);
              
            vec3 diffuseReflection = attenuation 
               * vec3(gl_LightSource[0].diffuse) * vec3(textureColor)
               * 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) 
                  * (1.0 - textureColor.a) 
                     // for usual gloss maps: "* textureColor.a"
                  * pow(max(0.0, dot(reflect(-lightDirection, 
                  normalDirection), viewDirection)), 
                  gl_FrontMaterial.shininess);
            }

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

纹理和球体必须按纹理球体教程中所述进行设置。本教程还解释了如何在 Python 脚本中设置统一变量textureUnit

对上述特定纹理图像,此着色器的有用修改是将漫射材质颜色设置为 alpha 分量为 0 的深蓝色。

每个顶点的照明着色器代码

[编辑 | 编辑源代码]

平滑镜面高光教程中所述,镜面高光通常不会使用每个顶点的照明很好地渲染。但是,有时由于性能限制别无选择。为了在照明纹理表面教程的着色器代码中包含光泽映射,应该用以下代码替换片段着色器

            varying diffuseColor;
            varying specularColor;
            varying vec4 texCoords;
            uniform sampler2D textureUnit;
 
            void main()
            {
               vec2 longitudeLatitude = vec2(
                   (atan(texCoords.y, texCoords.x)/3.1415926+1.0)*0.5, 
                   1.0 - acos(texCoords.z) / 3.1415926);
               vec4 textureColor = 
                  texture2D(textureUnit, longitudeLatitude);
               gl_FragColor = vec4(diffuseColor * vec3(textureColor)
                  + specularColor * (1.0 - textureColor.a), 1.0);
            }

请注意,通常的光泽贴图需要乘以textureColor.a而不是(1.0 - textureColor.a)

恭喜!您完成了关于光泽映射的重要教程。我们已经了解了

  • 什么是光泽映射。
  • 如何为每个像素的照明实现它。
  • 如何为每个顶点的照明实现它。

进一步阅读

[编辑 | 编辑源代码]

如果您还想了解更多


< GLSL 编程/Blender

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