我們通過(guò)assetbundle加載材質(zhì)球或shader時(shí)常遇到一個(gè)問(wèn)題:在電腦上測(cè)試ok的shader,在手機(jī)上顯示一片粉紅。
出現(xiàn)這種情況的原因有很多,但是其中可能性最大的就是shader變體丟失導(dǎo)致的。
ab資源加載中的shader是如何被引用到的?
加載ab資源中的材質(zhì)時(shí),會(huì)主動(dòng)索引并加載使用的shader。
“被包含在構(gòu)建包體中”,指的這幾種情況:
- shader文件在Resources文件夾下;
- shader文件被設(shè)置在Setting => Graphics => Always Included Shaders中;
- shader被構(gòu)建包含的場(chǎng)景中使用;這幾種情況下shader資源會(huì)被打進(jìn)apk包中。
基于shader是否在構(gòu)建包體中,有以下2種情況:
- 如果shader被包含在構(gòu)建包體內(nèi),則可以直接加載使用。
- 如果shader未被包含在構(gòu)建包體內(nèi),需要通過(guò)ab資源加載使用時(shí),使用前shader需要首先加載shader所在的ab資源。 如果ab資源尚未加載,此時(shí)使用shader的材質(zhì)就會(huì)出問(wèn)題。
為什么ab中的shader變體會(huì)丟失?
multi_compile和shader_feature是著色器中定義宏的關(guān)鍵字,不同的關(guān)鍵字會(huì)生成不同的變體。
相比于multi_compile指令,使用 shader_feature 指令定義的著色器在構(gòu)建時(shí),沒(méi)有在材質(zhì)球中使用到的變體將不會(huì)包含在最終的構(gòu)建中。
所以 shader_feature 用于材質(zhì)中設(shè)置的關(guān)鍵字,而 multi_compile 更適合通過(guò)代碼來(lái)全局設(shè)置的關(guān)鍵字。
在Assetbundle打包材質(zhì)與其引用的shader時(shí):
- 如果shader和材質(zhì)在同一個(gè)ab包中,在打包時(shí)會(huì)自動(dòng)檢索到使用的keyword,并將其對(duì)應(yīng)的變體編譯打包入ab包中。
- 如果shader和使用到的材質(zhì)不在同一個(gè)ab包中,則打包時(shí)無(wú)法檢索到使用到了哪些keyword,所以材質(zhì)使用的變體可能丟失。
這就是ab打包過(guò)程中的變體丟失的問(wèn)題。
如何避免shader變體丟失?
第一種方法:將shader加入到ProjectSetting=>Graphics=>Always Included Shaders 中。
加在這里的shader不會(huì)剔除變體,所以適用于變體少且全部使用到的shader。
如果加入有很多變體的shader,會(huì)導(dǎo)致打包包體膨脹。
注意:shader_feature定義的關(guān)鍵字如果未使用,即使加入到了這里,變體也不會(huì)保留!
第二種方法:使用ShaderVirantCollection。
我們可以使用ShaderVirantCollection文件來(lái)定義使用到的變體,這樣打ab包時(shí)會(huì)自動(dòng)將定義的變體編譯并打包,這樣就避免了變體丟失。
注意:ShaderVirantCollection也必須入包(與shader放入同一個(gè)ab包),否則不生效。
可以通過(guò)shader control插件來(lái)搜集使用到的shader變體,從而避免人工查找容易漏掉了某些變體。
當(dāng)然shader control可以?xún)H可以查詢(xún)到項(xiàng)目中材質(zhì)球使用到的shader關(guān)鍵字,如果是通過(guò)代碼加載/設(shè)置shader keyword,則無(wú)法通過(guò)shader control查詢(xún)到。
assetbundle打包shader的最佳方案
下面是我當(dāng)前項(xiàng)目中assetbundle打包shader的方案,暫時(shí)定義為我自己的最佳方案:
- 把a(bǔ)ssetbundle資源中引用到的shader都放到一個(gè)ab包中,在進(jìn)入游戲時(shí)首先加載此ab包,這樣可以保證后續(xù)所有使用的材質(zhì)沒(méi)有問(wèn)題。
- 常用到且變體數(shù)量少的shader,可以將shader加入到Always Included Shaders 中,然后在打包ab資源時(shí)剔除這些shader。
- 需要assetbundle打包的shader,使用ShaderVirantCollection定義需要打包的變體??梢酝ㄟ^(guò)shader control插件來(lái)搜集材質(zhì)球使用到的shader變體,結(jié)合人工查找代碼中調(diào)用使用到的變體。
multip_compile 和shader_feature的區(qū)別
相同點(diǎn):
- 聲明Keyword,用來(lái)產(chǎn)生Shader的變體(Variant)
#pragma multi_compile A B
//OR #pragma shader_feature A B
- 這個(gè)Shader會(huì)被編譯成兩個(gè)變體:一是只包含A模塊代碼的變體A;二是只包含B模塊代碼的變體B;
- 指定的第一個(gè)關(guān)鍵字是默認(rèn)生效的,即默認(rèn)使用變體A;
- 在腳本里用Material.EnableKeyword或Shader.EnableKeyword來(lái)控制運(yùn)行時(shí)具體使用變體A還是變體B;
- 它們聲明的Keyword是全局的,可以對(duì)全局的包含該Keyword的不同Shader起作用;
- 全局最多只能聲明256個(gè)(2020.3以后的全局keyword的數(shù)量限制是384)這樣的Keyword;
- 請(qǐng)注意Keyword的數(shù)量和變體的數(shù)量之間的關(guān)系,并可能由此導(dǎo)致的性能開(kāi)銷(xiāo),比如聲明#pragma multi_compile A B和#pragma multi_compile D E 這樣的兩組Keyword會(huì)產(chǎn)生 2x2=4 個(gè)Shader變體,但若聲明10組這樣的keyword,則該Shader會(huì)產(chǎn)生1024個(gè)變體;
不同點(diǎn):
如果使用shader_feature,build時(shí)沒(méi)有用到的變體會(huì)被刪除,不會(huì)打出來(lái)。也就是說(shuō),在build以后環(huán)境里,運(yùn)行代碼Material.EnableKeyword("B")可能不起作用,因?yàn)闆](méi)有Material在使用變體B,所以變體B沒(méi)有被build出來(lái),運(yùn)行時(shí)也找不到變體B。
如果想解決這個(gè)問(wèn)題,可以采取以下辦法中的其中一種:
- 使用multi_complie 代替 shader_feature,multi_complie 會(huì)把所有變體build出來(lái);
- 把這個(gè)Shader加入“always included shaders”中 (Project Settings -> Graphic);
- 創(chuàng)造一個(gè)使用變體B的Material,強(qiáng)行說(shuō)明變體B有用;
局部keword
全局的Keyword只能有256個(gè),這或許會(huì)最終對(duì)我們?cè)斐上拗?,而且大部分Keyword并不需要進(jìn)行全局聲明。
因此,我們可以使用multi_complie_local來(lái)聲明局部的、只在該Shader內(nèi)部起作用的Keyword,用法相似:
#pragma multi_compile_local __ A
//OR #pragma shader_feature_local __ A
注意:
- local Keyword仍有數(shù)量限制,每個(gè)Shader最多可以包含64個(gè)local Keyword;
- 因?yàn)檫@種Keyword是局部的,Material.EnableKeyword仍是有效的,但對(duì)Shader.EnableKeyword或CommandBuffer.EnableShaderKeyword這種全局開(kāi)關(guān)來(lái)說(shuō)無(wú)法使用;
- 當(dāng)你既聲明了一個(gè)全局的Keyword A ,同時(shí)又聲明了一個(gè)同名的、局部的Keyword A,那么優(yōu)先認(rèn)為Keyword A是局部的。
參考鏈接
Unity Manual - 著色器變體和關(guān)鍵字
Unity Blog - Stripping scriptable shader variants
【Unity游戲開(kāi)發(fā)】馬三的游戲性能優(yōu)化自留地