前言
最近有人問到寫Shader需要注意哪些地方及如何優化,正好筆者也在研究這方面,這裏主要針對Unity來說,其它平臺或引擎也可以參考,本文主要分如下幾個方面來說:Shader的選擇,屬性和狀態的設置,數據類型選擇,代碼編寫,舉例,調試
Unity中Shader的選擇
Unity中現在可以新建4種Shader,分別是Standard Surface Shader,Unlit Shader,Image Effect Shader,Compute Shader.
- Standard Surface Shader: 主要用於光照,Unity封裝好了許多光照模型,如果項目中需要比較多的光照,可以選用這個類型的Shader
- Unlit Shader:就是普通的Vertex&Fragment Shader,可以用CG語言來寫,這種Shader封裝的比較少,效果,光照都要自己碼代碼。
- Image Effect Shader:主要用於後期處理,和Unlit Shader一樣,唯一有點區別的是,關閉了Cull,ZWrite,打開了ZTest。
- Compute Shader:這個主要是GPU計算,這個不太熟悉,也就不探討這個了。
筆記的建議是儘量用Unlit Shader來寫,一方面封裝的少,可以更深入瞭解底層,一方面可以自己動手實現任何想要的效果。也沒有引入好多光照方面的變量,效率上會比Surface Shader高
如果想省時間,又不想了解光照具體實現的,Surface Shader可以幫你快速實現效果。
屬性和狀態的設置
- 大部分shader都有_Color屬性,但如果你並沒有使用,那麼就應該去掉,避免無謂的計算,其它的自動生成的屬性或無用的屬性也都應該去掉。
- Alpha Test,Cull,Zwrite,ZTest等能關的都關掉,需要時再打開。
- 排除延遲渲染的pass(通道)
- 關閉渲染附加通道(reduce lights Shader process)或者直接指定渲染
- 添加noforwardadd,只對一個方向光進行逐像素紋理運算,其他所有燈光都強制轉換成逐頂點的光照:
CGPROGRAM
#pragma surface surf SimpleLambert exclude_path:prepass noforwardadd
數據類型選擇(降低內存,提高運算速度)
- float:完整的32位浮點格式,對應的值有float2,float3,float4,適用於頂點變換,但性能最慢。
- half:簡化的16位浮點格式,對應的值有half2,half3,half4,適用於紋理UV座標,顏色值等,比float大約快兩倍。
- fixed:10位定點格式,對應的值有fixed2,fixed3,fixed4,適合顏色,光照,單位向量和其它高性能操作,速度大約比float快4倍。
- 爲獲得最佳性能,挑選精度儘可能小的浮點格式至關重要。很多臺式機 GPU 均完全忽略運算精確,但是它對於大量移動 GPU 的性能具有重大影響
代碼編寫
- 使用swizzle是非常快的
- 儘量把計算合併成向量計算,記住向量計算和一個float計算那樣快!
- shader內置的函數比條件判斷和分支的效率要高很多,因爲GPU主要是爲了計算而不是做判斷,因此在GPU編程中,if else ,switch case等條件語句和太複雜的邏輯是不推薦的。相應的,可以用step()等函數進行替換,用階梯函數的思維來構建條件語句
- 共享UV,使用了MainTexture的UV座標來代替法線貼圖的UV座標,去掉法線貼圖的輸入,使用UnpckNormal共享uv。
- 減少處理的光源個數,把其他所有光源當成頂點光源,而在計算像素顏色時只計算一個主平行光作爲像素光源
- 使用近似值代替精確值
- 減少或壓縮貼圖的使用
- 充分利用內置光照模型
- 使用半角向量(half vector)作爲視線方向並用於高光計算,因爲半角向量是基於逐頂點計算的
- 複雜的數學函數(如 pow,exp,log,cos,sin,tan 等等)會大大增加 GPU 負擔,所以一個好的經驗法則是,此類運算在每個像素中不得超過一個。考慮在合適時使用查找紋理作爲替代選擇
- 只計算需要計算的東西:減少無用的頂點,避免過多的頂點計算(如過多的光源),過於複雜的光照計算(複雜的光照模型)避免指令數量太多,減少VS的長度和複雜程度
- 儘量在VS前計算:儘量將運算從FS移到VS,或直接通過腳本設置固定值
舉例
- 計算合併成向量計算,向量計算和float一樣快:
比如:
修改前:
float x,y;
x=x*a;
y=y*b;
修改後:
float2 v = float2(x,y);
v = v*float2(a,b);
結果:前一種需要2次乘法,而後一種只需要1次。
- 用step替換分支
比如:
修改前:
float4 a;
if(b>1)
{
a.a=1;
}else
{
a.a=0.5;
}
修改後:
float4 a;
float tmp = step(b,1);//if(1>=b)=>1 else =>0;
a=tmp*0.5+(1-tmp);
結果:if else可以被step出來的0或1的乘法代替。
- 使用swizzle
如:
修改前:
float4 a=float4(1,1,1,1);
a.w = 2;
a.z = 3;
修改後:
float4 a=float4(1,1,1,1);
a.wz=float2(2,3);
調試
- 根據Unity自帶的性能分析器調試
- FrameDebugger
- glsl-optimizer 優化工具,glsl_optimizer 是一個免費開源的glsl優化器。可以生成GPU無關的shader優化代碼
總結
- 由於GPU受限的顯存空間及GPU架構上的不同,導致Shader不同的寫法對於其執行影響非常大,代碼的優化思想也不一樣。
- GPU是SIMD的架構,即單指令多數據流架構,也就是說同時執行n個數據和執行一個數據的效率是一樣的,所以儘量把並行的計算搬到GPU上。
- GPU是以向量計算爲基礎設計的,專門對向量運算做了硬件層面的處理,所以執行向量乘法和執行一個float乘法的效率是一樣的,並不像CPU那樣要多執行幾次。
- 避免使用分支或條件判斷語句,這種控制語句涉及到一些同步等消耗的操作,大多數這種語句可以用數值的方式替代
- 輕量級Shader主要是內存佔用量,貼圖使用量,還有數據使用量來優化
- 還可以進行非常多的優化項目,比如 函數內聯,死代碼刪除,常量摺疊,常量傳遞,數學優化等等