unity ShaderVariantCollection與shader變體

原文鏈接:https://blog.csdn.net/RandomXM/article/details/88642534

 

基礎知識介紹
什麼是ShaderVariant
在寫shader時,往往會在shader中定義多個宏,並在shader代碼中控制開啓宏或關閉宏時物體的渲染過程。最終編譯的時候也是根據這些不同的宏來編譯生成多種組合形式的shader源碼。其中每一種組合就是這個shader的一個變體(Variant)。

Material ShaderKeywords與ShaderVariant
Material所包含的Shader Keywords表示啓用shader中對應的宏,Unity會調用當前宏組合所對應的變體來爲Material進行渲染。
在Editor下,可以通過將material的inspector調成Debug模式來查看當前material定義的Keywords,也可在此模式下直接定義Keywords,用空格分隔Keyword。

在程序中,可用Material.EnableKeyword()、Material.DisableKeyword()、Shader.EnableKeyword()、Shader.DisableKeyword()來啓用/禁用相應的宏。Enable函數應與Disable函數相對應。若一個宏由Material.EnableKeyword()開啓,則應由Material.DisableKeyword()關閉,Shader.DisableKeyword()無法關閉這個宏。Material中定義的Keywords由Material的函數進行設置。

multi_compile與shader_feature
multi_compile與shader_feature可在shader中定義宏。兩者區別如下圖所示:

multi_compile    shader_feature
定義方式    #pragma multi_compile A    #pragma shader_feature A
宏的適用範圍    大多數shader    一般僅針對shader自身
變體的生成    生成所有的變體    可自定義生成何種變體
默認定義的宏    默認定義首個宏    默認定義首個宏
定義方式
定義方式中值得注意的是,#pragma shader_feature A其實是 #pragma shader_feature _ A的簡寫,下劃線表示未定義宏(nokeyword)。因此此時shader其實對應了兩個變體,一個是nokeyword,一個是定義了宏A的。
而#pragma multi_compile A並不存在簡寫這一說,所以shader此時只對應A這個變體。若要表示未定義任何變體,則應寫爲 #pragma multi_compile __ A。

宏的適用範圍
multi_compile定義的宏,如#pragma multi_compile_fog,#pragma multi_compile_fwdbase等,基本上適用於大部分shader,與shader自身所帶的屬性無關。
shader_feature定義的宏多用於針對shader自身的屬性。比如shader中有_NormalMap這個屬性(Property),便可通過#pragma shader_feature _NormalMap來定義宏,用來實現這個shader在material有無_NormalMap時可進行不同的處理。

變體的生成
#pragma multi_compile A B C
#pragma multi_compile D E
則此時會生成 A D、A E、B D、B E、C D、C E這6中變體。
shader_feature要生成何種變體可用shader variant collection進行自定義設置。

默認定義的宏
當material中的keywords無法對應shader所生成的變體時,Unity便會默認定義宏定義語句中的首個宏,並運行相應的變體來爲這個material進行渲染。
multi_compile與shader_feature都默認定義首個宏,如下表所示:

宏定義語句    默認定義的宏
#pragma shader_feature A    nokeyword(存在簡寫問題)
#pragma shader_feature A B C    A
#pragma multi_compile A    A
#pragma multi _compile A B C    A
如何控制項目中Shader變體的生成
項目中shader的生成方式主要有三種,其優缺點如下表所示:

生成方式    優點    缺點
shader與material打在一個包中    變體根據material中的keywords自動生成    多個不同的material包中可能存在相同的shader變體,造成資源冗餘
若在程序運行時動態改變material的keyword其變體可能並沒有被生成
Shader單獨打包,使用multi_compile定義全部宏    全部變體都被生成,不會發生需要的變體未生成的情況    生成的變體數量龐大,嚴重浪費資源
Shader單獨打包,shader_feature與multi_compile結合使用    能夠有效控制變體數量    如何確定哪些變體需要生成
容易遺漏需要生成的變體
而我們希望的結果是在保證渲染效果正確的情況下,要儘可能的控制項目中shader的變體數量,避免產生冗餘資源。幸運的是,Unity已經爲我們準備好了解決方案:ShaderVariantCollection。

解決方案:ShaderVariantCollection
ShaderVariantCollection介紹
Shader Variant Collection是用來記錄shader中哪些變體是實際使用的。其優點主要有:在shader_feature與multi_compile結合使用時,能夠設置生成何種變體,從而避免生成不必要的變體;shader不必和material打在一個包中,避免了多個包中存在相同的變體資源;明確直觀的顯示了哪些變體是需要生成的。

在Unity中可以通過Create->Shader-> Shader Variant Collection,就可以新建一個shader variant collection文件,shader variant collection 的使用如下圖所示:

點擊增加變體後,會出現變體選擇窗口

配置好需要生成的變體後,將collection與shader打在同一個包中,便能準確生成面板中所配置的shader變體。

ShaderVariantCollection生成變體規則
除了在collection中配置的變體會被生成外,Unity還在後臺爲我們多生成了幾個變體,這幾個變體是“隱藏的”,並未在collection面板中顯示。

必定生成首個宏定義開啓所對應的變體。
Shader中通過#pragma shader_feature A定義了宏A,並在collection中加入了宏A所對應的變體,如下圖所示:

此時生成的變體除了collection中已經存在的ForwardBase A外,還會生成變體ForwardBase nokeyword。因爲只定義單個宏時,A 爲 _ A的簡寫。實際上首個被定義的宏爲nokeyword,故 nokeyword所對應的變體必定會被生成。
同理,以 #pragma shader_feature A B C來定義宏時,即使collection中未添加變體Forward A,這個變體也必定會被生成(當shader的PassType僅有ForwardBase)。

Shader中有多個Pass時變體的生成規則

a. 讀取ShaderVariantCollection中已存在的變體,獲取它們的Keywords。
b. 將這些Keywords分別與每個Pass的多組Keywords列表求交集,取交集中Keywords數量最多得那組。
c. 用得到的Keywords與對應的PassType生成ShaderVariant,並添加到ShaderVariantCollection中。
d. 若得到得交集中有新的Keywords,則回到b。

上述過程類似遞歸。例如:
Shader 中有 ForwardBase、ForwardAdd、Normal 三種PassType(以下爲了方便簡稱Base、Add、 Normal)。定義的宏如下:

Base    Add    Normal
#pragma shader_feature A
#pragma shader_feature B
#pragma shader_feature C    #pragma shader_feature A
#pragma shader_feature E    #pragma shader_feature A
#pragma shader_feature B
#pragma shader_feature E
此時若ShaderVariantCollection中包含的變體是 Base ABC,Add AE。則此時生成的變體爲:這三種PassType的默認定義的宏(nokeyword)所對應的變體(3個)以及原先直接包含的Base ABC、Add AE。除此之外Unity還會額外生成Add A、Base A、Normal A、Normal AB、 Base AB、Normal AE這6個變體。

ABC ∩ Add AE -> Add A (A is NewKeyword)
    A ∩ Base ABC -> Base A
    A ∩ Normal ABE -> Normal A
ABC ∩ Normal ABE -> Normal AB (AB is NewKeyword)
    AB ∩ Base ABC -> Base AB
AE ∩ Normal ABE -> Normal AE

變體的調用規則
當collection將變體準確生成後,便能在運行時通過修改material中的keywords來實現對不同變體的調用。
假設某collection生成的變體只有Forward ABC,Forward ABE,Forward nokeyword這三種,則此時調用關係如下:

Material中的Keywords    調用的變體    解釋
A B C    Forward A B C    正常匹配
A B    Forward nokeyword    沒有匹配的變體,調用默認被定義的宏 所對應的變體
A B C D    Forward A B C    調用交集中keyword數量多的變體
ABCD ∩ ABC = ABC
ABCD ∩ ABE = AB
A B C E    Forward A B C    交集中keyword數量相同,在collection中誰在前就調用誰
A B C E    Forward A B C    與在material中的定義順序無關

以上規則均爲根據測試總結歸納出來的規則,若有錯誤之處還請嚴加指正!

項目中對Shader Variant的管理
項目中變體的添加
那麼項目中是如何確定哪些變體是需要加到collection中的呢?我們的做法是:

遍歷每一個Material,提取其shader keywords。
將獲得的keywords與shader的每個PassType所包含的宏定義做交集,並將其結果添加到collection中。
舉個簡單的例子,Material中的Keywords爲A B C D,則shader的PassType、PassType中所定義的宏、需要往collection中添加的變體則如下表所示:

PassType    定義的宏    需要往collection中添加的變體
ForwardBase    #pragma shader_feature A
#pragma shader_feature B    Forward A B
((ABCD ∩ AB = AB))
ForwardAdd    #pragma shader_feature _ C D    Add C
(ABCD ∩ C = C,ABCD ∩ D = D,但C的定義在D前,故只添加C)
Normal    #pragma shader_feature _ E F    Normal NoKeyword
(ABCD ∩ E = NoKeyword)
(ABCD ∩ F = NoKeyword)
需要說明的是,我們自己的代碼裏爲了降低變體生成邏輯的複雜度、保持collection面板上變體的直觀性,不將Unity爲我們額外生成的那幾個變體添加到collection面板中,但要記得Unity是會爲我們生成額外的變體的。

Shader編寫規範
建議使用shader_feature時將定義語句寫成完整模式,並且不要在一個語句中定義多個宏。
完整模式:#pragma shader_feature _ A,不建議寫成#pragma shader_feature A。
不建議在一個語句中定義多個宏,如: #pragma shader_feature _ A B C,若一定要定義多個宏,請務必將其寫成完整模式,不使用完整模式在切換shader時可能會與想要的效果不一致,具體原因尚未測得。

若在shader中使用shader_feature,請爲這個shader指定一個CustomEditor
每個使用shader_feature來定義Keyword的shader都需要再末尾加個 CusomEditor “xxxx”,並在代碼中實現類xxxx(需繼承自UnityEditor.ShaderGUI),用來對Keywords定義進行設定。
這麼做是因爲Material中的部分Keyword是由shader中的屬性(Properties)所控制的。比如shader中含有_NormalMap的屬性並且定義了與_NormalMap相關的Keyword,這個Keyword需要在Material含有NormalMap時添加,不含NormalMap時移除。這個功能可由自定義的CustomEidtor實現。
具體如何寫這個CustomEditor類可參考Unity builtin_shaders\Editor\StandardShaderGUI.cs。該文件可去Unity官網下載,下載時選擇內置着色器即可。


如果需要在代碼中開關宏,請使用multi_compile來定義這個宏,以免變體丟失。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章