第六章 基础纹理(3)

目录

@

渐变纹理

尽管在一开始,我们在渲染中使用纹理是为了定义一个物体的颜色,但后来人们发现,纹理其实可以用来存储任何表面属性。一种常见的用法就是使用渐变纹理来控制漫反射光照的结果。在之前计算漫反射光照结果的时候,我们都是使用表面法线和光照方向的点积结果与材质的反射率相乘来得到表面的漫反射光照。但有时,我们需要更加灵活的控制光照。使用这种技术,可以保证物体的轮廓线相比于之前使用的传统漫反射光照更加明显,而且能够提供多种色调变化。
在本节中,我们将学习如何用一张渐变纹理来控制漫反射光照。然后得到类似下图的效果。
第六章 基础纹理(3)
可以看出,使用这种方式可以*的控制物体的漫反射光照。不同的渐变纹理有不同的特性。例如在左边的图中,我们使用一张从紫色调到浅黄的渐变纹理;而中间的渐变纹理是从黑色向浅灰色靠拢,而中间的分界线略微微发红,这是因为画家在插画中往往会在阴影中使用这种色调;右边的渐变纹理则通常被用于卡通风格的渲染,这种渐变纹理中的渲染通常是突变的,既没有平滑过渡,以此来模拟卡通中的阴影色块。
为了达到上述效果,我们需要进行以下工作:
(1)我们在Properties语义块中声明一个纹理属性来存储渐变纹理:

Properties{
_Color("Color Tint",Color)={1,1,1,1}
_RampTex("Ramp Tex",2D)="white"{}
_Specualr("Specular",Color)={1,1,1,1}
_Gloss("Gloss",Range(8.0,256))=20
}

(2)然后,我们在Subshader语义块中定义了一个Pass语义块,并且在Pass的第一行指明了该Pass的光照模式:

Subshader{
Pass{
Tags{"LightMode"="ForwardBase"}
}
}

LightMode是Pass标签中的一种,它用于定义该Pass在Unity的光照流水线中的角色
(3)然后,我们使用CGPROGRAM和ENDCG开包围住Cg代码片,以定义最重要的顶点着色器和片元着色器代码。我们使用#pragma指令来告诉Unity,我们定义的顶点着色器和片元着色器叫什么名字。在本例中它们名字分别是vert和frag

CGPROGRAM
#pragma vertex vert
#pragma fragment frag

(4)为了使用Unity内置的一些变量,如_LightColor0,还需要包含进Unity的内置文件Lighting.cginc:

#include"Lighting.cginc"

(5)随后,我们需要定义和Properties中各个属性相匹配的变量:

fixed4 _Color;
sampler2D _RampTex;
float4 _RampTex_ST;
fixed4 _Specular;
float _Gloss;

我们为渐变纹理_RampTex定义了它的纹理属性变量_RampTex_ST
(6)定义顶点着色器输入和输出结构体

struct a2v{
float4 vertex :POSITION;
float3 normal:NORMAL:
float4 texcoord:TEXCOORD0;
};
struct v2f{
float4 pos:SV_POSITION;
float3 worldNormal:TEXCOORD0;
float3 worldPos:TEXCOORD1;
float2 uv:TEXCOORD2;
}

(7)定义顶点着色器:

v2f vert(a2v v){
v2f o;
o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
o.worldNormal=UnityObjectWorldNormal(v.normal);
o.worldPos=mul(_Object2World,v.vertex).xyz;
o.uv=TRANSFORM_TEX(v.texcoord,_RampTex);
}

我们使用了内置的TRANSFORM_TEX宏来计算经过平铺和偏移后的纹理坐标。
(8)接下来是关键的片元着色器:

fixed4 frag(v2f i):SV_Target{
fixed3 worldNormal=normalize(i.worldNormal);
fixed3 worldLightDir=normalize(UnityWorldLightDir(i.worldPos));
fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz;
//Use the texture to sample the diffuse color
fixed halfLambert=0.5*dot(worldNormal,worldLightDir)+0.5;
fixed3 diffuseColor=tex2D(_RampTex,fixed2(halfLambert,halfLambert)).rgb*_Color.rgb;
fixed3 diffuse = _LightColor0.rgb*diffuseColor;
fixed3 viewDir=normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 halfDir=normalize(worldLightDir+viewDir);
fixed3 specular=_LightColor0.rgb*_Specular.rgb*pow(max(0,dot(worldNormal,halfDir)),_Gloss);
return fixed4(ambient+diffuse+specular,1.0);
}

在上面的代码中,我们使用了以前所讲的半兰伯特模型,通过对该法线方向和光照方向的点积做一次0.5倍的缩放以及0.5倍的平移来计算半兰伯特部分的halfLambert。这样我们得到的halfLambert的范围被映射到了[0,1]之间。之后我们使用halfLambert来构建一个纹理坐标,并用这个纹理坐标对渐变纹理_RampTex进行采样,由于_RampTex实际上就是一个一维纹理(它在纵轴方向上颜色不变),因此纹理坐标的u和v方向我们都使用了halfLambert。然后把从渐变纹理采样得到的颜色和材质颜色_Color相乘,得到最终的漫反射颜色。剩下的代码就是计算高光反射和环境光,并把它们的结果进行相加。
(9)最后,我们为该UnityShader设置合适的Fallback:

Fallback"Specular"

需要注意的是。我们需要把渐变纹理的Wrap Mode设置为Clamp模式,以防止由于浮点数精度而造成的问题。下图给出了WrapMode分别为Repeat和Clamp模式的效果对比。
第六章 基础纹理(3)
可以看出,左图(使用Repeat模式)在高光区域有一些黑点。这是由于浮点数精度造成的,当我们使用fixed2(halfLambert,halfLambert)对渐变纹理进行采样时,虽然理论上halfLambert的值在[0,1]之间,但可能会有1.00001这样的值出现。如果我们使用的是Repeat模式,此时就会舍弃整数部分,只保留小数部分,得到的值就是0.00001,对应了渐变图中最左边的值,即黑色。因此,就会出现图中这样在高光区域反而有黑点的情况。我们只需把纹理的WrapMode设为Clamp模式就可以解决这种问题。

上一篇:只需三步,实时多边形折射


下一篇:OpenGL学习笔记——光照