unity shader 變種(多重編譯 multi_compile)

一、定義

在unity中我們可以通過使用#pragma multi_compile或#pragma shader_feature指令來爲shader創建多個稍微有點區別的shader變體。這個Shader被稱爲宏着色器(mega shader)或者超着色器(uber shader)。實現原理:根據不同的情況,使用不同的預處理器指令,來多次編譯Shader代碼。
在運行時,Unity從Material宏Material.EnableKeyword和Shader.DisableKeyword或全局着色器宏Shader.EnableKeyword和Shader.DisableKeyword中選擇適當的着色器變體。如果這兩個宏都未啓用,則Unity使用第一個宏。

二、使用

//定義兩個TEST_1,TEST_2兩個宏
#pragma multi_compile TEST_1 TEST_2
//在shader中使用
#ifdef TEST_1
    //Todo      
#endif
#ifdef TEST_2
    //Todo      
#endif

上面這個命令會產生2種着色器變種:TEST_1,TEST_2。
要生成未定義預處理器宏的着色器變體,請添加一個僅爲下劃線(__)的名稱。這是避免使用兩個宏的常用技術,因爲對項目中可以使用的宏數量有限制,例如:

#pragma multi_compile __ TEST_1

在腳本中控制使用:

//使用TEST_1變種
Shader.EnableKeyword ("TEST_1");
Shader.DisableKeyword ("TEST_2");

三、組合

#pragma multi_compile TEST_1 TEST_2
#pragma multi_compile TEST_3 TEST_4 TEST_5

它產生總共六個着色器變體(TEST_1_TEST_3,TEST_1_TEST_4,TEST_1_TEST_5,TEST_2_TEST_3,TEST_2_TEST_4,TEST_2_TEST_5)。
所以如果有10行multi_compile,每行2個選項,那麼將一共產生1024個着色器變體。
請記住,着色器變體數量將以這種方式瘋狂增長。

四、pragma shader_feature

shader_feature非常相似multi_compile。唯一的區別是Unity shader_feature在最終版本中不包含未使用的着色器變體。所以shader_feature適用於在我們在編輯器中,選中材質,設置它使用的shader的宏,如果在程序中動態的去設置可能無效(原因下面說明)。而對於multi_compile,會把所有的變體都編譯進程序裏,所以適合需要在程序運行中動態改變狀態的宏,適合全局設置 。
材質中設置位置截圖:

五、宏限制

在unity中限制了全局的宏個數爲265個,而unity內部使用了大約60個,所以在多個不同的着色器中定義全局宏時需要注意宏數量不要超過限制。
使用本地宏替代一部分全局宏:使用shader_feature_local和multi_compile_local。
shader_feature_local:類似於shader_feature,但枚舉宏是本地的。
multi_compile_local:類似於multi_compile,但枚舉宏是本地的。
在項目中除非是希望通過全局API啓用的那些特定宏,否則應儘量使用本地宏,
使用更多本地宏和更少的全局宏,以減少每個着色器的宏總計數。如果存在具有相同名稱的全局和本地宏,則Unity會優先使用local宏。
注意:
(1)不能將本地宏與進行全局宏更改的API一起使用(例如Shader.EnableKeyword或CommandBuffer.EnableShaderKeyword)。
(2)每個着色器最多有64個唯一的本地宏。
(3)如果Material啓用了本地宏,並且其着色器更改爲不再聲明的宏,則Unity會創建一個新的全局宏。

六、內置multi_compile快捷方式

unity中提供一些內置的宏用於編譯多個着色器變體。這些主要用於處理Unity中不同的光照,陰影和光照貼圖類型。
multi_compile_fwdbase:編譯PassType.ForwardBase所需的所有變體。變體處理不同的光照貼圖類型,並啓用或禁用主方向光的陰影。
multi_compile_fwdadd:爲PassType.ForwardAdd編譯變體。這將編譯變體以處理Directional,Spot或Point Light類型及其變體與Cookie紋理。
multi_compile_fwdadd_fullshadows:同樣multi_compile_fwdadd,但也包括燈具有實時陰影的能力。
multi_compile_fog:擴展爲多個變體以處理不同的霧類型(off / linear / exp / exp2)。

大多數內置快捷方式都會產生許多着色器變體。如果您知道項目不需要它們,您可以使用#pragma skip_variants跳過編譯它們中的一些。例如:

#pragma multi_compile_fwdadd
#pragma skip_variants POINT POINT_COOKIE

該指令跳過包含POINT或POINT_COOKIE的所有變體。

七、查看shader變種數量

#pragma multi_compile TEST_1 TEST_2 TEST_3
#pragma multi_compile TEST_4 TEST_5
#pragma multi_compile TEST_6 TEST_7

查看變體數量.png

 

上面的組合會產生3x2x2=12種變體,我們可以點擊show查看具體的變體組合名稱。

// Total snippets: 1
// -----------------------------------------
// Snippet #0 platforms ffffffff:
Keywords always included into build: TEST_1 TEST_2 TEST_3 TEST_4 TEST_5 TEST_6 TEST_7

12 keyword variants used in scene:

TEST_1 TEST_4 TEST_6
TEST_1 TEST_4 TEST_7
TEST_1 TEST_5 TEST_6
TEST_1 TEST_5 TEST_7
TEST_2 TEST_4 TEST_6
TEST_2 TEST_4 TEST_7
TEST_2 TEST_5 TEST_6
TEST_2 TEST_5 TEST_7
TEST_3 TEST_4 TEST_6
TEST_3 TEST_4 TEST_7
TEST_3 TEST_5 TEST_6
TEST_3 TEST_5 TEST_7

這裏查看的是所有會被編譯的變體的數量,也就是#pragma multi_compile聲明的宏的全部組合。

#pragma multi_compile TEST_1 TEST_2 TEST_3
#pragma multi_compile TEST_4 TEST_5
#pragma shader_feature TEST_6 TEST_7

查看變種數量.png

// Total snippets: 1
// -----------------------------------------
// Snippet #0 platforms ffffffff:
Keywords stripped away when not used: TEST_6 TEST_7
Keywords always included into build: TEST_1 TEST_2 TEST_3 TEST_4 TEST_5

6 keyword variants used in scene:

TEST_1 TEST_4 TEST_6
TEST_1 TEST_5 TEST_6
TEST_2 TEST_4 TEST_6
TEST_2 TEST_5 TEST_6
TEST_3 TEST_4 TEST_6
TEST_3 TEST_5 TEST_6

上面的組合會產生3x2x1=6種變體,#pragma shader_feature沒有特別處理的話只有會默認包括第一個宏。

八、編譯

(1)material的ShaderKeywords

Material所包含的Shader Keywords表示啓用shader中對應的宏,Unity會調用當前宏組合所對應的變體來爲Material進行渲染。在Editor下,可以通過將material的inspector調成Debug模式來查看當前material定義的Keywords,也可在此模式下直接定義Keywords,用空格分隔Keyword。

 

設置ShaderKeywords.png

 

優點:根據material中的ShaderKeywords自動生成變體。無需額外設置
缺點:多個不同的material包中可能存在相同的shader變體,造成資源冗餘。若在程序運行時動態改變material的keyword其變體可能並沒有被生成
如上圖設置:如果ShaderKeywords中沒有設置TEST_6,這是如果我們想在程序中通過代碼動態使用TEST_6這個宏(Shader.EnableKeyword("TEST_6"))。可能不能得到想要的效果,因爲TEST_6這個變種沒有生成。

(2)把Shader加入到Always Include Shaders列表裏

找到ProjectSetting->Graphics->Always Include Shaders列表,將我們需要的shader添加到裏面,這樣unity將會把這個shader的所有的變種都生成出來。

 

AlwaysIncludeShaders.png

優點:我們不用擔心項目發佈出去以後有些變種沒有生成,不能在程序中動態的去控制我們的宏。
缺點:生成的變體數量龐大,導致發佈時間變長,遊戲包體過大。比如你把standardShader放進去,由於它有大量的keyword,全部變種都生成的話大概有幾百兆。

(3)使用ShaderVariantCollection是生成指定變體

ShaderVariantCollection是unity5.x以後用來記錄shader的哪些變體需要被生成。這樣做的好處就是在shader_feature與multi_compile結合使用時,能夠設置生成何種變體,從而避免生成不必要的變體;shader不必和material打在一個包中,避免了多個包中存在相同的變體資源;明確直觀的顯示了哪些變體是需要生成的。

生成方式:

(1)通過Create->Shader-> Shader Variant Collection,就可以新建一個shader variant collection文件,手動添加需要編譯的變種

 

ShaderVariantCollection.png

 

選擇需要生成的變種.png

 

(2)通過Edit->Project Settings->Graphics中的save to asst...按鈕,生成unity幫我們自動收集的,使用到的變種信息。

 

自動生成.png

這時候只需要先Clear一下,然後依次打開我們的所有場景,把需要的物體都顯示一遍,Unity就會自動記錄下來哪些着色器的哪些着色器變體已經被使用到。統計完後只需點擊下面的保存按鈕就可以生成我們所需要的ShaderVariantCollection資源。當然你也可以爲你的每一個場景或者按需生成足夠多的ShaderVariantCollection資源。自動收集的功能不一定百分百可靠,最好事後多檢查。

ShaderVariantCollection加載:

啓動時預加載:

 

最簡單最粗暴的使用方式就是在遊戲啓動的瞬間就直接加載ShaderVariantCollection資源並編譯裏面的着色器變體,Unity已經爲我們做好這一步了,依然還是在圖形設置面板裏,只需把需要啓動是就編譯的ShaderVariantCollection添加在Preloaded Shaders裏面

預加載.png


代碼加載:

由於ShaderVariantCollection也是一種資源,可以跟紋理、模型等等資源一起打包和加載等,只需在加載之後調用一句WarmUp。

ShaderVariantCollection shaderVariantCollection = Resources.Load <ShaderVariantCollection>( "MainShaderVariant");
if (shaderVariantCollection )
      shaderVariantCollection.WarmUp ();

也可以把ShaderVariantCollection放在Resources目錄下,好像會被自動加載。



 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章