Unity shader的內置宏與變體(二)


簡介

本文總結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里可以直接獲得當前編輯器中使用到的變體集。該文件如下圖所示:

image

利用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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM