@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通道,