@3d
什么是遮罩呢?简单来说,遮罩能够容许咱们保护某些区域,使它们免于 某些修改。例如,在以前的实现中,咱们都是把高光反射应用到模型表面的全部地方,即全部的像素都使用一样大小的高光强度和高光指数。但有时咱们但愿模型表面的某些区域的反光强一些,而某些区域弱一些。为了获得更加细腻的效果,咱们就可使用一张遮罩纹理来控制光照。另外一种是常见的应用是在制做地形材质时须要混合多张图片,例如表现草地的纹理、表现石子的纹理、表现裸露土地的纹理等,使用遮罩纹理能够控制如何混合这些纹理。
使用遮罩纹理的通常流程是:经过采样获得遮罩纹理的纹素值,而后使用其中某个(或几个)通道的值(texel.r)来与某种表面属性进行相乘,这样当该通道的值为0时,能够保护表面不受该属性影响。总而言之,使用遮罩纹理可让美术人员更加精准(像素级别)地控制模型表面的各类性质。code
在本节中,咱们将学习如何使用一张高光遮罩纹理,逐像素的控制模型表面的高光反射强度。下图西安设了只包含漫反射、未使用高光反射和使用遮罩的高光反射的对比效果。
咱们使用的遮罩纹理如图所示,能够看出,遮罩纹理可让咱们更加精细地控制光照细节,获得更加细腻的效果。
步骤:
(1)咱们须要在Properties语义块中声明更多的变量来控制高光反射:orm
Properties{ _Color("Color Tint",Color)={1,1,1,1} _MainTex("Main Tex",2D)="white"{} _BumpMap("Normal Map",2D)="bump"{} _BumpScale("Bump Scale",Float)=1.0 _SpecularMask("Specular Mask",2D)="white"{} _SpecuarScale("Specular Scale",Float)=1.0 _Specular("Specular",Color)=(1,1,1,1) _Gloss("Gloss",Range(8.0,256))=20 }
上面属性中的_SpecularMask既是咱们须要使用的高光反射遮罩纹理,_SpecularScale则是用于控制遮罩影响度的系数。
(2)而后,咱们在SubShader语义块中定义了Pass语义块,并在Pass的第一行指明了该Pass的光照模式:blog
SubShader{ Pass{ Tags{"LightMode"="ForwardBase"} } }
(3)咱们须要定义和Properties中各个属性类型相匹配的变量:游戏
fixed4 _Color; sampler2D _MainTex; float4 _MainTex_ST; sampler2D _BumpMap; float _BumpScale; sampler2D _SpecularMask; float _SpecularScale; fixed4 _Specular; float _Gloss;
咱们为主纹理_MainTex、法线纹理_BumpMap和遮罩纹理_SpecularMask定义了它们共同使用的纹理属性_MainTex_ST。这意味着,在材质面板中修改主纹理的平铺系数和偏移系数会同时影响3个纹理采样。使用这种方式可让咱们节省须要存储的纹理坐标数目,若是咱们为每个纹理都使用一个单独的属性变量TextureName_ST,那么随着纹理数目的增长,咱们会迅速占满顶点着色器中可使用的插值寄存器。而不少时候,咱们不须要对纹理进行平铺和位移操做,或者不少纹理可使用同一种平铺和位移操做,此时咱们就能够对这些纹理使用同一个变换后的纹理坐标进行采样。
(4)定义顶点着色器的输入和输出结构体图片
struct a2v{ float4 vertex:POSITION; float3:NORMAL; float4 tangent:TANGENT; float4 texcoord:TEXCOORD0; }; struct v2f{ float4 pos:SV_POSITION; float2 uv:TEXCOORD0; float3 lightDir:TEXCOORD1; float3 viewDir:TEXCOORD2; };
(5)在顶点着色器中,咱们对光照方向和视角方向进行了坐标空间变换,把它们从模型空间变换到了切线空间中,以便在片元着色器中和法线进行光照运算:get
v2f vert(a2v v){ v2f o; o.pos=mul(UNITY_MATRIX_MVP,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; }
(6)使用遮罩纹理的地方是片元着色器,咱们使用它来控制模型表面的高光反射强度:it
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; tangentNormal.z=sqrt(1.0-saturate(dot(tangentNormal.xy,tangentNormal.xy))); fixed3 albedo = tex2D(_MainTex,i.uv).rgb*_Color.rgb; fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz*albedo; fixed3 diffuse=_LightColor0.rgb*albedo*max(0,dot(tangentNormal,tangentLightDir)); fixed3 halfDir=normalize(tangentLightDir+tangentViewDir); //Get the value fixed specularMask=tex2D(_SpecularMask,i.uv).r*_SpecularScale; //Compute specular term with the specular mask fixed3 specular=_LightColor0.rgb*_Specular.rgb*pow(max(0,dot(tangentNormal,halfDir)),_Gloss)*specularMask; return fixed(ambient+diffuse+specular,1.0); }
环境光照和漫反射光照和以前使用过的代码彻底同样。在计算高光反射时,咱们首先对遮罩纹理_SpecularMask进行采样。因为本书中使用的遮罩纹理的每一个纹素的rgb份量其实都是同样的,代表了该点对应的高光反射强度,在这里咱们选择使用r份量来计算掩码值。而后咱们获得的掩码值和_SpecularScale相乘,一块儿来控制高光的反射强度。
须要说明的是,咱们使用的这张遮罩纹理其实有不少空间被浪费了——它的rgb份量存储的都是同一个值。在实际的游戏制做中,咱们每每会充分利用遮罩纹理中的每个颜色通道来存储不一样的表面属性。
(7)最后咱们为该UnityShader设置合适的Fallback:io
Fallback"Specular"
在真实的游戏制做过程当中,遮罩纹理已经不止限于保护某些区域使它们免于修改,而是存储任何咱们但愿逐像素控制的表面属性。一般,咱们会充分利用一张纹理的RGBA四个通道,用于存储不一样的属性。例如咱们把高光反射强度存储在R通道,把边缘光照的强度存储在G通道,把高光发射的指数部分存储在B通道,最后把自发光强度存储在A通道,