跳转到内容

Cg 编程/Unity/纹理层

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

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

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

表面层

[编辑 | 编辑源代码]

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

事实上,Phong 反射模型中包含的镜面反射(参见“镜面高光”部分)通常对应于反射光的透明层:人类皮肤上的汗液、水果上的蜡、包含嵌入色素粒子的透明塑料等。另一方面,漫反射对应于最顶层透明层下面的层。

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

对这些过程的完整描述超出了本教程的范围。需要说明的是,层通常与纹理图像相关联以指定它们的特性。这里我们只展示如何使用两个纹理和一种特定的组合方式。实际上,这个示例与层无关,因此说明了多重纹理的应用范围比表面层更广泛。

未照亮地球的地图。
照亮地球的地图。

亮暗地球

[编辑 | 编辑源代码]

由于人类活动,地球的背阴面并不完全黑暗。相反,人造光线标出了城市的位置和范围,如左侧图像所示。因此,地球的漫反射照明不应仅仅是调暗阳光照射面的纹理图像,而应该将其与背阴面的纹理图像混合。请注意,阳光照射的地球比背阴面的人造灯光亮得多;但是,我们降低了这种对比度,以便展示夜间纹理。

着色器代码扩展了“纹理球体”部分中的代码以支持两个纹理图像,并使用“漫反射”部分中描述的计算方法来处理单个方向光源

根据这个公式,漫反射照明级别levelOfLighting为max(0, N·L)。然后,我们根据levelOfLighting混合白天纹理和夜间纹理的颜色。这可以通过将白天颜色乘以levelOfLighting,并将夜间颜色乘以1.0 - levelOfLighting来实现,然后再将它们相加以确定片段的颜色。或者,可以使用内置的 Cg 函数lerplerp(a, b, w) = b*w + a*(1.0-w)),该函数可能效率更高。因此,片段着色器可以为

         float4 frag(vertexOutput input) : COLOR
         {
            float4 nighttimeColor = 
               tex2D(_MainTex, input.tex.xy);    
            float4 daytimeColor = 
               tex2D(_DecalTex, input.tex.xy);    
            return lerp(nighttimeColor, daytimeColor, 
               input.levelOfLighting);
               // = daytimeColor * levelOfLighting 
               // + nighttimeColor * (1.0 - levelOfLighting)
         }

请注意,这种混合与“透明度”部分中讨论的 alpha 混合非常相似,不同之处在于我们在片段着色器内部执行混合,并使用levelOfLighting代替 alpha 分量(即纹理的透明度),该纹理应该与其他纹理“混合”。事实上,如果_DecalTex指定了 alpha 分量(参见“透明纹理”部分),我们可以使用此 alpha 分量将_DecalTex_MainTex混合。这实际上是 Unity 的标准Decal着色器所做的,它对应于一个部分透明的层,位于一个不透明层的顶部,该层在最顶层透明的地方可见。

完整着色器代码

[编辑 | 编辑源代码]

着色器属性的名称选择与回退着色器的属性名称一致——在本例中为Decal着色器(请注意,回退Decal着色器和标准Decal着色器似乎以相反的方式使用这两个纹理)。此外,引入了额外的属性_Color,并将其(按分量)乘以夜间纹理的纹理颜色,以控制其整体亮度。此外,光源的颜色_LightColor0也(按分量)乘以白天纹理的颜色,以考虑彩色光源。

Shader "Cg 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 
 
         CGPROGRAM
 
         #pragma vertex vert  
         #pragma fragment frag 
 
         #include "UnityCG.cginc"
         uniform float4 _LightColor0; 
            // color of light source (from "Lighting.cginc")
 
         uniform sampler2D _MainTex;
         uniform sampler2D _DecalTex;
         uniform float4 _Color; 
 
         struct vertexInput {
            float4 vertex : POSITION;
            float3 normal : NORMAL;
            float4 texcoord : TEXCOORD0;
         };
         struct vertexOutput {
            float4 pos : SV_POSITION;
            float4 tex : TEXCOORD0;
            float levelOfLighting : TEXCOORD1;
               // level of diffuse lighting computed in vertex shader
         };
 
         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output;
 
            float4x4 modelMatrix = unity_ObjectToWorld;
            float4x4 modelMatrixInverse = unity_WorldToObject;
 
            float3 normalDirection = normalize(
               mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
            float3 lightDirection = normalize(
               _WorldSpaceLightPos0.xyz);
 
            output.levelOfLighting = 
               max(0.0, dot(normalDirection, lightDirection));
            output.tex = input.texcoord;
            output.pos = UnityObjectToClipPos(input.vertex);
            return output;
         }
 
         float4 frag(vertexOutput input) : COLOR
         {
            float4 nighttimeColor = 
               tex2D(_MainTex, input.tex.xy) * _Color;    
            float4 daytimeColor = 
               tex2D(_DecalTex, input.tex.xy) * _LightColor0;    
            return lerp(nighttimeColor, daytimeColor, 
               input.levelOfLighting);
               // = daytimeColor * levelOfLighting 
               // + nighttimeColor * (1.0 - levelOfLighting)
         }
 
         ENDCG
      }
   } 
   Fallback "Decal"
}

运行此着色器时,请确保场景中有一个激活的方向光源。

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

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

进一步阅读

[编辑 | 编辑源代码]

如果你还想了解更多

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

< Cg 编程/Unity

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