1. 程式人生 > >如何優化資原始檔過多導致的安裝變慢?

如何優化資原始檔過多導致的安裝變慢?

這是第129篇UWA技術知識分享的推送。今天我們繼續為大家精選了若干和開發、優化相關的問題,建議閱讀時間10分鐘,認真讀完必有收穫。

UWA 問答社群:answer.uwa4d.com UWA QQ群:465082844(僅限技術交流)

資源管理

Q1:我們專案中資原始檔有16000多個,導致在安卓手機上安裝很慢。為此,我們分析了下其他遊戲,發現他們會把所有的檔案整合到一個大的檔案,然後讀取。

我們模仿寫了個簡單的檔案整合,把所有的檔案寫入到一個大檔案,然後通過記錄的Pos和Length,Seek讀取資料,發現很卡。想請教下,針對這種檔案數量過多的,有什麼好的解決方案麼?

我們的資源大部分對應為Bundle,打成大檔案,記錄檔案頭和長度是可以的,但這裡要注意兩點: 1、不要釋放控制代碼,避免重複開銷; 2、public static AssetBundle LoadFromFile(string path, uint crc, ulong offset);Unity提供了AssetBundle讀取時offset的介面,可以使用; 3、自維護表頭檔案提前讀取,建立對映關係; 4、支援多檔案表頭對映,這樣可通過分包提高iO速度。 感謝鄭驍

[email protected]問答社群提供了回答

不知道題主說的安裝過程很慢,是apk的安裝過程,還是安裝好之後第一次啟動遊戲的資源拷貝過程? 我們遊戲的AssetBundle檔案的數量大約也有1萬多個,也算是一個量級吧。我們在一些安卓裝置上apk的安裝時間是不快,但是因為這個只是給我們測試的時候帶來一些時間上的影響,沒有納入玩家體驗的統計上,所以目前並不知道具體花費多久。在安裝好apk之後,我們沒有選擇解壓,所以這塊是沒有時間消耗的。 整合到一個大檔案,是一種常見的做法,不過我個人感覺是如果可以在C++底層直接做這件事情是最好的,相當於一套虛擬檔案系統,而在C#層做這件事情,我覺得會比較繞,從而可能導致很多無意義的記憶體佔用以及CPU消耗。一方面個人不太建議這來做,另外一方面題主也可以在Profiler裡看下具體這塊的消耗是在哪裡,也許有提升和改進的空間。 如果想用一個大檔案,基於offset的讀取又沒有發現優化空間,也可以考慮壓縮成一塊/幾塊大的檔案,然後第一次執行的時候進行解壓,不過感覺還不如安裝時間稍微長一些。 另外,題主考慮下目前的包體有多大,儘量進行一些合理的合併,在控制Patch大小的情況下,也可以減少AssetBundle的數量。如果是1GB包體,接近2萬個檔案,平均每一個也就50KB,算很小了,合併到1萬個AssetBundle檔案左右,也就是100KB的均值,都是可以接受的。 當然,也感謝題主提了這樣一個問題,讓我注意到apk的安裝時長和檔案數量可能存在關係。 感謝賈偉昊@UWA問答社群提供了該問答

Shader

Q2:我們專案中的Shader使用了Shader_feature,會產生多個變體。在編輯器模式下面執行一切正常。但是打包到Android上面,會出現Shader丟失的情況。 推測原因是,當AssetBundle打包的時候,我把Shader獨立設定了名字,模型在收集的時候,沒有管Material上面是否有巨集的設定,導致這個Shader本身是被引用到了,但是模型卻用的是變體導致丟失。

也嘗試過其他辦法,比如把Shader放入Always Included Shaders中,這樣會導致編譯漫長,包體變大,因為Shader所有的變體都會獨立被編譯一次放入apk包中,這樣確實能解決問題。其次,也嘗試過使用ShaderVariantCollection,版本是Unity 5.6.4p4,感覺沒有效果,我在Collection中設定了需要的引數,然後將它和Shader一起打包,上真機測試後Shader還是會丟失,感覺就是完全沒有生效。

最後嘗試了一種辦法,自己收集使用這個變體Shader的所有Material,然後把所有Material和Shader打成一個AssetBundle,這樣就不會丟失,但是又有一個問題,如果我一個Material需要設定一個Keyword,需要一個新的變體Shader,如果當前打包的Material中沒有產生這個Shader,那麼同理也會丟失。希望遇到過這個問題,或者有解決方案的朋友不吝賜教。

UWA:關於ShaderVariantCollection,這個問題裡有一些討論:https://answer.uwa4d.com/question/5af11b260e95a527a7a81cf0 簡單說,就是高版本(Unity 2018)能正常使用,其他情況下使用Dummy Material比較穩定。 至於“如果我一個Material需要設定一個Keyword,需要一個新的變體Shader,如果當前打包的Material中沒有產生這個Shader,那麼同理的也會出現丟失”這個問題,似乎也沒有什麼好辦法,因為這種方式的缺點就是維護起來比較麻煩。 該回答由UWA提供

ShaderVariantCollection不僅可以自動收集,也可以手動設定Keyword,所以,首先要確認所有變體和美術使用到的所有變體,建議讓美術使用前就限制好,避免後續發生問題。另外進行Shader程式碼分離,Lightmap相關Shader和角色分離,無Light和有Light分離,1個Light和2個Light分離,儘量通過程式碼減少變種,限制美術使用Shader的範圍。 感謝鄭驍@UWA問答社群提供了回答

OpenGL

Q3:之前專案Android Graphics API一直是選擇的強制OpenGL ES 2.0,近期想做優化就試著選擇成了Auto。切完後,在大部分手機上都跑的正常,測試後效能也稍有提升,但現在遇到一個奇怪的現象。現象如下:

1)在OPPO R9, MEIZU m2e 兩臺手機上會發現特效會出現粒子亂閃、粒子被放大、粒子消失等多種奇怪現象,而且只有特效有問題,其他的畫面表現都正常;

2)同樣的資源,切換回之前強制的OpenGL ES 2.0下就是好的;

3)出問題的特效都是放到UI上的特效,包括純UI面板特效,以及用RenderTexture方式實現的UI模型上綁的特效,同樣的模型特效在場景上播放是正常的。

我們用的是Unity 5.3.6版本,想問下有沒有人遇到過類似問題,或者哪方面有可能導致這個問題。

現在很多機型包括高階機都出現了精度問題,建議在可以使用float的情況下,使用float再逐步優化。 感謝鄭曉@UWA問答社群提供了回答

結下帖,經過認真思考反覆觀察,終於發現了出問題特效的共同點:位置出現了問題,然後跑去看特效Shader頂點計算部分,發現大部分特效頂點的屬性都只給了half精度,再對比了下場景特效和UI特效的區別,發現UI特效的世界座標都比場景特效大很多,於是第一直覺是half精度在這些手機上出問題了,改half為float,重新打包驗證,問題解決。 感謝題主提供了回答

Shader

**Q4:Shader中,實際計算未使用的引數,是否會進入GPU,假如在一個如下Shader中。Property裡面定義了一個貼圖_MainTexture,並且有賦值。然後SubShader裡面也定義了引數_MainTexture,但是實際運算流程並沒有使用這個貼圖。**

是不是CPU仍然會將該貼圖推入視訊記憶體,造成CPU和視訊記憶體消耗?如果保持Property存在,但是不在SubShader中定義Sampler2D,是不是就可以避開這個消耗?

Shader "Test/DummyTex"
{
    Properties
    {
        _Color ("Main Color", Color) = (1,1,1,1)
        _MainTex ("Base (RGB)", 2D) = "white" {}
    }

    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100
        
        Pass {  
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct appdata_t {
                float4 vertex : POSITION;
            };

            struct v2f {
                float4 vertex : SV_POSITION;
            };

            fixed4 _Color;
            sampler2D _MainTex;
                
            v2f vert (appdata_t v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }
                
            fixed4 frag (v2f i) : COLOR
            {
                fixed4 col = _Color;
                return col;
            }
            ENDCG
        }
    }
}

UWA:題主可以嘗試將Unity的Shader編譯成OpenGL或者DX的Shader看一看,我們以OpenGL ES 2.0為例。點選下Shader的Inspector面板裡面的Compile and Show Code按鈕,就可以顯示了。我把題主給出的Shader編譯GLES 2.0版本如下:

   //////////////////////////////////
  //                              //
  //      Compiled programs       //
  //                              //
  //////////////////////////////////
//////////////////////////////////////////////////////
No keywords set in this variant.
-- Vertex shader for "gles":
Shader Disassembly:
#version 100

#ifdef VERTEX
attribute vec4 _glesVertex;
uniform highp mat4 unity_ObjectToWorld;
uniform highp mat4 unity_MatrixVP;
void main ()
{
  highp vec4 tmpvar_1;
  tmpvar_1.w = 1.0;
  tmpvar_1.xyz = _glesVertex.xyz;
  gl_Position = (unity_MatrixVP * (unity_ObjectToWorld * tmpvar_1));
}


#endif
#ifdef FRAGMENT
uniform lowp vec4 _Color;
void main ()
{
  gl_FragData[0] = _Color;
}


#endif

可以看到由於Unity的Shader程式碼中沒有使用紋理取樣,最終生成的GLSL也沒有紋理取樣的變數(被優化掉了)。所以在編譯、使用這個GLSL程式碼的時候也無需給它指定一個紋理單元的變數。不會有紋理記憶體的開銷。 另外,紋理在GPU中視訊記憶體開銷取決於有沒有Shader使用它,並不是所有Shader使用紋理都會造成新的紋理記憶體開銷。例如:ShaderA使用紋理A,之後ShaderB也使用了紋理A,那麼紋理A並不需要上傳到GPU兩次,而是隻需要一次就可以了。

資源管理

Q5:請問Unity3D引擎執行時,如果我們使用的是採用了某個壓縮格式的紋理,則紋理是在載入時解壓並按照點陣圖的RGBA32位的格式儲存在記憶體,還是在記憶體中也是壓縮格式的資料,並在提交渲染時由GPU自行解壓並採樣紋素。為何經常看到文章說GPU硬體支援某些圖片或者視訊流的壓縮格式呢?這個處理流程大概是什麼樣的?

其實這是兩個概念,圖片壓縮和紋理壓縮(術語可能使用的不準確,請大神指正)。jpg和png這種是圖片壓縮,ETC、PVRTC、ASTC這種是紋理壓縮。圖片檔案從硬碟讀取到記憶體,解壓縮成為紋理,再傳給GPU,GPU再進行解壓縮變成具體的RGB色彩值。如果沒有開Read&Write,紋理會從記憶體中解除安裝掉。 感謝凱奧斯@UWA問答社群提供了回答

今天的分享就到這裡。當然,生有涯而知無涯。在漫漫的開發週期中,您看到的這些問題也許都只是冰山一角,我們早已在UWA問答網站上準備了更多的技術話題等你一起來探索和分享。歡迎熱愛進步的你加入,也許你的方法恰能解別人的燃眉之急;而他山之“石”,也能攻你之“玉”。