跳至内容

GLSL 编程/Blender/纹理层

来自维基教科书,开放世界中的开放书籍
人皮肤层。

本教程介绍了**多纹理映射**,即在着色器中使用多个纹理图像。

它扩展了纹理球教程中的着色器代码,使其支持多个纹理,并展示了将它们组合在一起的方法。如果你还没有读过这些教程,现在就是个好机会。

表面层

[编辑 | 编辑源代码]

许多真实表面(例如左侧图像中的人类皮肤)由几层不同颜色、透明度、反射率等组成。如果最顶层是不透明的并且不透光,那么渲染表面时实际上并不重要。然而,在许多情况下,最顶层是(半)透明的,因此对表面的准确渲染必须考虑多层。

实际上,包含在 Phong 反射模型中的镜面反射(参见镜面高光教程)通常对应于一个透明层,它会反射光线:人类皮肤上的汗水、水果上的蜡、嵌入色素颗粒的透明塑料等等。另一方面,漫反射对应于最顶层透明层下方的层。

照亮这种分层表面不需要几何模型来表示这些层:它们可以用一个单一的、无限薄的多边形网格来表示。然而,照明计算必须对不同的层计算不同的反射,并且必须考虑光在层之间的传输(当光进入层时和当光离开层时)。这种方法的例子包括 Nvidia 的“Dawn”演示(参见“GPU Gems”一书的第 3 章,该书可以在网上获取)以及 Nvidia 的“Human Head”演示(参见“GPU Gems 3”一书的第 14 章,该书也可以在网上获取)。

本教程无法完整描述这些过程。只需说,层通常与纹理图像相关联,以指定它们的特征。这里,我们只展示了如何使用两种纹理和一种特定的组合方式。实际上,这个例子与层无关,因此说明了多纹理映射除了表面层之外还有更多应用。

未照亮地球的地图。
阳光照射地球的地图。

亮暗地球

[编辑 | 编辑源代码]

由于人类活动,地球的阴暗面并不完全黑暗。相反,人造灯光标志着城市的位置和延伸,如左侧图像所示。因此,地球的漫射光照不应该仅仅使阳光照射表面的纹理图像变暗,而应该将其与阴暗面的纹理图像混合。请注意,阳光照射的地球远比阴暗面的人造灯光亮;然而,我们降低了这种对比度,以便显示夜间纹理。

着色器代码扩展了纹理球教程中的代码,使其支持两种纹理图像,并使用漫射反射教程中描述的计算方法来处理单个方向光源

根据这个公式,漫射光照的级别 levelOfLighting 是 max(0, N·L)。然后,我们根据 levelOfLighting 混合白天纹理和夜间纹理的颜色。这可以通过将白天颜色乘以 levelOfLighting,将夜间颜色乘以 1.0 - levelOfLighting,然后将它们加在一起来确定片段的颜色来实现。或者,可以使用内置的 GLSL 函数 mixmix(a, b, w) = b*w + a*(1.0-w)),这很可能更有效。因此,片段着色器可以是(同样使用我们特定的 longitudeLatitude 中的纹理坐标计算)

         void main()
         {
            vec2 longitudeLatitude = vec2(
               (atan(texCoords.y, texCoords.x)/3.1415926+1.0)*0.5, 
               1.0 - acos(texCoords.z) / 3.1415926);

            nighttimeColor = 
               texture2D(nighttimeTexture, longitudeLatitude);    
            daytimeColor = 
               texture2D(daytimeTexture, longitudeLatitude);    
            gl_FragColor = mix(nighttimeColor, daytimeColor, 
               levelOfLighting);
               // = daytimeColor * levelOfLighting 
               // + nighttimeColor * (1.0 - levelOfLighting)
         }

请注意,这种混合与透明度教程中讨论的 alpha 混合非常相似,只是我们在片段着色器内部执行混合,并使用 levelOfLighting 而不是应该混合到另一个纹理之上的纹理的 alpha 分量(即不透明度)。实际上,如果 daytimeTexture 指定了 alpha 分量(参见透明纹理教程),我们可以使用这个 alpha 分量来将 daytimeTexture 混合到 nighttimeTexture 上。这对应于一个部分透明的层,它位于一个不透明层的顶部,该不透明层在最顶层透明的地方可见。

完整着色器代码

[编辑 | 编辑源代码]

着色器代码将 gl_FrontMaterial.emission 按分量乘以夜间纹理的纹理颜色,以便可以通过更改材质选项卡阴影 > 发射的值来控制其整体亮度。此外,光源的颜色 gl_LightSource[0].diffuse 也按分量乘以最终颜色,以便考虑彩色光源。

import bge

cont = bge.logic.getCurrentController()

VertexShader = """
         varying float levelOfLighting; // the level of diffuse  
            // lighting that is computed in the vertex shader
         varying vec4 texCoords;

         void main()
         {				
            vec3 normalDirection = 
               normalize(gl_NormalMatrix * gl_Normal);
            vec3 lightDirection;
 
            if (0.0 == gl_LightSource[0].position.w) 
               // directional light?
            {
               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);
               lightDirection = normalize(vertexToLightSource);
            }
            
            levelOfLighting = 
               max(0.0, dot(normalDirection, lightDirection));
            texCoords = gl_MultiTexCoord0;
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
"""

FragmentShader = """
         varying float levelOfLighting; 
         varying vec4 texCoords;
         uniform sampler2D daytimeTexture;
         uniform sampler2D nighttimeTexture;
        
         void main()
         {
            vec2 longitudeLatitude = vec2(
               (atan(texCoords.y, texCoords.x)/3.1415926+1.0)*0.5, 
               1.0 - acos(texCoords.z) / 3.1415926);

            vec4 nighttimeColor = 
               texture2D(nighttimeTexture, longitudeLatitude) 
               * gl_FrontMaterial.emission;    
            vec4 daytimeColor = 
               texture2D(daytimeTexture, longitudeLatitude);    
            gl_FragColor = mix(nighttimeColor, daytimeColor, 
               levelOfLighting) * gl_LightSource[0].diffuse;
               // = daytimeColor * levelOfLighting 
               // + nighttimeColor * (1.0 - levelOfLighting) * ...
         }
"""

mesh = cont.owner.meshes[0]
for mat in mesh.materials:
    shader = mat.getShader()
    if shader != None:
        if not shader.isValid():
            shader.setSource(VertexShader, FragmentShader, 1)
            shader.setSampler('daytimeTexture', 0)
            shader.setSampler('nighttimeTexture', 1)

使用这个着色器时,你必须确保白天纹理位于材质选项卡 > 纹理列表中的第一个位置,而夜间纹理位于第二个位置。否则,你必须调整 Python 脚本最后两行中的整数。

恭喜!你已经完成了最后一个关于基础纹理映射的教程。我们已经了解了

  • 表面层如何影响材质的外观(例如人皮肤、打蜡的水果、塑料等等)
  • 如何在对代表地球的球体进行纹理映射时考虑阴暗面的人造灯光。
  • 如何在着色器中实现这种技术。
  • 这与将 alpha 纹理混合到另一个不透明纹理之上的关系。

进一步阅读

[编辑 | 编辑源代码]

如果你还想了解更多

  • 关于基础纹理映射,你应该阅读纹理球教程
  • 关于漫射反射,你应该阅读漫射反射教程
  • 关于 alpha 纹理,你应该阅读透明纹理教程
  • 关于高级皮肤渲染,你可以阅读 Randima Fernando(编辑)出版的《GPU Gems》一书中的第 3 章“‘Dawn’ 演示中的皮肤” by Curtis Beeson and Kevin Bjorke,该书由 Addison-Wesley 于 2004 年出版,可以在网上获取,以及 Hubert Nguyen(编辑)出版的《GPU Gems 3》一书中的第 14 章“用于逼真的实时皮肤渲染的高级技术” by Eugene d’Eon and David Luebke,该书由 Addison-Wesley 于 2007 年出版,也可以在网上获取。


< GLSL 编程/Blender

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