Unity實驗室之Shader優化

前言

最近有人問到寫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主要是內存佔用量,貼圖使用量,還有數據使用量來優化
  • 還可以進行非常多的優化項目,比如 函數內聯,死代碼刪除,常量摺疊,常量傳遞,數學優化等等
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章