Unity

Unity

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

《Unity》Shader 03: Custom Lighting Shader

【Unity】 【shader】 【渲染】
作者:Hui-Ku Shih 時間:2015-10-13

原文:Unity Shader 03: Custom Lighting Shader

我們來寫一個用Unity 遠距光但不用到Unity 得lighting pass 得shader 吧  (總滴來說 比 legacy 得 diffuse surface shader 省一個draw call)

這個shader 會使用最簡單的 lambert shading 是一種很簡單計算的lighting model 

將normalize(單位化)後的 "光源方向" 與 vertex 得 normal 的內積(dot product)得到的結果 通常會用一個float 值來表示, 如果將這個結果直接回傳給螢幕,那結果就是黑白的。

 

這次我們在廠景裡,放上兩顆球,一顆shader 使用 legacy diffuse shader, 另一顆我們要用custom shader 寫出一模一樣的shading 結果而不使用UNITY CGINCLUDE 中給的任何計算遠聚光的方法與資料(注1)

 

《Unity》Shader 03: Custom Lighting Shader

 

以下是這次要講解的shader 

 

《Unity》Shader 03: Custom Lighting Shader

 

第一段code 跟上次的大同小異,要注意的是我們這次新增了幾個變數:

 

float4 _lightDir;

float _lightIntensity;

fixed4 _lightColor;

 

而這三個變數也沒在shader property 中顯示

所以我們要建立一個monobehavior 得 class 來 assign 這些變數

以下是該class   可以建立在該sphere 或是其他任何空的物件理   

 

《Unity》Shader 03: Custom Lighting Shader

 

這邊一個關鍵的方法是 Shader.SetGlobalVector / Shader.SetGlobalColor / Shader.SetGlobalFloat  可以一次指定所有shader 中沒有出現在shader property 得某個變數,而這邊我們就是要把在廠景中的遠聚光的方向,強弱,和顏色傳給我們的shader

 

回到shader,按照lambert shading model 我們將會需要用到模型頂點的normal 所以在一開始定義頂點資料的 struct 我們要把normal 加入:

 

float3 normal : NORMAL;  

 

在lambert shading model 中另外一個必要的資料是 單位化的光線方向 (normalized light direction vector)

所以我們需要在頂點輸出的struct 註冊這個資料, 當然頂點輸出的資料中也得有Normal 資料。

 

float3 normal : NORMAL;

float3  lightDirection  : TEXCOORD1;

 

接著我們來看 vertex shader 部分

 

《Unity》Shader 03: Custom Lighting Shader

 

首先,要計算該遠距光真實的世界座標。ㄟ等等,不是在廠景理就看得到了吗?遠距光的真實世界座標,並不能用廠景中的transform.position來表示所以我們得計算出一個模擬真實的位置

 

float3 lightPos = mul(_Object2World,_lightDir*-1000).xyz;

 

上述計算得意思是   我們用該遠距光的1000個單位方向vector 取的模擬的燈光位置 然後再轉換成世界座標

 

float3 lightDirection =normalize(lightPos.xyz - mul(o.vertex,_Object2World).xyz);

 

最後計算出真正的燈光到該頂點得單位方向vector 

然後輸出給 v2f struct 中的 lightDirection

 

另外就是要將normalize 過的 normal 輸出給 fragment shader 用

 

o.normal = normalize(v.normal);

 

好啦~  萬事俱備  我們可以寫最後的 fragment shader

 

《Unity》Shader 03: Custom Lighting Shader

 

fixed diff = max(dot(i.normal, i.lightDirection.xyz),0);

 

這一行就實踐 lambert shading model  

我們都知道內積的範圍是從 -1~1  所以這邊用 max( x, 0) 得意思是

如果 x>0 則 diff = x  如果 x < 0 則diff = 0 

所以出來的結果就會是 0 ~ 1  

 

最後輸出正確的顏色   

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

 

結果就跟上面的結果圖依樣,幾乎是一模一樣的結果  而且少一個 draw call   不信的可以自己試試看喔! 

 

注1:  如果render path 使用 forward ,那unity有限制燈光使用量  一個遠距光 和 四個點光源  所以在CGINCLUDE中  

定義遠距光的位置只有一個變數可以用  _WorldSpaceLightPos0  然後計算遠距光到頂點得單位向量也有提供方法:

inline float3 WorldSpaceLightDir( in float4 v )

用這個去算也可以少一個drawcall   但是缺點是,沒辦法算超過一個遠距光。

 

下一集 我們要用三個遠距光在shader 中模擬全局光   

 

一樣! 如果對內容有問題  有錯  還是想補充的  儘管上! 

 

以下是針對上述shader 作出的render速度上最佳化修正  大家可以自行比較 和思考

 

簡單來說  我們把lambert shading 計算放到了vertex shader 中會比較快  但是如果是面數少的模型會看起來有多邊型感

而既然是無限遠得遠距光  我們就可以不用計算真實的光的位置 (但是如果是點光源或是Spot都需要之道光的位置) 而直接用把光的方向向量給normalize 後就行了

最後別忘記把v2f struct 修正一下  就搞定啦  

 

《Unity》Shader 03: Custom Lighting Shader

《Unity》Shader 03: Custom Lighting Shader

 

 

最後  built-in 多的那個 draw call 之迷解開了  

在project setting quality 裡  把shadow disable 掉 多的draw call就消失了  

至於 明明就已經在物體設定中關掉了shadow  為何還用的一個pass 畫看不到的shadow

請大家幹譙 Unity 好了......


x