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 函数lerp
(lerp(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 出版,也可以在网上获取。