一.定義著色器變體
定義一個著色器變體(Shader Variant)從概念和實現上講,主要包括以下幾個核心部分
1.使用預編譯指令來聲明變體關鍵字
關鍵字是驅動變體生成的“開關”。它們是簡單的字符串標識符,用于在 Shader 代碼中標記不同的功能路徑。
以multi_compile類型為例在ShaderLab中聲明自定義變體關鍵字:
#pragma?multi_compile _ _CustomKeyWord
這里用到了關鍵字的組合寫法(下劃線+自定義關鍵字名),默認會使用第一個變體(這里的?_代表的是 默認狀態或無關鍵字激活的狀態 )
(*)使用下劃線組合寫法的好處
清晰性:它明確指出了當該組功能都未開啟時的 Shader 行為。這使得 Shader 代碼更易讀、更易于理解。
智能裁剪:對于 shader_feature,Unity 在構建游戲時會執行 Shader Stripping(著色器裁剪)。如果你的項目中,沒有任何一個材質勾選了“開啟功能 A”,那么與 MY_FEATURE_ON
對應的變體就不會被編譯和打包。但由于有 _
的存在,默認的、不帶該功能的變體始終會被包含(只要這個 Shader 被使用了)。
優化包體:這意味著你不需要為每個可選功能都強制打包“開啟”和“關閉”兩個版本。如果“開啟”版本根本沒用,它就不會被打包,從而節省了存儲空間。
注意:Unity有變體數量的限制
(*)使用multicompile時可以不明確使用 _
隱含的默認狀態:當 multi_compile 列表中沒有 _
且沒有其他關鍵字被激活時,Unity 仍然會生成一個變體,這個變體是 沒有任何該 multi_compile 行中關鍵字定義的。這其實就等同于“默認”或“無宏定義”的狀態。但如果使用了 _ ,那么監視器面板里默認顯示為未激活狀態。
總結:使用下劃線可以節省變體數量
(*)multi_compile與shader_feature的區別
1.概述
#pragma multi_compile指令會強制編譯所有可能的關鍵字組合對應的 Shader 變體,無論這些變體是否在你的項目中被實際使用。
#pragma shader_feature指令會按需編譯 Shader 變體,它只會編譯和包含那些在你的項目中被 材質實際使用 的關鍵字組合對應的變體。
2.區別
功能獨立:它們各自執行的任務是獨立的。shader_feature 的核心是裁剪(stripping),而 multi_compile 的核心是強制編譯所有變體。一個不能替代另一個的功能。
目的不同:
shader_feature 主要服務于優化構建包體大小和編譯時間,針對的是那些通過材質屬性控制的“可選”功能。
multi_compile 主要服務于保證運行時功能的完整性,針對的是那些引擎內部控制的“必需”功能。
2.靜態分支代碼塊
在 Shader 源代碼內部,需要使用 條件編譯宏(#ifdef / #ifndef / #else
) 來包裹那些受關鍵字控制的代碼邏輯。這些宏在編譯時根據關鍵字的激活狀態,決定哪些代碼會被包含在最終的變體中,哪些會被剔除.
一般在頂點或片元著色器中常使用如下格式分別書寫變體分支的邏輯:
#ifdef _CustomKeyword_ONxxxxxxxxxxx;#elsexxxxxxxxxxx;#endif
3.關鍵字與材質屬性的關聯(可選,但常用)
為了方便美術師和設計師在 Unity 編輯器中控制這些變體,通常需要將 Shader 關鍵字與材質的 Properties
塊中的屬性 關聯起來。這通過使用像 [Toggle]
、[KeywordEnum]
這樣的屬性修飾符來實現。
變體開關的實現
1.ShaderLab屬性塊
可以發現在Property中命名相同的屬性會指向ShaderLab中的同一變體關鍵字。
注意命名的匹配規則
SubShader中的關鍵字命名:(全大寫)自定義關鍵字名"+"?_ON"
#pragma shader_feature _CustomKeyword_ON
Proprty中聲明的屬性名命名:(大小寫可混搭,但要與SubSahder中關鍵字的字符組成保持一致)自定義關鍵字名
[Toggle]_CustomKeyword_ON ("Toggle MyOn", Float) = 1
另外,如果你使用不同形式(如[Toggle]和[MaterialToggle])定義了屬性名完全相同的變量開關,在監視器面板中,它們將同時切換狀態。
2.C#代碼
1.單個材質實例
(1)啟用關鍵字:Material.EnableKeyword(string keyword)
作用: 激活此 Material
實例上 Shader 的特定關鍵字
(2)禁用關鍵字:Material.DisableKeyword(string keyword)
作用: 禁用此 Material
實例上 Shader 的特定關鍵字。
(3)檢查關鍵字狀態:Material.IsKeywordEnabled(string keyword)
作用: 檢查此 Material
實例上 Shader 的某個關鍵字是否處于激活狀態。
2.全局材質
(1)啟用關鍵字:Shader.EnableKeyword(string keyword)
作用: 激活一個全局著色器關鍵字。一旦激活,所有使用該關鍵字的 Shader 都會切換到對應的變體(如果該變體存在)。
(2)禁用關鍵字:Shader.DisableKeyword(string keyword)
作用: 禁用一個全局著色器關鍵字。
(3)檢查關鍵字狀態:Shader.IsKeywordEnabled(string keyword)
作用: 檢查一個全局著色器關鍵字當前是否處于激活狀態。
(4)重置所有全局關鍵字:Shader.DisableAllKeywords()
作用: 禁用所有當前激活的全局著色器關鍵字。這是一個比較少用的操作,因為它會影響所有 Shader 的行為。
二.著色器變體的限制
Unity中的著色器變體也存在著一些限制。
1.變體數量限制與關鍵字的關系
Unity 的變體數量限制主要體現在 關鍵字數量的限制 上,而不是直接的“變體”數量限制。這是因為每一個變體都是由一套激活的關鍵字組合來定義的。
(1)全局關鍵字限制:
Unity 對項目中所有著色器使用的全局關鍵字總數有一個限制。這個限制是 256 個。Unity 引擎自身也會使用大約 60 多個內部關鍵字(如用于光照、陰影、霧效等),這會進一步減少你可以使用的自定義關鍵字數量。無論是 multi_compile
還是 shader_feature
定義的關鍵字,只要它們是全局的(即沒有使用 _local
后綴),都會計入這 256 個全局關鍵字的限制。
(2)局部關鍵字限制(Local Keywords):
從 Unity 2019.1 版本開始,引入了 局部關鍵字 的概念,通過 #pragma shader_feature_local
和 #pragma multi_compile_local
來聲明。每個獨立的 Shader 文件可以有最多 64 個獨特的局部關鍵字。
局部關鍵字不計入全局關鍵字的 256 個限制。這是解決全局關鍵字限制的一種重要方式,尤其適用于那些只在該特定 Shader 內部使用的功能。注意:局部關鍵字不能與 Shader.EnableKeyword
或 CommandBuffer.EnableShaderKeyword
等全局關鍵字 API 一起使用。
2.變體爆炸 (Shader Variant Explosion)
雖然有關鍵字數量的限制,但更直接的問題是 “變體爆炸”。即使你沒有達到關鍵字數量的硬性限制,但如果你使用了過多的 multi_compile 指令,或者在shader_feature中定義了太多未優化的組合,仍然會導致天文數字的變體數量:
變體數量是關鍵字組合的乘積。 例如,如果你有 10 個multi_compile?指令,每個指令定義了兩個關鍵字,那么總的變體數量可能是 210=1024 個。如果其中一些是三個或更多關鍵字的組合,這個數字會呈指數級增長。
本篇完