Unity

Unity

官方網站:點擊進入
官方粉絲團:點擊進入

《Unity》 Shader 04: normal map shader

【unity】 【norma map shader】 【3D】 【渲染】
作者:Hui-Ku Shih 時間:2015-10-13

在開始講normal map shader 之前,我們要複習一下上次的lighting model:光的單位向量和vertex normal 的 dot product 就是基本的 Lambert lighting model. 利用這個簡單的概念,我們基本上可以再Unity 理面不放一盞光,就可以render 出多盞光的效果。如果有興趣的人可以參考 三光技術,直接在shader理打出便宜漂亮的光。

 

~進入正題~

看似簡單? 為何看似簡單,因為大部分所需要的計算Unity 都幫你搞定了,如果你用surface shader 寫 normal map shader,只需要在surface output 理把normap map 計算後的結果指定給 surface normal 就可以了,大概是這樣 

 

o.normal =  UnpackNormal( text2D( _NormalMap, IN.uv_NormalMap) );

 

但是為啥很複雜呢?  因為normal map 的實現需要 tangent space rotation matrix, 之前的lambert lighting model 雖然可以用,但是light direction 必須轉成tangent spece 才行。而 tengent space rotation matrix 是 float3x3( v.tangent.xyz, binormal, v.normal )

從這個公式我們得知,我們在 vertex struct 必須有 tangent  normal 和 binormal, 看起來可能會像這樣

 

struct appdata {    

float4 vertex : POSITION;    

half2 texcoord : TEXCOORD0;    

fixed3 normal : NORMAL;    

float3 binormal : BINORMAL;  

float4 tangent : TANGENT;

};

 

ㄟ 如果你這樣寫大概不會過.........  

因為圖學課教過 binormal = cross( normalize(v.normal), normalize(v.tangent.xyz))

所以上述可以自己的struct必須踢掉binormal 

 

struct appdata {    

float4 vertex : POSITION;    

half2 texcoord : TEXCOORD0;    

fixed3 normal : NORMAL;     

float4 tangent : TANGENT;

};

 

來問題又來了...... 為何tengent 要用float4 而不是 float3, 這要扯到3D繪圖軟體怎麼計算tangent的

我們以MAYA為例 
 

如果你懶得看上面文章,小弟在這裡大略的說,Maya 計算vertex 的 tangent 是靠 UV,而UV的順序有正向和反向,例如我們在作一個玩全對稱的模型時,UV通常為了節省空間,我們會先unwrap 一半 mesh 後 把另外一半反過來重疊UV。這時候就有UV是正向和反向。為了計算出正確的tangent space 我們需要第四個float 去記錄UV的方向。所以上述計算binormal 的公式就必須修正為

 

 binormal = cross( normalize(v.normal), normalize(v.tangent.xyz)) *  v.tangent.w

 

這樣最後的tangent space rotation matrix 才會正確

 

非常幸運的是,這些UniytCG.cginc 都幫你定義好了

你只需要很不經意的

在vertex shader function 中寫上

 

TANGENT_SPACE_ROTATION;

 

就自動定義了 rotation 這個變數 (這個變數定義了 tangent space rotation matrix)   

後面你就可以開心的轉換作標空間到tangent space 像是

 

o.lightDirection =mul(rotation, -normalize(_lightDir));

 

所以以下是 vertex shader function

 

v2f vert (appdata v) {

v2f o; TANGENT_SPACE_ROTATION;

o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);

// turn light into Tangent space

o.lightDirection =mul(rotation, -normalize(_lightDir));

return o; }

 

寫到這了有人會問啊~  

之前講lighting model 的時候不是可以把 Lambert 放在vertex shader 裡寫吗?  

這次怎麼不幹?  很簡單,在sampler texture (tex2D(.....))得過程只能在 fragment shader 裡寫

而我們這次的normal 是由normal map 來取代,所以只能在fragment shader 裡完成剩下的事情

 

於是乎

 

我們先sampler normal map 

 

fixed4 normalCol = tex2D(_NormalTex, i.uv);

 

然後計算真實的normal 向量

 

fixed3 norm = UnpackNormal(normalCol); (這理的UnpackNormal 是 UnityCG.cginc 理的定義 如祥知內容 請看自己的UnityCG.cginc)

 

最後把這個 norm 拿來套入 lambert lighting model 

 

fixed diff = max(dot(norm,normalize(i.lightDirection)),0);

 

到這裡可以看出  有沒有normal map 的所需計算差很多吧~  normal map 至今還是被認為在mobile 理非常耗效能

 

最後我們看依下完整的 fragment shader function

 

fixed4 frag (v2f i) : COLOR {

 

  //Get normal color 

fixed4 normalCol = tex2D(_NormalTex, i.uv);

 

  //calculate normal for Unity 

fixed3 norm = UnpackNormal(normalCol);

 

  //calculate new diff based on normal map 

fixed diff = max(dot(norm,normalize(i.lightDirection)),0);

 

//final color

fixed4 outCol = tex2D(_MainTex, i.uv)*diff*_Color*_lightIntensity*_lightColor;

 

  return outCol;

}

 

 

最後大家一定都會發現  Lambert model 下的 normal map 效果很不明顯   

然後大家一定都會再加入 speculer 來增強normal map 的效果   

然後就愈來愈貴  愈來愈貴..........最後就變成了 一個shader 通吃

那就是  Unity 5 中的 standard shader.....................

 

 

《Unity》 Shader 04: normal map shaderlambert

 

《Unity》 Shader 04: normal map shaderlambert normal

 

《Unity》 Shader 04: normal map shaderlambert normal spec

 

至於怎麼寫 specular  

我們下回分曉~ 或是你想自己挑戰也可以~  
 

※作者作品

《落跑藍圖》 Android
《落跑藍圖》iOS


x