遮罩纹理实现
遮罩纹理(mask texture)是本章要介绍的最后一种纹理,它非常有用,在很多商业游戏中都可以见到它的身影。那么什么是遮罩呢?简单来讲,遮罩允许我们可以保护某些区域,使它们免于某些修改。例如,在之前的实现中,我们都是把高光反射应用到模型表面的所有地方,即所有的像素都使用同样大小的高光强度和高光指数。但有时,我们希望模型表面某些区域的反光强烈一些,而某些区域弱一些。为了得到更加细腻的效果,我们就可以使用一张遮罩纹理来控制光照。另一种常见的应用是在制作地形材质时需要混合多张图片,例如表现草地的纹理、表现石子的纹理、表现裸露土地的纹理等,使用遮置纹理可以控制如何混合这些纹理。使用遮罩纹理的流程一般是:通过采样得到遮罩纹理的纹素值,然后使用其中某个(或某几个)通道的值(例如 texel.r)来与某种表面属性进行相乘,这样,当该通道的值为0时,可以保护表面不受该属性的影响。总而言之,使用遮罩纹理可以让美术人员更加精准(像素级别)地控制模型表面的各种性质。
Shader "Custom/MaskTexture"
{
Properties{
_Color("Color Tint",Color)=(1,1,1,1)
_MainTex("Main Tex",2D) = "while"{} //主纹理
_BumpMap("Normal Tex",2D) = "while"{}//法线纹理
_BumpScale("Bump Scale",Float) = 1
_SpecularMask("Specular Mask",2D) = "while"{}//高光反射遮罩纹理
_SpecularScale("Specular Scale",Float) = 1 //控制遮罩影响度系数
_Specular("Specular",Color) = (1,1,1,1)
_Gloss("Gloss",Range(8,256)) = 20
}
SubShader{
Pass{
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;//贴图 xy 偏移值
sampler2D _BumpMap;
float _BumpScale;
sampler2D _SpecularMask;
float _SpecularScale;
fixed4 _Specular;
float _Gloss;
struct a2v{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float4 texcoord : TEXCOORD0;
};
struct v2f{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float3 lightDir : TEXCOORD2;
float3 viewDir : TEXCOORD3;
};
//在顶点着色器中,对光照和视角方向进行坐标空间转换,
//把他们从模型空间转换到切线空间中,以便在片元着色器中
//和法线进行光照运算
v2f vert(a2v v){
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv.xy = v.texcoord.xy*_MainTex_ST.xy+_MainTex_ST.zw;
TANGENT_SPACE_ROTATION;//法线空间转换矩阵宏定义;
//将光照和视角方向由模型空间转换到世界空间再转换到切线空间
o.lightDir = mul(rotation,ObjSpaceLightDir(v.vertex)).xyz;
o.viewDir = mul(rotation,ObjSpaceViewDir(v.vertex)).xyz;
return o;
}
//使用遮罩纹理的是片元着色器,使用它来控制模型表面的高光反射强度
fixed4 frag(v2f i):SV_Target{
fixed3 tangentLightDir = normalize(i.lightDir);
fixed3 tangentViewDir = normalize(i.viewDir);
fixed3 tangentNormal = UnpackNormal(tex2D(_BumpMap,i.uv));
tangentNormal.xy*=_BumpScale;
//由于法线是单位矢量,因此z分量可以xy分量计算而得
tangentNormal.z = sqrt(1.0-saturate(dot(tangentNormal.xy,tangentNormal.xy)));
//计算反射率
fixed3 albedo = tex2D(_MainTex,i.uv).rgb*_Color;
//计算环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz*albedo;
//计算漫反射
fixed3 diffuse = _LightColor0.rgb*albedo*max(0,dot(tangentNormal,tangentLightDir));
//计算高光反射
fixed3 halfDir = normalize(tangentLightDir+tangentViewDir);
//得到遮罩值
fixed speculuarMask = tex2D(_SpecularMask,i.uv).r*_SpecularScale;
//计算带有遮罩的高光反射
fixed3 specular = _LightColor0.rgb*_Specular.rgb*pow(max(0,dot(tangentNormal,halfDir)),_Gloss)*speculuarMask;
return fixed4(ambient+diffuse+specular,1.0);
}
ENDCG
}
}
Fallback "Specular"
}
其他遮罩纹理
在真实的游戏制作过程中,遮罩纹理已经不止限于保护某些区域使它们免于某些修改,而是可以存储任何我们希望逐像素控制的表面属性。通常,我们会充分利用一张纹理的 RGBA四个通道,用于存储不同的属性。例如,我们可以把高光反射的强度存储在R通道,把边缘光照的强度存储在G通道,把高光反射的指数部分存储在B 通道,最后把自发光强度存储在A通道在游戏《DOTA2》的开发中,开发人员为每个模型使用了4张纹理:一张用于定义模型颜色张用于定义表面法线,另外两张则都是遮罩纹理。这样,两张遮罩纹理提供了共种额外的表面属性,这使得游戏中的人物材质自由度很强,可以支持很多高级的模型属性。读者可以在他们的官网上找到关于《DOTA2》的更加详细的制作资料,包括游戏中的人物模型、纹理以及制作手册等。这是非常好的学习资料。