轉自:https://www.cnblogs.com/cpxnet/p/6439706.html
在unity的資源中,shader是比較特殊的一類。主要有下面幾個疑問
1. Shader算是代碼,並且需要編譯。那麼是否可以熱更新?
2. AB中加載進來的shader是否可以通過shader.Find(名稱)來索引?
3.在使用shader_feature關鍵字後,build時忽略的變種是否要在運行時編譯?
4.預編譯shader cache的存儲位置究竟在哪裏?
直接補充最終結論:
1. shader可以熱更新。
2. 使用multi_compile生成Shader Variant時,材質可以直接熱更新。
3. 使用shader_feature生成Shader Variant時,可以使用ShaderVariantCollection來記錄所有使用到的變種,實現材質熱更新。(目前仍有bug,必須將shader、SVC和所有材質放在一個AB中)
4. 不要用Shader.Find找自己包裏的shader,使用AssetBundle.LoadAsset<Shader>()
5. shadercache隨材質存儲,材質可以熱更新。
針對以上問題我做了一系列測試,記錄如下:
測試一:
準備一個shader ,一個材質,一個cube做的prefab,各自打成一個AB。
在一個空場景中用腳本按如下順序加載:
shaderAB->materialAB->prefabAB->prefab->GameObject。顯示正常。
(事實上只要保證prefabAB->prefab->GameObject的順序,materialAB和shaderAB在同一函數的任意位置都可以。Unity應該是延遲處理了資源的引用關係)
修改shader,打成新的ab,改名或者另存爲備用。
發佈以後,在文件夾中找到對應的shaderAB,使用新的shaderAB替換。
重新啓動,效果已經更新。
結論一: shader可以熱更新!
測試平臺:android和pc standalone
代碼稍作修改可以在運行時實現熱更新。
測試二:
準備3個shader,引用同一個頭文件。shader和cginc全部進入一個ab裏。
運行時先加載shaderAB,然後用一個按鈕切換shader
結果如下表:
|
熱更新前 |
熱更新後 |
Shader.Find |
正常(原shader) |
錯誤(shader丟失)[1] |
AssetBundle.LoadAsset[2] |
正常(原shader) |
正常(新shader) |
[1] 在Standalone或者移動平臺上會有shader丟失;在Editor模式下會使用舊的shader,仔細分析後猜測是在Editor模式下,shader.Find的查找順序如下:已加載的 AssetBundle->Shader源文件。而在發佈平臺上,由於沒有散的shader源文件,所以直接丟失。
[2] AssetBundle.LoadAsset的路徑要使用Manifest中記載的路徑,如下形式:
Assets/Shaders/Src/shaderTest2.shader
結論二:可以在運行時手動替換上AB中的shader,但必須使用AssetBundle.LoadAsset!
·可以使用cginc頭文件!
·可以使用文件夾管理Shader!
·最好完全不使用Shader.Find,除非你100%確定這個shader不會熱更新。
關於Shader.Find,個人猜測如下:
Unity內部使用一個字典或者HashSet來支持Shader.Find,這裏暫且叫它ShaderMap。ShaderMap的鍵是ShaderLab語法中的名字;值是Shader文件的GUID。
ShaderMap生成於Build項目時,保存了來自三個地方的shader cache引用關係:
1. Resources中的shader或Resources其中其他資源引用到的shader
2. 任意場景中引用到的shader
3. StreamingAssets中Asset Bundle內的Shader
運行時使用ShaderFind,只能找到這些Shader,如果對應GUID的shader不存在,查找就會失敗,即使熱更新後加入了新的Asset Bundle中含有同名Shader(即ShaderLab語法同名)。
4. 目前沒有辦法在發佈以後動態更新ShaderMap。
測試三:
準備兩個同樣的shader,設定好#ifdef FEATURE,其一使用multi_compile,其二使用shader_feature
準備四個材質,分別對應
·multi_compile FEATURE on
·multi_compile FEATURE off
·shader_feature FEATURE on
·shader_feature FEATURE off
所有shader打成一個ab, 所有material打成一個ab
在運行時切換4個材質。結果如下:
·multi_compile FEATURE off |
正常 |
·multi_compile FEATURE on |
正常 |
·shader_feature FEATURE off |
正常 |
·shader_feature FEATURE on |
錯誤(和shader_feature FEATURE off一樣) |
結論三:
·使用shader_feature的uber shader無法熱更新!(結論已更新)
·若將shader存儲於自定義AB時,僅按照所有shader_feature都沒有定義的方式來編譯。並且不會彙報這個編譯過程中的任何錯誤!(如:在shader中定義了shader_feature A B;並且依賴於A、B二者任一必須定義的話,編譯就會出錯。)
·Unity並不會在發佈平臺上編譯缺失的變種。(直接拿個現有的變種湊數?)
測試四
放棄熱更新shader,檢查在使用shader_feature的時候,材質能否熱更新。即能否在熱更新時生成缺失的變種。
準備一個uber shader。再來3個材質,各使用不同的變種,並分別打成m1,m2,m3三個包。發佈時僅選擇m1發佈,然後在運行時熱更新,使用m2,m3替換m1,顯示效果達到預期。
這時候注意到m1,m2,m3體積分別爲11,9,11KB,應該不只是存有shader引用和相關參數。因此再將m1,m2,m3打爲一個ab,體積爲11kb。
結論四:
·在shader進入mainAssets的前提下,材質可以熱更新。
·shader cache隨material ab存儲,多個引用了同樣shader(變種)的材質會重複存儲cache。
更新測試五
使用ShaderVariantCollection,記錄所有用到的variant。
將SVC和shader打入一個ShaderAB。
將材質打成MaterialAB
運行時加載ShaderAB,取SVC,WarmUp,再加載MaterialAB。結果丟失部分variant
更換分包方式,SVC、shader和Material打成一個包。一切正常。
結論五:
·使用ShaderVariantCollection可以做到帶變種的材質更新。
·目前版本(5.6.0和5.5.2)依然有bug,必須將SVC、shader和所有對應材質放在一起才能做到可熱更新。
備註:
·爲了測試,我用HFS配置了局域網http服務器,只要在同一個無線網端下,pc和手機都能訪問,配合不同平臺的AB分文件夾管理,所有平臺都能同步看到效果。
·ab.Unload()會把ab設爲null!
·cginc頭文件修改後,所有用到的shader必須手動import一次以強制重新編譯!
參考資料:
·hfs使用介紹:http://bbs.feng.com/read-htm-tid-2234118.html
·ab模型:http://www.cnblogs.com/88999660/archive/2013/03/15/2961663.html
·www禁用cache方法:http://answers.unity3d.com/questions/209078/disable-cache-for-www.html