1. 程式人生 > >【Unity Shaders】Mobile Shader Adjustment—— 什麽是高效的Shader

【Unity Shaders】Mobile Shader Adjustment—— 什麽是高效的Shader

ria 類型 告訴 菜單 inline 反射 當我 自己的 html

http://blog.csdn.net/candycat1992/article/details/38358773

本系列主要參考《Unity Shaders and Effects Cookbook》一書(感謝原書作者),同時會加上一點個人理解或拓展。

這裏是本書所有的插圖。這裏是本書所需的代碼和資源(當然你也可以從官網下載)。

========================================== 分割線 ==========================================

寫在前面

之前學習的各種Shader時,我們從沒有考慮在所有平臺下的可用性。Unity是一個強大的跨平臺遊戲引擎,但這也決定了在編寫代碼時我們需要考慮更多的平臺因素。對於Shader而言,如果沒有進行相應的優化,很有可能無法運行在移動平臺等對性能限制較高的平臺上。我們需要理解一些關鍵的因素來優化我們的Shader,以提高遊戲性能而又能盡可能保持取得同樣的視覺效果。

尤其是如果你的目標平臺包括Android系統,那麽就一定要小心中國各種山寨機的大浪一下把你拍在沙灘上的後果。。。所以,如果你從來沒有為你的Shader考慮過這些情況,那麽,且用且小心吧。。。

這一章中,我們會學習三節內容:什麽是一個高效的Shader,怎樣對Shader進行性能分析,為移動平臺優化我們的Shader。

那麽,什麽是一個高效的Shader呢?這是個有點復雜的問題,它涉及到了很多因素。例如,和你使用的變量個數及其所占內存,Shader使用的紋理個數有關等等。還有可能,你的Shade雖然工作良好,但我們實際商可以使用一半數目的變量就可以取得相同的效果。我們將在本節中發掘這樣的一些技巧,並向你說明它們是如何組合起來讓我們的Shader更快更高效的,而又可以各種平臺上取得同樣高質量的視覺效果。

準備工作

我們將首先使用一個最常見的Shader之一:Bumped Diffuse Shader。也就是應用了法線貼圖的Shader。

  1. 創建一個新的場景和一個球體,添加一個平行光。
  2. 創建一個新的Shader和Material,可以命名為OptimizedShader001。
  3. 把Shader賦給Material,把Material賦給球體。
  4. 最後,使用下列代碼修改Shader。

[plain] view plain copy print?
  1. Shader "Custom/OptimizedShader001" {
  2. Properties {
  3. _MainTex ("Base (RGB)", 2D) = "white" {}
  4. _NormalMap ("Normal Map", 2D) = "bump" {}
  5. }
  6. SubShader {
  7. Tags { "RenderType"="Opaque" }
  8. LOD 200
  9. CGPROGRAM
  10. #pragma surface surf SimpleLambert
  11. sampler2D _MainTex;
  12. sampler2D _NormalMap;
  13. struct Input {
  14. float2 uv_MainTex;
  15. float2 uv_NormalMap;
  16. };
  17. inline float4 LightingSimpleLambert (SurfaceOutput s, float3 lightDir, float atten)
  18. {
  19. float diff = max (0, dot (s.Normal, lightDir));
  20. float4 c;
  21. c.rgb = s.Albedo * _LightColor0.rgb * (diff * atten * 2);
  22. c.a = s.Alpha;
  23. return c;
  24. }
  25. void surf (Input IN, inout SurfaceOutput o)
  26. {
  27. float4 c = tex2D (_MainTex, IN.uv_MainTex);
  28. o.Albedo = c.rgb;
  29. o.Alpha = c.a;
  30. o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap));
  31. }
  32. ENDCG
  33. }
  34. FallBack "Diffuse"
  35. }


簡單的光照函數裏面進行了簡單的漫反射處理,surf函數裏則改變了模型的法線。

最後,你得到的效果大概是這樣的:

技術分享

實現

下面,我們來一步步優化這個Shader。

首先,我們需要優化變量類型,以便它們盡可能少地占用內存:

  1. 修改Input結構。之前,我們的UV坐標都是存儲在了float2類型的變量中,現在我們將它們改為half2
    [plain] view plain copy print?
    1. struct Input {
    2. half2 uv_MainTex;
    3. half2 uv_NormalMap;
    4. };
  2. 接下來是光照函數。同樣,將其中float家族的變量改成對應的fixed類型變量:
    [plain] view plain copy print?
    1. inline fixed4 LightingSimpleLambert (SurfaceOutput s, fixed3 lightDir, fixed atten)
    2. {
    3. fixed diff = max (0, dot (s.Normal, lightDir));
    4. fixed4 c;
    5. c.rgb = s.Albedo * _LightColor0.rgb * (diff * atten * 2);
    6. c.a = s.Alpha;
    7. return c;
    8. }
  3. 最後,修改surf函數中的變量類型。同樣使用fixed類型變量:
    [plain] view plain copy print?
    1. void surf (Input IN, inout SurfaceOutput o)
    2. {
    3. fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
    4. o.Albedo = c.rgb;
    5. o.Alpha = c.a;
    6. o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap));
    7. }
在修改了變量類型後,我們現在來利用Unity內置的光照函數變量,以便我們可以控制Shader是如何處理光源的。為此,我們可以很大程度上減少Shader處理的光源個數。修改#pragma聲明:

[plain] view plain copy print?
  1. CGPROGRAM
  2. #pragma surface surf SimpleLambert noforwardadd


現在,我們可以使用共享UV坐標來繼續優化Shader。為此,我們使用_MainTex的UV坐標代替_NormalMap的UV在UnpackNormal()中的查找作用,並移除Input結構中的uv_NormalMap:

[plain] view plain copy print?
  1. o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_MainTex));

[plain] view plain copy print?
  1. struct Input {
  2. half2 uv_MainTex;
  3. };


最後,我們告訴Unity,這個Shader只工作在特定的渲染器上

[plain] view plain copy print?
  1. CGPROGRAM
  2. #pragma surface surf SimpleLambert exclude_path:prepass noforwardadd


最後優化前後效果如下(左前右後):

技術分享 技術分享

可以看出,我們肉眼幾乎看不出任何差別,但是我們已經減少了這個Shader被繪制到屏幕上所花費的時間。我們將在下一節中利用Unity的可視化工具來分析這種減少程度的大小。但在這裏,我們關註的是,使用了更少的數據來得到相同的渲染效果。在創建我們自己的Shader的時候,也要一直記住這個思想!

解釋

上面一共提到了4種優化方式:優化變量類型,共享UV坐標,減少處理的光源個數,讓Shader只工作在特定的渲染器上。下面,我們來更深入地理解這些技術是如何工作的,最後再學習其他一些技巧。

優化變量類型

首先,我們來看一下在我們聲明變量時每個變量存儲的數據大小。由於在聲明變量時,我們往往有多個選擇(float,half,fixed),我們需要來看一下這些類型的特點:

  • float:高精度浮點值,通常是32位,也是三者中最慢的一個。它對應的還有float2,float3和float4。

  • half:中精度浮點值。通常是16位,範圍是-60000至+60000,它適合存儲UV坐標,顏色值等,比float類型快很多。它對應的還有half2,half3,和half4。

  • fixed:低精度浮點值。通常是11位,範圍是-2.0至+2.0,精度為1/256。這是三者中最小的一個,可以用於光照計算顏色等。它對應的值有fixed2,fixed3和fixed4。
官網對類型的選擇,給出了下面的建議:
  • 盡可能使用低精度變量。
  • 對於顏色值和單位長度的向量,使用fixed。
  • 對於其他類型,如果範圍和精度合適的話,使用half;其他情況使用float。

減少處理的光源個數

從上可以看出,這一步優化是通過在#pragma語句中聲明noforwardadd值來實現的。這主要是告訴Unity,使用這種Shader的對象,只接受一個單一的平行光光源作為逐像素光源,其他的光源都使用內置的球諧函數處理後作為逐頂點的光源。當我們在場景中放置了另一個光源時,這種策略會很明顯,因為我們的Shader使用一個法線貼圖進行逐像素的操作。

這樣做當然很好,但是如果我們需要不止一個平行光,而且想要控制哪一個是用於該逐像素計算的主光源,又該怎麽辦呢?這就需要Unity面板中的一個設置啦!如果你仔細觀察,就會法線每一個光源都有一個Render Mode下拉菜單。當你點擊它時,會出現Auto, Important, 和Not Important三種選項。通過選擇Important,你可以告訴Unity這個光源更需要被當成一個逐像素光源,而非一個逐頂點光源。如果設置為Auto,那麽就由Unity自己做決定啦!

懵了是不是。。。為了說明上述意思,我們來做個試驗!在場景裏放置另一個點光源,然後移除Shader中的Main Texture。第一次,打開平行光,關閉點光源(左圖);第二次關閉平行光,打開點光源(右圖)。你可以發現第二個點光源並不會影響我們的法線貼圖(只是照亮了模型,也就是它只是逐頂點處理),只有第一個平行光才會影響。

技術分享 技術分享

這裏的優化,是由於我們把其他所有光源當成了頂點光源,而在計算像素顏色時只計算一個主平行光作為像素光源。

共享UV坐標

這步優化很簡單,僅僅使用了Main Texture的UV坐標來代替法線貼圖的UV坐標,這樣實際上減少了內部提取法線貼圖UV坐標的代碼。這種方法可以很好地簡化我們的代碼。

只工作在特定渲染器上

最後,我們在語句中聲明了,以便告訴Unity,這個Shader不會再接受來自延遲渲染中的其他任何自定義的光照。這意味著,我們僅可以在正向渲染(forward render)中有效地使用這個Shader,這是在主攝像機的設置中設置的。

幫助鏈接:正向渲染,延遲渲染。

寫在最後

其他的優化策略還有很多。我們之前學過如何把多個灰度圖打包到一個RGBA貼圖中,以及如何使用一張貼圖來模擬光照效果。由於這些眾多的技術,因此問如何優化Shader是一個很模糊的問題。但是,了解這些技術使得我們可以根據不同的Shader和平臺采用合適的技術,來得到一個具有穩定幀率的Shader。

【Unity Shaders】Mobile Shader Adjustment—— 什麽是高效的Shader