簡介
本文總結Unity變體與Shader打包相關內容。基於Unity 2020.3和Built-in管線。
1.宏的定義
首先說明,本文中的宏不包含由#define定義的宏。
Unity提供了兩種定義宏的方法:
- multi_compile
- shader_feature
以及相應的局部版本(2019引入):
- multi_compile_local
- shader_feature_local
另外還有只在特定階段起作用的宏定義方法(2020.3引入),如:
- multi_compile_fragment
- shader_feature_local_vertex
實際使用中multi_compile與shader_feature基本沒有區別。他們主要有兩個區別。第一個是在shader_feature后面只跟了一個宏的時候,會生成兩個變體,一個是不包含該宏,另一個是包含該宏。另一個區別是打包時的表現。在打包的時候shader_feature不會包含沒有使用的變體,而multi_compile會排列組合所有變體。
2.變體
Unity在編譯shader時,不同的宏組合會生成獨立的shader程序,這些獨立的shader程序就叫做變體。
實際項目中,宏的使用非常復雜:有些宏是靜態的,有些宏是動態的。因此Unity很難幫我們正確的收集所有變體:不是缺少,就是冗余。因此,Unity提供了兩個操作變體的工具:Shader Variant Collection和IPreprocessShaders.OnProcessShader。
2.1 Shader Variant Collection
Shader Variant Collection是一種包含shader變體列表的文件。在Project窗口右鍵依次點擊[Create] - [Shader] - [Shader Variant Collection]可以創建它。或者在Graphics Setting里可以直接獲得當前編輯器中使用到的變體集。該文件如下圖所示:
利用Shader Variant Collection我們可以手動的將需要的變體加入其中。
加入的時候需要設置Pass Type和宏組合。其中Pass Type就是shader中在pass的Tags里設置的LightMode。具體見參考資料。
2.2 IPreprocessShaders.OnProcessShader
我們可以通過繼承IPreprocessShaders以及實現OnProcessShader接口來實現剔除變體的目的。簡易的代碼如下:
public class ShaderProcess : IPreprocessShaders
{
public int callbackOrder => 0;
public void OnProcessShader(Shader shader, ShaderSnippetData snippet, IList<ShaderCompilerData> data)
{
for (int i = data.Count - 1; i >= 0; --i)
{
if (data[i].graphicsTier != UnityEngine.Rendering.GraphicsTier.Tier1)
{
data.RemoveAt(i);
}
}
}
}
通過實現這個接口,我們可以更深入的理解Unity變體的編譯過程,Unity是以shader階段為單位進行編譯的,如:編譯VS時調用一次該接口,編譯PS時再調用一次該接口。因此Unity建議在定義宏時加上階段后綴,這樣可以減少shader的編譯時間。另外在Build-in管線中,宏是區分Tier的,即使並沒有使用Tier相關宏。Tier的使用比較少,而且在SRP中已經廢除了Tier機制,建議直接把Tier2和Tier3的變體剔除掉。
3.總結
宏的使用要小心謹慎。使用不當可能會導致變體爆炸,極大的拖慢打包速度,以及增大內存。而缺變體會導致效果出錯。Unity提供的兩種變體操作方法,一種是手動增加變體,一種是手動減少變體。或許這兩種方法正好對應着shader_feature和multi_compile。在實際使用中,筆者發現這兩種方法同時使用有重復用功的嫌疑。比如如果完整的手動收集了變體,那么可能並不需要再剔除了。那么是不是可以只使用其中一種方法呢?比如所有宏都使用shader_feature定義,然后通過shader variant collection手動收集所有需要的變體。再或者所有宏都使用multi_compile定義,然后通過IPreprocessShaders.OnProcessShader剔除掉所有不可能出現的宏組合。具體怎么處理還有待實踐驗證。
參考資料:
[1]: https://docs.unity3d.com/Manual/SL-MultipleProgramVariants.html
[2]: https://docs.unity3d.com/Manual/shader-variant-collections.html
[3]: https://docs.unity3d.com/ScriptReference/Rendering.PassType.html
[4]: https://docs.unity3d.com/Manual/shader-predefined-pass-tags-built-in.html
[5]: https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@11.0/manual/urp-shaders/urp-shaderlab-pass-tags.html#urp-pass-tags-lightmode