UnityShaderVariant的一些探究心得


  最近遇到了一個問題,角色在Unity編輯器里運行渲染結果都是好的,打包到IOS上卻發現,角色身上渲染的很黑.花了些時間查了查,又試了試,把這方面算是初步弄清楚了。

  先說出現問題的原因,由於我們把shader打包進了AssetBundle中,並且在Shader中使用了shader_feature來定義了宏。

  為了完整起見,先從unity的shader  variant說起。


 ShaderVariant


   舉個例子,對於一個支持法線貼圖的Shader來說,用戶肯定希望無論是否為材質提供法線貼圖它的Shader都能正確的進行渲染處理。一般有兩種方法來保證這種需求:

  1.在底層shader(GLSL,HLSL等)定義一個由外部傳進來的變量(如int),有沒有提供法線貼圖由外部來判斷並給這個shader傳參,若是有則傳0,否則傳1,在Shader用if對這個變量進行判斷,然后在兩個分支中進行對應的處理。

  2.對底層shader封裝,如Unity的ShaderLab就是這種,然后在上層為用戶提供定義宏的功能,並決定宏在被定義和未被定義下如何處理。最終編譯時,根據上層的宏定義,根據不同的組合編譯出多套底層shader.

  上述兩種方法,各有利弊,對於前者由於引入了條件判斷,會影響最終shader在GPU上的執行效率。而后者則會導致生成的shader源碼(或二進制文件)變大。Unity中內置的Shader往往采取的是后者,所以這里只討論這種情況。   

  Unity的Shader中通過multi_compile和shader_feature來定義宏(keyword)。最終編譯的時候也是根據這些宏來編譯成多種組合形式的Shader源碼。其中每一種組合就是這個Uniy Shader的一個Variant。


 MaterialShaderVariant的關系


   一個Material同一時刻只能對應它所使用的Shader的一個variant。進行切換的要使用Material.EnableKeyword()和Material.DisableKeyword()來開關對應的宏,然后Unity會根據你設定的組合來匹配響應的shader variant進行渲染。如果你是在編輯器非運行模式下進行的修改那么這些keyword的設置會被保存到材質的.mat文件中,嘗試用NotePad++打開.mat文件,你應該會看到類似於下面的一段內容(需要在編輯器設置里把AssetSerializationMode設置為Force Text):

 1 %YAML 1.1
 2 
 3 %TAG !u! tag:unity3d.com,2011:
 4 
 5 --- !u!21 &2100000
 6 
 7 Material:
 8 
 9   serializedVersion: 6
10 
11   m_ObjectHideFlags: 0
12 
13   m_PrefabParentObject: {fileID: 0}
14 
15   m_PrefabInternal: {fileID: 0}
16 
17   m_Name: New Material
18 
19   m_Shader: {fileID: 4800000, guid: 3e0be7fac8c0b7c4599935fa92c842a4, type: 3}
20 
21   m_ShaderKeywords: _B
22 
23   m_LightmapFlags: 1
24 
25   m_CustomRenderQueue: -1
26 
27

  其中的m_ShaderKeywords就保存了這個材質球使用了哪些宏(keyword).

  如果你手頭有built-in Shader的源碼可以打開里面的StandardShaderGUI.cs看一下Unity自己事怎么處理對於StandardShader的keyword設置的。

  另外Shader.EnableKeyword,和Shader.DisableKeyword是對Shader進行全局宏設置的,這里不提了。


 multi_compileshader_feature的區別


   完全沒接觸過它們的同學可以先看官方文檔的介紹,multi_compile是一直都有的,shader_feature是后來的unity版本中加入的關鍵字。

舉例介紹一下multi_compile和shader_feature:

1.如果你在shader中添加了

1 #pragma multi_compile  _A _B
2 #pragma multi_compile  _C _D

  那么無論這些宏是否真的被用到,你的shader都會被Unity編譯成四個variant,分別包含了_A _C,_A _D, _B _C,_B _D四種keyword組合的代碼

2.如果是

1 #pragma shader_feature _A _B
2 #pragma shader_feature _C _D

  那么你的shader只會保留生成被用到的keyword組合的variant,至於如何判定哪些組合被用到了,等后面提到Assetbundle時候再說。


 ShaderVariantAssetbundle的關系


  我所遇到的問題正是和Assetbundle(簡稱AB)有關,原因是打成AB包之后shader_feature所定義的宏沒有被正確包含進去。

  上面說了multi_compile定義的keyword是一定能正確的生成對應的多種組合的shaderVariant,但shader_feature不盡然,Unity引入shader_feature就是為了避免multi_compile那種完整編譯所導致組合爆炸,很多根本不會被使用的shader_variant也會被生成。Unity在處理shader_feature時會判斷相應的keyword組合是否被使用。需要區分一下幾種情況:

  1.如果shader沒有與使用它的材質打在一個AB中,那么shader_feature的所有宏相關的代碼都不會被包含進AB包中(有一種例外,就是當shader_feature _A這種形式的時候是可以的),這個shader最終被程序從AB包中拿出來使用也會是錯誤的(粉紅色).

  2.把shader和使用它的材質放到一個AB包中,但是材質中沒有保存任何的keyword信息(你在編輯器中也是這種情況),shader_feature會默認的把第一個keyword也就是上面的_A和_C(即每個shader_feature的第一個)作為你的選擇。而不會把_A _D,_B _C,_B _D這三種組合的代碼編譯到AB包中。

  3.把shader和使用它的材質放到一個AB包中,並且材質保存了keyword信息(_A _C)為例,那么這個AB包就只包含_A _C的shaderVariant.

  可以看到shader_feature所定義的keyword產生的ShaderVariant並不是全部被打包到AB中,特別是你想在游戲運行時動態的通過EnableKeyWorld函數來進行動態修改材質使用的shaderVariant,如果一開始就沒有把對於variant放進AB包,自然也就找不到。


 ShaderVariantCollection


  要正確的讓各種variant正確的在游戲運行時正確處理,

最直接暴力的兩種方法:

1.把Shader放到在ProjectSetting->Graphics->Always Include Shaders列表里,Unity就會編譯所有的組合變種。

2.把Shader放到Resources文件夾下,也會正確處理,我猜也應該是全部keyword組合都編譯,有知道的同學,麻煩留言告訴我。

  但是這兩種情況最大的問題就是組合爆炸的問題,如果keyword比較少還好,要是多了那真是不得了,比如你把standardShader放進去,由於它有大量的keyword,全部變種都生成的話大概有幾百兆。另外一個問題就是這種辦法沒法熱更新。自然不如放到AB包里的好控制。

  放到AB包就又涉及到shader_feature的處理,為了在運行時動態切換材質的shadervariant,可以在工程里新建一堆材質,然后把每個材質設置成一種想要的keyword組合,把他們和shader放到一起打到一個AB中去,這樣雖然能讓shadervariant正確生成,但是這些Material是完全多余的。

  為了解決這種問題,Unity5.0以后引入了ShaderVariantCollection(下面簡稱SVC),這里不講用法,只說問題,這個SVC文件可以讓我指定某個shader要編譯都要編譯帶有哪些keyword的變種。並且在ProjectSetting->Graphics界面新加了一個Preloaded Shaders列表,可以讓你把SVC文件放進去,編譯時指定的Shader就會按照SVC中的設置進行正確的variant生成,而不會像Always Include Shaders列表中的那樣全部變種都生成。

  但是它在AB中的表現可就不盡如人意了,要讓它起作用,就必須把它和對應的shader放在一個AB中,而且除了5.6以外版本,我試了幾個都不能正確使用,不是一個variant都沒生成,就是只生成一個shadervariant(和放一個沒有設置keyword的材質效果一樣).你可以自己用UnityStudio打開查看一下生成的AB內容。


寫在最后


  應該正確的理解Unity提供multi_compile和shader_feature以及ShaderVariantCollection的意圖,根據自己的情況來選擇合理的解決方案。

  在查這個問題的過程中也google了一些,發現國外在這方面的討論遠沒國內多,應該是因為老外很少使用熱更新這種東西,也自然很少用AB。

  

  尊重他人智慧成果,若要轉載,請注明作者esfog,原文地址http://www.cnblogs.com/Esfog/p/Shader_Variant.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM