UnityShaderVariant的一些探究心得
最近遇到了一個問題,角色在Unity編輯器裏運行渲染結果都是好的,打包到IOS上卻發現,角色身上渲染的很黑.花了些時間查了查,又試了試,把這方面算是初步弄清楚了。
先說出現問題的原因,由於我們把shader打包進了AssetBundle中,並且在Shader中使用了shader_feature來定義了宏。
為了完整起見,先從unity的shader variant說起。
ShaderVariant
舉個例子,對於一個支持法線貼圖的Shader來說,用戶肯定希望無論是否為Shader提供法線貼圖這個Shader都能正確的進行渲染處理。一般有兩種方法來保證這種需求:
1.在底層shader(GLSL,HLSL等)定義一個由外部傳進來的變量(如int),有沒有提供法線貼圖由外部來判斷並給這個shader傳參,若是有則傳0,否則傳1,在Shader用if對這個變量進行判斷,然後在兩個分支中進行對應的處理。
2.對底層shader封裝,如Unity的ShaderLab就是這種,然後在上層為用戶提供定義宏的功能,並決定宏在被定義和未被定義下如何處理。最終編譯時,根據上層的宏定義,根據不同的組合編譯出多套底層shader.
上述兩種方法,各有利弊,對於前者由於引入了條件判斷,會影響最終shader在GPU上的執行效率。而後者則會導致生成的shader源碼(或二進制文件)變大。Unity采取的是後者,我們這裏也只討論Unity對後者的使用。
Unity的Shader中通過multi_compile和shader_feature來定義宏(keyword)。最終編譯的時候也是根據這些宏來編譯成多種組合形式的Shader源碼。其中每一種組合就是這個Uniy Shader的一個Variant。
Material與ShaderVariant的關系
一個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_compile和shader_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時候會說。
ShaderVariant與Assetbundle的關系
我所遇到的問題正是和Assetbundle(簡稱AB)有關,原因是打成AB包之後shader_feature所定義的宏沒有被包含進去。
上面說了multi_compile定義的keyword是一定能正確的生成對應的多種組合的shaderVariant,但shader_feature不盡然,Unity引入shader_feature就是為了避免multi_compile那種完整編譯所導致的冗余的沒有被使用的shader_variant被生成。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
UnityShaderVariant的一些探究心得