Unity-著色器變體和關(guān)鍵字
可以編寫著色器代碼片段來共享通用代碼,但在啟用或禁用給定關(guān)鍵字時具有不同功能。Unity 編譯這些著色器代碼片段時,它將為已啟用和已禁用關(guān)鍵字的不同組合創(chuàng)建單獨(dú)的著色器程序。這些各個著色器程序被稱為著色器變體。
由于項目工作流程的原因,著色器變體可能會很有用;可以將同一著色器分配給不同材質(zhì),但要為每種材質(zhì)配置不同關(guān)鍵字。這意味著可以在同一個地方編寫和維護(hù)著色器代碼,并減少項目中的著色器資源。還可以使用著色器變體,通過啟用或禁用關(guān)鍵字在運(yùn)行時更改著色器行為。
具有大量變體的著色器被稱為“大型著色器”或“超級著色器”。Unity 的標(biāo)準(zhǔn)著色器就是此類著色器的一個示例。
使用著色器變體和關(guān)鍵字
創(chuàng)建著色器變體
要創(chuàng)建著色器變體,請將以下指令之一添加到著色器代碼片段:
#pragma multi_compile
#pragma multi_compile_local
#pragma shader_feature
#pragma shader_feature_local
可以將這些指令用于常規(guī)著色器(包括表面著色器)和計算著色器。
然后,Unity 使用不同的預(yù)處理器指令來多次編譯同一著色器代碼。
啟用和禁用著色器關(guān)鍵字
要啟用和禁用著色器關(guān)鍵字,請使用以下 API:
Shader.EnableKeyword:啟用全局關(guān)鍵字
Shader.DisableKeyword:禁用全局關(guān)鍵字
CommandBuffer.EnableShaderKeyword:使用?
CommandBuffer
?來啟用全局關(guān)鍵字CommandBuffer.DisableShaderKeyword:使用?
CommandBuffer
?來禁用全局關(guān)鍵字Material.EnableKeyword:為常規(guī)著色器啟用本地關(guān)鍵字
Material.DisableKeyword:為常規(guī)著色器禁用本地關(guān)鍵字
ComputeShader.EnableKeyword:為計算著色器啟用本地關(guān)鍵字
ComputeShader.DisableKeyword:為計算著色器禁用本地關(guān)鍵字
啟用或禁用關(guān)鍵字時,Unity 會使用相應(yīng)變體。
multi_compile 的工作方式
指令示例:
此指令示例生成兩個著色器變體:一個定義了?FANCY_STUFF_OFF
,另一個定義了?FANCY_STUFF_ON
。在運(yùn)行時,Unity 根據(jù)材質(zhì)或全局著色器關(guān)鍵字來激活其中一個變體。如果這兩個關(guān)鍵字均未啟用,則 Unity 使用第一個關(guān)鍵字(在此示例中為?FANCY_STUFF_OFF
)。
可以在 multi_compile 行中添加兩個以上的關(guān)鍵字。例如:
此指令示例生成四個著色器變體:SIMPLE_SHADING
、BETTER_SHADING
、GOOD_SHADING
?和?BEST_SHADING
。
為了生成未定義預(yù)處理器宏的著色器變體,請?zhí)砑右粋€只有下劃線 (__
) 的名稱。這是避免用完兩個關(guān)鍵字的常用方法,因為在一個項目中可以使用的關(guān)鍵字?jǐn)?shù)量有限(請參閱后面的關(guān)鍵字限制部分)。例如:
此指令生成兩個著色器變體:一個未定義任何關(guān)鍵字 (__
),另一個定義了?FOO_ON
。
shader_feature 與 multi_compile 之間的區(qū)別
shader_feature
?與?multi_compile
?非常相似。唯一的區(qū)別是 Unity 沒有將?shader_feature
?著色器的未用變體包含在最終構(gòu)建中。因此,應(yīng)該將?shader_feature
?用于材質(zhì)中設(shè)置的關(guān)鍵字,而?multi_compile
?更適合通過代碼來全局設(shè)置的關(guān)鍵字。
此外,有一個只包含一個關(guān)鍵字的速記符號:
這只是?#pragma shader_feature _ FANCY_STUFF
?的快捷方式。它會擴(kuò)展為兩個著色器變體(第一個沒有定義;第二個有定義)。
合并多個 multi_compile 行
如果提供?multi_compile
?行,Unity 將會針對所有可能的行組合來編譯生成的著色器。例如:
這會為第一行生成三個變體,為第二行生成兩個變體??偣采闪鶄€著色器變體(A+D、B+D、C+D、A+E、B+E、C+E)。
每個?multi_compile
?行可以視為用于控制單個著色器“功能”。請記住,著色器變體的總數(shù)會以這種方式急速增長。例如,十個各有兩個選項的?multi_compile
?功能總共生成 1024 個著色器變體。
關(guān)鍵字限制
使用著色器變體時,Unity 中的關(guān)鍵字?jǐn)?shù)量上限是 256,Unity 將大約 60 個關(guān)鍵字保留供內(nèi)部使用(因此降低了可用上限)。關(guān)鍵字會在整個 Unity 項目中全局啟用,因此在多個不同著色器中定義多個關(guān)鍵字時,請注意不要超過限制。
本地關(guān)鍵字
shader_feature?和?multi_compile?的主要缺點(diǎn)是其中定義的所有關(guān)鍵字均會影響 Unity 的全局關(guān)鍵字計數(shù)上限(256 個全局關(guān)鍵字,外加 64 個本地關(guān)鍵字)。為了避免此問題,可以使用不同的著色器變體指令:__shader_feature_local__ 和?multi_compile_local。
shader_feature_local:__類似于?shader_feature__,但是枚舉的關(guān)鍵字為本地關(guān)鍵字。
multi_compile_local:__類似于?multi_compile__,但是枚舉的關(guān)鍵字為本地關(guān)鍵字。
本地指令將已定義的關(guān)鍵字保留在特定于該著色器的這些指令之下,而不是將這些關(guān)鍵字應(yīng)用于整個項目。因此,應(yīng)該使用本地關(guān)鍵字而不是全局關(guān)鍵字,除非計劃通過全局 API 來啟用這些特定關(guān)鍵字。
開始使用本地關(guān)鍵字時,您可能會發(fā)現(xiàn)性能有變化,但是此差異取決于項目的設(shè)置方式。每個著色器的本地和全局關(guān)鍵字總數(shù)會影響性能:在理想設(shè)置中,多用本地關(guān)鍵字和少用全局關(guān)鍵字可以減少每個著色器的關(guān)鍵字總數(shù)。
如果全局關(guān)鍵字和本地關(guān)鍵字同名,Unity 會優(yōu)先考慮本地關(guān)鍵字。
限制
不能將本地關(guān)鍵字與進(jìn)行全局關(guān)鍵字更改的 API 一起使用(例如 Shader.EnableKeyword 或 CommandBuffer.EnableShaderKeyword)。
每個著色器最多有 64 個唯一性的本地關(guān)鍵字。
如果材質(zhì)啟用了本地關(guān)鍵字,并且其著色器變?yōu)椴辉俾暶鞯闹鳎琔nity 將創(chuàng)建新的全局關(guān)鍵字。
示例
此指令生成兩個著色器變體:一個未定義任何關(guān)鍵字 (__
),另一個定義了?FOO_ON
(本地關(guān)鍵字)。
啟用本地關(guān)鍵字的過程與啟用全局關(guān)鍵字的過程相同:
內(nèi)置 multi_compile 快捷方式
有幾個“快捷方式”符號用于編譯多個著色器變體。這些變體主要處理 Unity 中的不同光源、陰影和光照貼圖類型。請參閱有關(guān)渲染管線的文檔以了解詳細(xì)信息。
multi_compile_fwdbase
?編譯?PassType.ForwardBase?所需的所有變體。這些變體處理不同的光照貼圖類型以及啟用或禁用的方向光主要陰影。multi_compile_fwdadd
?編譯?PassType.ForwardAdd?的變體。這將編譯變體來處理方向光、聚光燈或點(diǎn)光源類型,以及它們帶有剪影紋理的變體。multi_compile_fwdadd_fullshadows
?- 與?multi_compile_fwdadd
?相同,但還能夠讓光源具有實(shí)時陰影。multi_compile_fog
?擴(kuò)展為多個變體以處理不同的霧效類型 (off/linear/exp/exp2)。
大多數(shù)內(nèi)置快捷方式會產(chǎn)生許多著色器變體。如果知道項目不需要這些變體,可以使用?#pragma skip_variants
?來跳過對其中一些變體的編譯。例如:
該指令會跳過包含?POINT
?或?POINT_COOKIE
?的所有變體。
圖形層和著色器變體
在運(yùn)行時,Unity 會檢查 GPU 的功能并確定其對應(yīng)的圖形層。在內(nèi)置渲染管線中,可以為每個圖形層自動創(chuàng)建一組著色器變體;為此,請使用?#pragma hardware_tier_variants
?指令。
該功能僅與內(nèi)置渲染管線兼容。它不兼容通用渲染管線 (URP)、高清渲染管線 (HDRP) 或自定義的可編程渲染管線。
要啟用此功能,請?zhí)砑?#pragma hardware_tier_variants renderer
,其中?renderer
?是有效的渲染平臺,如下所示:
除了其他所有關(guān)鍵字,Unity 還為每個著色器生成三個著色器變體。每個生成的變體都有以下定義命令之一,它們對應(yīng)于?GraphicsTier?枚舉的相同編號值:
UNITY_HARDWARE_TIER1
UNITY_HARDWARE_TIER2
UNITY_HARDWARE_TIER3
您可以使用它們?yōu)楦投嘶蚋叨擞布帉憲l件性回退或額外功能。
Unity 首次加載應(yīng)用程序時,它會檢測到?GraphicsTier
?并將結(jié)果存儲在?Graphics.activeTier?中。要覆蓋?Graphics.activeTier
?的值,請直接設(shè)置該值。請注意,必須在 Unity 加載您要更改的任何著色器之前執(zhí)行此操作。一個非常適合設(shè)置此值的位置是在加載主場景之前的預(yù)加載場景中。
為了幫助盡可能降低這些變體的影響,Unity 在播放器中只加載一組著色器。相同的著色器(例如,如果您只為?TIER1
?編寫了專用版本而其他所有版本都相同)將不占用磁盤上的任何額外空間。
要在 Unity Editor 中測試圖形層,請導(dǎo)航至?Edit > Graphics tier,然后選擇您希望 Unity Editor 使用的層。
請注意,圖形層與質(zhì)量設(shè)置無關(guān)。它們是此設(shè)置的補(bǔ)充。
每平臺著色器定義設(shè)置和圖形層變體
在內(nèi)置渲染管線中,可以使用?EditorGraphicsSettings.SetShaderSettingsForPlatform?API 針對給定的?BuildTarget?和?GraphicsTier?來覆蓋 Unity 的內(nèi)部 #define。
該功能僅與內(nèi)置渲染管線兼容。它不兼容通用渲染管線 (URP)、高清渲染管線 (HDRP) 或自定義的可編程渲染管線。
請注意,如果您為給定的?BuildTarget
?的不同?GraphicsTier
?提供不同的?TierSettings
?值,即使您未向著色器代碼添加?#pragma hardware_tier_variants
,Unity 也會為著色器生成層變體。