1.變體簡介
2.為什么需要變體
3.變體是如何產生的
4.變體帶來的麻煩
5.multi_compile和shader_feature
1.變體簡介
比如我們開了一家餐廳, 你有一本萬能的菜單( Shader源代碼) , 上面包含了所有可能的菜式; 但是顧客每次來點餐時, 不可能將整本菜單都做一遍, 他們會根據今天有沒有優惠( 是否啟用霧效) , 是不是會員( 是否啟用陰影) , 想吃主食還是甜點( 使用哪個渲染管線) 來勾選一些選項( Shader Keywords) a. 萬能的菜單( Shader源碼) 包含了所有可能的代碼, 比如做牛排的步驟, 做冰淇淋的步驟, 會員專屬擺盤的步驟b. 最終給顧客的定制菜單( Shader Variant, 著色器變體) 根據顧客勾選的選項( Keywords) , 從萬能的菜單里實際抄錄下來的那一小部分菜式的集合; 它是一個最終編譯好的, 獨立無二, 只包含當前所需功能的完整Shader程序c. 選項( Shader Keywords) 比如_ENABLE_FOG_ON ( 啟用霧效) , _LIGHT_TYPE_SPOT ( 點光源) 等, 這些是生成不同變體的條件
Shader變體就是同一個Shader源代碼, 根據不同的功能開關( Keywords) 組合, 編譯出的多個不同版本的, 實實在在的Shader程序
2.為什么需要變體
1 ) . 性能如果在Shader里寫if ( _USE_FOG) { .. . } , GPU在執行時依然會判斷這個條件, 會有性能損耗; 而變體就是編譯的時候就決定了代碼的去留, 運行時沒有任何判斷開銷2 ) . 靈活性游戲要運行在不同性能的設備上, 可以為低端機編譯一個去掉高級效果的變體, 為高端機保留所有效果; 一份Shader源碼適配多種配置
3.變體是如何產生的
變體通過#pragma multi_compile和#pragma shader_feature兩個指令
a. multi_compile不管你的場景用不用這些關鍵字, 引擎都會關鍵字生成變體; 適合必須存在的功能, 比如光源類型b. shader_feature只有在Material上真正啟用了某個關鍵字或其他變體依賴時, 才會生成對應的變體; 適合可選的功能, 比如是否啟用積雪
4.變體帶來的麻煩
a. 編譯時間變長ShaderLab編譯器需要為每一種組合編譯一次, 1024 個變體……你自己想象b. 構建體積巨大每個變體都會被打包到最終的游戲里( 如果使用了的話) , 導致游戲安裝包( APK/ IPA) 變得非常大c. 內存占用高即使你沒使用, 有些被強制編譯的變體( multi_compile) 也會被加載到內存中; multi_compile變體即使未被使用也會占用內存, 主要是因為Unity的資源加載機制為了確保運行時的穩定性和效率, 傾向于將一個Shader的所有已編譯變體作為一個整體來加載和管理
5.multi_compile和shader_feature
1 ) . multi_compile它的設計目的是強制列出所有可能性, __代表一個你必須明確指出的, 有效的全局默認狀態; 它告訴你: 看, 即使你什么都不開, 我也會為你生成一個專門的變體; 所以它的語法是 __ A B, 非常直白2 ) . shader_feature它的設計初衷是一個可選的、開關式的功能, 它的思維模式是: - 我有一個功能, 比如積雪- 這個功能有兩種狀態: 啟用( 對應 _KEYWORD_C) 和禁用( 這是一個隱含的、不需要聲明的狀態) 因此, 它的標準寫法就是簡單地列出啟用這個功能時所需的關鍵字; 禁用狀態被認為是自然而然的默認情況, 不需要額外聲明