通常,保留大部分的着色代碼是很方便的,但也允許產生稍微不同的着色“變體”。這通常被稱為“mega shaders”或“uber shaders”,並通過為每個案例編譯不同的預處理程序指令來編譯shader代碼。
在Unity中,可以使用指令 #pragma multi_compile
或者 #pragma shader_feature 來編譯着色器程序片段。這個東西對表面着色器也起作用。
在運行時,通過材質球的關鍵字(Material.EnableKeyword and DisableKeyword) 或者全局着色器關鍵字(Shader.EnableKeyword and DisableKeyword)來選擇合適的 着色器變體.
多重編譯如何工作
編譯指令長這樣:
#pragma multi_compile FANCY_STUFF_OFF FANCY_STUFF_ON
這個將會產生兩個着色器變體。一個變體中定義了FANCY_STUFF_OFF,另一個定義了FANCY_STUFF_ON。在運行時,其中一個會被材質球或者全局着色器關鍵字激活。如果兩個關鍵字都是被禁止的,那么就會使用第一個。
當然,這里可以不止兩個關鍵字,如下面,可以有4個:
#pragma multi_compile SIMPLE_SHADING BETTER_SHADING GOOD_SHADING BEST_SHADING
當任意一個名字的字符全部是下划線,那么一個着色器變體也會生成,但是沒有任何預定義宏被包含在內。這通常用於着色器的特性,來避免用完兩個關鍵字。比如,西面的指令會產生兩個變體,第一個什么都沒定義,第二個定義了一個 交FOO_ON的宏。
#pragma multi_compile __ FOO_ON
shader_feature 與multi_compile的差異
這兩個指令很相似,唯一的差別是由 shader_feature定義出的,沒有被使用的變體不會被打包到游戲中。所以 shader_feature 適用於產生那些定義在材質球上的關鍵字控制的着色器變體,而multi_compile則更適合產生全局關鍵字控制的着色器變體。
除此以外,shader_feature有一個簡寫特性:
#pragma shader_feature FANCY_STUFF
上面這段代碼 等同於#pragma shader_feature _ FANCY_STUFF,它會產生兩個變體,一個啥都沒有定義,一個定義了一個FANCY_STUFF。
結合幾行multi_compile
使用好幾行multi_compile指令是可以的,然后shader就會被編譯成這幾行定義的所有可能組合個數的變體:
#pragma multi_compile A B C #pragma multi_compile D E
看上面這行代碼,那么第一行會產生三個變體,第二行產生兩個,然后總的還有6個(A+D, B+D, C+D, A+E, B+E, C+E)
最簡單的理解方式就是認為每一行multi_compile指令控制一個shader特性。要記住的是這樣的話shader的變體數量將增加的很快。比如,10行multi_compile指令,每一行都只有兩個特性,結果會產生1024個變體。
關鍵字限制
當使用shader變體時,記住在unity中關鍵字最多只有256個。而且在內部已經用了60個了。而且,在一個項目中,關鍵字都是全局使用的,所以記得不要超標了。
內置multi_compile縮寫
針對於編譯多個shader變體,這里有好些個縮寫符號。他們大多數情況下用來處理光照,陰影和
光線映射。
multi_compile_fwdbase
:編譯所有Pass類型為ForwardBase 的shader變體。這些變體會處理不同的光線映射類型和主平行光是否有陰影。multi_compile_fwdadd
:編譯所有Pass類型為ForwardAdd 的shader變體。這些編譯變體用來處理平行光、聚光和點光類型,並且他們的變體會烘焙貼圖。multi_compile_fwdadd_fullshadows
:與上面的一致,但是還包含了光源實時陰影的處理。multi_compile_fog
:擴展了幾個變體來處理不同的霧類型
大部分內置的multi_compile縮寫都會生成許多的shader變體。如果你清晰的知道有些東西你是不需要的,那么你可以使用指令 #pragma skip_variants來跳過一些變體。
#pragma multi_compile_fwdadd // will make all variants containing // "POINT" or "POINT_COOKIE" be skipped #pragma skip_variants POINT POINT_COOKIE
着色器硬件變體
一個常見的使用shader變體的原因是為了創建可靠的,簡化的shader,並且這些shader還可以在高端或者低端的硬件設備上高效的運行,同時只有一個目標平台,比如說 OpenGL ES。無敵了提供一個特別的,專為不同級別能力的硬件優化的shader變體,你可以使用shader的硬件變體。
要生成shader的硬件變體,那么需要添加指令:#pragma hardware_tier_variants renderer,這里面的renderer是一個可用的硬件平台。使用這個#pragma 每個shader都會生成3個shader變體,不需要任何的關鍵字。每一個變體都會有以下其中之一被定義:
UNITY_HARDWARE_TIER1 UNITY_HARDWARE_TIER2 UNITY_HARDWARE_TIER3
你可以使用這些關鍵字來對更高或者更低的硬件編寫額外的回調或者特性。在編輯器模式下你可以使用圖形模擬菜單來測試任意一個檔位。
為了保持這些變體盡量小,只有一個變體的shader會被加載到玩家處。除此之外,任何其它相同的shader-比如你只對TIER1做了特別優化版本,但是其它版本都一樣,那么就不會增加額外的存儲空間。
在加載時,Unity會檢查當前在使用的GPU並且自動的獲取一個 硬件等級值;如果GPU沒有被自動獲取到,那么默認就會是最高等級。當然,你可以覆寫等級值: Shader.globalShaderHardwareTier,但是這個必須要在你希望的任何更改着色器加載之前完成。一旦shader被加載,他們會自動選擇他們的變體,這個時候這個值就沒卵用了。一個好的做法是,加載一個預加載的場景,然后再加載主場景。
注意,這些shader硬件層級不會受玩家設置的圖像質量的影響,它們只取決於當前運行的GPU是個什么等級。
平台shader設置
除了為不同的硬件層調整您的着色器代碼之外,您可能還想調整unity內部的定義(比如,你可能想要強制在手機上開啟陰影接受)。你可以通過這個API來找到一些細節:UnityEditor.Rendering.PlatformShaderSettings ,這里面提供了一系列 可以覆寫每個層級的特性。使用UnityEditor.Rendering.EditorGraphicsSettings.SetShaderSettingsForPlatform 來為每個平台,每個硬件等級進行shader調整。
注意如果 PlatformShaderSettings為不同硬件層級設置了不同的屬性,那么即便#pragma hardware_tier_variants沒有使用,也會生成許多shader變體。