跳至内容

GLSL 编程/Unity/纹理层

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

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

它扩展了 “纹理球体”部分 的着色器代码到多个纹理,并展示了一种组合它们的方法。如果您还没有阅读过该教程,这是一个很好的机会去阅读它。

表面层

[编辑 | 编辑源代码]

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

事实上,包含在 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)),这可能更有效。因此,片段着色器可能是

         #ifdef FRAGMENT
 
         void main()
         {
            vec4 nighttimeColor = _Color 
               * texture2D(_MainTex, vec2(textureCoordinates));    
            vec4 daytimeColor = _LightColor0 
               * texture2D(_DecalTex, vec2(textureCoordinates));    
            gl_FragColor = 
               mix(nighttimeColor, daytimeColor, levelOfLighting);
               // = daytimeColor * levelOfLighting 
               // + nighttimeColor * (1.0 - levelOfLighting)
        }
 
         #endif

请注意,这种混合与在 “透明度”部分 中讨论的 alpha 混合非常相似,除了我们在片段着色器中执行混合,并使用 levelOfLighting 而不是纹理的 alpha 分量(即不透明度),该纹理应该混合到另一个纹理的“上面”。事实上,如果 _DecalTex 指定了一个 alpha 分量(见 “透明纹理”部分),我们可以使用这个 alpha 分量来混合 _DecalTex_MainTex 上。这实际上是 Unity 的标准 Decal 着色器所做的,它对应于一个层,该层在可见的透明层的顶部部分透明。

完整着色器代码

[编辑 | 编辑源代码]

着色器属性的名称选择与回退着色器的属性名称一致 - 在这种情况下是 Decal 着色器(注意,回退 Decal 着色器和标准 Decal 着色器似乎以相反的方式使用这两个纹理)。此外,还引入了另一个属性 _Color,它与夜晚纹理的纹理颜色(按分量)相乘,以控制其整体亮度。此外,光源的颜色 _LightColor0 也与白天纹理的颜色(按分量)相乘,以考虑彩色光源。

Shader "GLSL multitexturing of Earth" {
   Properties {
      _DecalTex ("Daytime Earth", 2D) = "white" {}
      _MainTex ("Nighttime Earth", 2D) = "white" {} 
      _Color ("Nighttime Color Filter", Color) = (1,1,1,1)
   }
   SubShader {
      Pass {	
         Tags { "LightMode" = "ForwardBase" } 
            // pass for the first, directional light 

         GLSLPROGRAM

         uniform sampler2D _MainTex;
         uniform sampler2D _DecalTex;
         uniform vec4 _Color; 

         // The following built-in uniforms (except _LightColor0) 
         // are also defined in "UnityCG.glslinc", 
         // i.e. one could #include "UnityCG.glslinc" 
         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 float levelOfLighting; 
            // level of diffuse lighting computed in vertex shader
         varying vec4 textureCoordinates;

         #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 lightDirection;

            if (0.0 == _WorldSpaceLightPos0.w) // directional light?
            {
               lightDirection = normalize(vec3(_WorldSpaceLightPos0));
            } 
            else // point or spot light
            {
               lightDirection = vec3(0.0, 0.0, 0.0); 
                  // ignore other light sources
            }

            levelOfLighting = 
               max(0.0, dot(normalDirection, lightDirection));
            textureCoordinates = gl_MultiTexCoord0;
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
         
         #endif

         #ifdef FRAGMENT
         
         void main()
         {
            vec4 nighttimeColor = _Color 
               * texture2D(_MainTex, vec2(textureCoordinates));    
            vec4 daytimeColor = _LightColor0 
               * texture2D(_DecalTex, vec2(textureCoordinates));    
            gl_FragColor = 
               mix(nighttimeColor, daytimeColor, levelOfLighting);
               // = daytimeColor * levelOfLighting 
               // + nighttimeColor * (1.0 - levelOfLighting)
        }
         
         #endif

         ENDGLSL
      }
   } 
   // The definition of a fallback shader should be commented out 
   // during development:
   // Fallback "Decal"
}

运行此着色器时,请确保在场景中启用了方向光源。

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

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

进一步阅读

[编辑 | 编辑源代码]

如果您仍然想了解更多

  • 关于基本纹理,您应该阅读 “纹理球体”部分
  • 关于漫反射,您应该阅读 “漫反射”部分
  • 关于 alpha 纹理,您应该阅读 “透明纹理”部分
  • 关于高级皮肤渲染,您可以阅读 Randima Fernando(编辑)于 2004 年由 Addison-Wesley 出版、由 Curtis Beeson 和 Kevin Bjorke 撰写的“GPU Gems”一书中的第 3 章“‘Dawn’ 演示中的皮肤”,该书可在 在线 获取,以及 Hubert Nguyen(编辑)于 2007 年由 Addison-Wesley 出版、由 Eugene d’Eon 和 David Luebke 撰写的“GPU Gems 3”一书中的第 14 章“逼真的实时皮肤渲染的高级技术”,该书也可在 在线 获取。


< GLSL 编程/Unity

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