最近比較忙,好久沒有更新博客了,新項目切換到unity5.x后使用了新的打包機制,在打包shader的時候遇到了一些問題,這里來記錄一下吧。
在上一個項目中,我們使用unity4.7,對於shader並沒有進行依賴打包,而是由unity打包到了每個用到的AssetBundle中去,其實這樣是“很不科學的”。這樣不僅增加了ab的總體積,而且還會在運行時產生很多的shader實例,增加很多的顯存占用。所以我們決定把自定義的shader打包成一個AssetBundle。
用到的工具:
1、UnityStudio:https://github.com/Perfare/UnityStudio/releases
這是一個解包AssetBundle的工具,可以查看和導出ab中的資源。
2、Unity自帶的profiler
下面是一些嘗試和結果分析:
一、不進行依賴打包(不需要設置GraphicsSetting,不給自定義shader設置AssetBundle Name)
這種情況unity5.x還是與unity4.x一樣,把shader打包到每一個使用它的AssetBundle 中去,這樣就會同時存在多個一樣的shader,並在運行時產生多個shader實例。
如下圖:
這個Character是我們自定義的一個shader,在運行時產生了多個實例:

使用UnityStudio解包一個ab可以看到,我們自定義的shader ParticlesAlphaTintColor被打包到ab中:

打包后的運行測試結果如下:
1、編輯器是pc平台的時候,打出來的包在編輯器里運行是正常的。
2、編輯器是安卓時,打出來的包在編輯器里運行顯示粉紅(查看材質可以發現並不是丟失材質和shader),但在手機正常。
這是為什么呢?這是因為以安卓為平台打包的時候被打包的shader被編譯成安卓平台的版本,在unity編輯器中運行會發生異常。這是從4.x到5.x一直有的問題。
解包后我們可以看到不同平台編譯出來的shader subProgram不同。
Pc:

安卓:

二、依賴打包(將shader設置AssetBundle Name打包)
使用這種方式,需要將shader添加到GraphicsSetting-> always included shader設置中,否則shader也會顯示粉紅。注意要在設置Graphics之后把shader重新打包,在能生效。
文檔中也有說到:https://docs.unity3d.com/Manual/class-GraphicsSettings.html

測試結果如下:
1、編輯器是pc平台的時候,打出來的包在編輯器里運行是正常的。
2、編輯器是安卓時,打出來的包在編輯器里運行顯示粉紅(查看材質可以發現並不是丟失材質和shader),但在手機正常,原因同上。
再使用profiler查看,可以發現shader可以被物體共用了,隨着物體數量的增加也不會產生多個shader實例。(下圖有兩個Character是因為編輯器中ShaderVariantCollection跟蹤到了這個shader,也算一個引用)

這時解包模型的ab,也不會看到shader被打進ab中了,它們只存在與自己的ab包中。
三、注意事項
1、細心的朋友會發現,一中我們profiler中看到的Character是45.7k,而二中是334k,這是因為Character是一個多變體的shader,而加入了GraphicsSetting-> always included shader后,會將它所有的變體打包到游戲中。
2、在測試打包的過程中我發現一個有趣的現象--- unity5.4和4.x打包后的shader解包(非依賴打包)出來看起來不一樣:
5.4沒有看到代碼,而是看到GPUProgramID

而4.7是直接看到代碼

結合最新更新的unity5.5的更新日志,似乎可以看出點端倪:
https://unity3d.com/cn/unity/whats-new/unity-5.5.0
---Shaders: Shaders are now exported to the Unity player completely in binary. There is no Shader text string and parsing in run time.
3、關於untiy內置shader
如果沒有設置到GraphicsSetting-> always included shader中,那么會打包到依賴它的ab中,如果設置了就不會打包進去。而是再構建的時候,就導入到你的游戲。
4、手動設置shader 加入GraphicsSetting-> always included shader很麻煩,怎么辦?
其實是可以使用代碼設置的:
來自:http://www.hiwrz.com/2016/04/18/unity/175/
- [MenuItem("Test/測試設置included shader", false, 11)]
- public static void TestIncludedShader()
- {
- string[] myShaders = new string[1]{
- "Legacy Shaders/Diffuse"
- };
- SerializedObject graphicsSettings = new SerializedObject (AssetDatabase.LoadAllAssetsAtPath ("ProjectSettings/GraphicsSettings.asset") [0]);
- SerializedProperty it = graphicsSettings.GetIterator ();
- SerializedProperty dataPoint;
- while (it.NextVisible(true)) {
- if (it.name == "m_AlwaysIncludedShaders") {
- it.ClearArray();
- for (int i = 0; i < myShaders.Length; i++) {
- it.InsertArrayElementAtIndex(i);
- dataPoint = it.GetArrayElementAtIndex (i);
- dataPoint.objectReferenceValue = Shader.Find(myShaders[i]);
- }
- graphicsSettings.ApplyModifiedProperties ();
- }
- }
- }
5、加入AlwaysIncludedShaders的shader是開始游戲的時候就全部編譯了嗎?
答案是不會,加到always include 的shader,會將shader的所有變體打包到游戲,用到的時候才會加載用到的變體到內存!需要預加載變體可以使用:ShaderVariantCollection
相關文檔:https://docs.unity3d.com/Manual/OptimizingShaderLoadTime.html

值得注意的是unity5.x內置的 Standard shader是一個有成千上萬變體的shader,要謹慎的把它加入到GraphicsSetting-> always included shader,因為1中的原因,會使得你的包體非常大,打包也非常耗時。至少在我測試下是一直卡在這個界面到下班都沒有響應。。
文檔里面也有提到:

6、關於shader加載和預熱的一些資料:
Unity - Manual: Optimizing Shader Load Time
https://docs.unity3d.com/ScriptReference/ShaderVariantCollection.html
http://www.seven-fire.cn/archives/174
