轉載
http://wetest.qq.com/lab/view/315.html
移動平臺硬件架構
移動平臺無論是Android 還是 IOS 用的都是統一內存架構,GPU和CPU共享一個物理內存,通常我們有“顯存”和“內存”兩種叫法,可以認為是這塊物理內存的所有者不同,當這段映射到cpu,就是通常意義上的內存;當映射到gpu,就是通常意義上的顯存。并且同一段物理內存同一時刻只會映射到一個device。
即使是在同一物理內存上 ,之前的openGL ES規范中CPU和GPU之間的內存是不能共享的,vertex和texture的buffer是需要拷貝的。后面出來的vulkan 與IOS的metal 可以共享內存。
了解了移動平臺的硬件架構,就知道了 1)CPU 2) 帶寬 3) GPU 4) 內存 都有可能成為移動平臺3D應用性能瓶頸。
二
移動平臺3D應用的畫面渲染過程
1、CPU通過調用繪制命令(稱為一次Draw Call)來告訴GPU開始進行一個渲染過程的。一個Draw Call命令會指向本次繪制需要渲染的信息,這些信息包括:頂點數據、紋理數據、shader參數(光照模型、法線方向、光照方向等)等,簡單地說就 畫什么,用什么畫,怎么畫。
2、GPU接收到Draw Call命令之后就會開始進行一次單元渲染,關于GPU的單元渲染的過程是這樣的(簡單示意圖):
1)從顯存中取出拷貝的頂點數據和光照模型。
2)通過頂點處理器(Vertex Processor)對頂點數據進行一系列的變換和光照處理,包括裁剪處理。tips: 簡單的想想,游戲中的各個物體的坐標都是參照游戲中的世界坐標系的,而實際顯示的畫面是玩家視角或者攝像機視角,這中間就有許多坐標系的轉換。這些活就需要頂點處理器來做,最終我們得到了我們所需要視角的畫面。
3)到這一步,畫面還只是一些多邊形,而實際顯示在屏幕上的是一個個像素,這里就需要(光柵處理器)Rasterizer進行光柵化(Rasterization),從而將畫面變成一個像素圖,把所有的頂點對應到一個一個的像素位置。
4)對這些像素進行上色,通過片元處理器(Fragment Processor)中的像素著色器(Pixel Shader)按照shader光照模型,根據紋理對應位置顏色,計算元顏色,再經過深度計算、透明測試計算出每個像素的最終顏色。
5)把結果輸出到圖像緩存中,全部完成后拿去顯示。
三
Unity3D應用性能優化之CPU
CPU的優化非常重要,CPU的表現直接決定了VR應用的幀率,應用的耗電量,發熱量。我們來看看相比于普通的app,VR應用的CPU都承擔了什么責任:a、業務邏輯 b、網絡通信 c、I/O操作 d、drawcall e、physic邏輯 f、GC內存回收 g、垂直同步等待。
業務邏輯、網絡通信、I/O操作
這一塊的優化和普通的app差不多。
關于業務邏輯:有些不同的是Unity腳本中有一類update方法(Update、FixedUpdate、OnGUI等),這一類方法是在每幀刷新的時候調用的,是比較影響每幀耗時的,為了嚴格控制這一部分的執行時間,需要注意的以下幾點:a、盡量不要再Update函數中做復雜計算,如有需要,可以隔N幀計算一次,對于純數學計算,可以開辟新線程來計算(Unity 為什么一般避免使用多線程, 實際上大多數游戲引擎也都是單線程的, 因為大多數游戲引擎是主循環結構, 邏輯更新和畫面更新的時間點要求有確定性, 如果在邏輯更新和畫面更新中引入多線程, 就需要做同步而這加大了游戲的開發難度。UnityEngine絕大多數類是不支持子線程的,所以一般只有純數學計算才會用到子線程去計算。)
b、關閉所有在update類中執行log的打印操作(Unity中一次log打印有時長達7ms,Profiler數據)。
c、不在update類方法調用Getcomponent、SendMessage、FindWithTag這幾個耗時較長的方法。
d、不在update類方法中使用臨時變量。關于網絡通信、I/O操作:這些普通app的優化和注意點沒有什么很大區別,有一點是,Unity工程中使用了資源動態加載,有些資源是保存在服務器端的,在有必要的時候才會通過網絡load下去加載。這個資源動態加載需要注意一個問題:由于網絡通信過程,CPU總是處于等待的狀態,一般資源下載是多線程同時操作,為了盡快上屏顯示資源(在這個工程中是一些圖片和英雄的3D模型),但是資源有可能是在同一個幀周期中下載完畢的,如果直接加載的話,可能會出現Camera瞬時渲染過多三角形面,造成渲染時間(Camera.Render()函數執行時間)過長,,卡頓的現象。所以這里要注意,網絡下載可以多線程多任務同時下載,但是在Unity主線程,要避免出現同時加載大型模型和大紋理的情況,最好使用隊列的方式,保證一幀只渲染一個3D模型。
關于GC
為什么要把GC放在CPU這一部分?雖然GC是用來處理內存回收的,但是卻增加了CPU的開銷(GC一次開銷可長可短,有時長達100ms)。因此對于GC的優化目標就是盡量少的觸發GC。
首先我們要知道所謂的GC是Mono運行時的機制,而非Unity3D游戲引擎的機制,所以GC也主要是針對Mono的對象來說的,而它管理的也是Mono的托管堆。 明白了這一點,你也就明白了GC不是用來處理引擎的Assets(貼圖,音效,模型等等)的內存釋放的,因為U3D引擎也有自己的內存堆而不是和Mono一起使用所謂的托管堆。其次我們還要清楚什么東西會被分配到托管堆上?對,就是引用類型。引用類型包括:用戶自定義的類,接口,委托,數組,字符串,Object.而值類型包括:幾種基本數據類型(如:int,float,bool等),結構體,枚舉,空類型。所以GC的優化也就是代碼的優化。****
那么GC什么時候會觸發呢?兩種情況:a、當我們的堆的內存不足時,會自動調用GC來回收內存。b、手動的調用GC,用System.GC.Collect(),一般情況下,不建議手動去手動進行內存回收,因為容易出現問題。
檢查整個工程代碼,關于減少GC這一方面的優化經驗總結大概如下:1、字符串連接的處理。因為將兩個字符串連接的過程,其實是生成一個新的字符串的過程。而之前的舊的字符串自然而然就成為了垃圾。而作為引用類型的字符串,其空間是在堆上分配的,被棄置的舊的字符串的空間會被GC當做垃圾回收,可以使用StringBuilder來解決(注意:C#沒有StringBuffer,Java里才有!!String 在進行運算時(如賦值、拼接等)會產生一個新的實例,而 StringBuilder 則不會。所以在大量字符串拼接或頻繁對某一字符串進行操作時最好使用 StringBuilder,不要使用 String)。
2、盡量不要使用foreach,而是使用for。foreach其實會涉及到迭代器的使用,而據傳說每一次循環所產生的迭代器會帶來24 Bytes的垃圾。那么循環10次就是240Bytes。
3、不要直接訪問gameobject的tag屬性。比如if (go.tag ==“human”)最好換成if (go.CompareTag (“human”))。因為訪問物體的tag屬性(每次Object.name也會分配39B的堆內存.)會在堆上額外的分配空間。如果在循環中這么處理,留下的垃圾就可想而知了。
4、不要實例化(Instantiate)和(Destroy)對象,事先建好對象池,以實現空間的重復利用。
5、在某些可能的情況下,可以使用結構(struct)來代替類(class)。這是因為,結構變量主要存放在棧區而非堆區。因為棧的分配較快,并且不調用垃圾回收操作,所以當結構變量比較小時可以提升程序的運行性能。但是當結構體較大時,雖然它仍可避免分配/回收的開銷,而它由于"傳值"操作也會導致單獨的開銷,實際上它可能比等效對象類的效率還要低。所以要注意選擇。
6、場景切換時,可以主動進行垃圾回收(調用System.GC.Collect()),從而及時去除游戲中已經不必要地內存占用。
Draw Call 的優化
前面說過了,DrawCall是CPU調用底層圖形接口的操作。比如有上千個物體,每一個的渲染都需要去調用一次底層接口,而每一次的調用CPU都需要做很多工作,那么CPU必然不堪重負。但是對于GPU來說,圖形處理的工作量是一樣的。
我們先來看看Draw Call對CPU的消耗大概是一個什么級別的量:NVIDIA 在 GDC 曾提出,25K batchs/sec 會吃滿 1GHz 的 CPU,100%的使用率。有一個公式可以和清楚得計算出在給定的CPU資源 與 幀率的情況下,最多能有多少個DrawCall。DrawCall_Num = 25K * CPU_Frame * CPU_Percentage / Framerate。DrawCall_Num : DrawCall數量CPU_Frame : CPU 工作頻率(GHz單位)CPU_Percentage:CPU 分配在DrawCall這件事情上的時間率(百分比)
Framerate:希望的游戲幀率
比如說我們使用一個高通820,工作頻率在2GHz上,分配10%的CPU時間給DrawCall上,并且我們VR要求60幀,那么一幀最多能有83個DrawCall(由于雙camera的存在,單眼DrawCall只能保證在41個以內)。其實,google官方的建議是單眼DrawCall不多于50個。
所以對DrawCall的優化,主要就是為了盡量解放CPU在調用圖形接口上的開銷。所以針對drawcall我們主要的思路就是每個物體盡量減少渲染次數,多個物體最好一起渲染。那么DrawCall次數的優化有哪些方案呢?
DC Batching(DC批處理)batch即批處理,DrawCall batching即DC的批處理,即把多次DrawCall合并成一次DrawCall的方案。
Dynamic Batching 動態批處理Unity引擎對于使用相同材質的物體會自動進行批處理,相同材質意味著shader完全一樣,這一部分主要是要注意那些破壞這一特性的人為因素,比如說:1、批處理動態物體需要在每個頂點上進行一定的開銷,所以動態批處理僅支持小于900頂點的網格物體,如果你的著色器使用頂點位置,法線和UV值三種屬性,那么你只能批處理300頂點以下的物體(如果在這基礎上還使用了UV2,則只能批處理180頂點以下的物體);請注意:屬性數量的限制可能會在將來進行改變。
2、使用不同的縮放比例的物體,unity將無法對這些物體進行批處理。比如(1,1,1)和(1,2,2)就不會動態批處理,但是(1,1,1)和(2,2,2)會動態批處理。
3、擁有lightmap的物體含有額外(隱藏)的材質屬性,比如:lightmap的偏移和縮放系數等。所以,擁有lightmap的物體將不會進行批處理(除非他們指向lightmap的同一部分)。接受實時陰影的物體也不會批處理。
4、多通道的shader會中斷批處理操作(為了達到特殊的渲染目的,可能某個物體要多遍渲染.這是就要多個通道)。
5、在腳本中動態地指定了物體的材質,也不會進行批處理。
Static Batching 靜態批處理動態批處理雖然是自動的,但是限制非常多,不小心就會打破批處理,所以unity在專業版中還提供了靜態批處理,靜態批處理要求是想批處理的物體一定是static的,靜態的,不會改變位置和旋轉角度以及縮放的,且必須材質一致。其原理是把物體的網格進行合并,變成一個靜態的更大的網格物體,再使用一個統一的材質進行渲染。
知道了它的原理,它的某些坑就比較清晰了:1、在一個平行光、環境光下,沒有問題,但是如果你使用了多個平行光,點光源,聚光燈這種復雜的光源去照射物體,那么靜態批處理就會被打斷。(項目中就遇到過,因為兩邊有兩排英雄模型,所以場景中使用了兩個不同平行光,場景中勾選的static物體并沒有被合并drawcall,經過一番折磨才找到原因)。
2、如果靜態批處理前有一些物體共享了相同的網格,那么每一個物體都會有一個該網格的復制品(本來unity只會保留一份,但是靜態批處理會生成新的一個大網格,所以會保留所有物體的網格,最后合并),即一個網格會變成多個網格被發送給GPU。這樣會造成內存的使用變大,需要注意這個問題,但是一般場景中使用相同網格的物體會比較少。
3、對于那些shader相同,紋理不同導致的不同材質無法進行批處理的物體(比如項目中的場景環境,基座,地面,其實都使用了unity自帶的standard shader)可以通過紋理合并的方法來使得它們可以被靜態批處理。這就引發了下面的事情:**BUS總線帶寬 CPU完成一次DrawCall,除了需要發一個DrawCall的命令之外,還需要把內存中頂點數據、紋理貼圖、shader參數通過bus總線拷貝到內存分配給GPU的顯存之中,注意這是拷貝,不是指針傳遞,速度不快。如果一次drawcall傳遞的數據過大,帶寬成為了瓶頸,那就會大大影響效率(其它的DrawCall無法出發,GPU又處于閑置)。這種情況最有可能出現在為了減少DrawCall,瘋狂的合并紋理上。在項目中,UI的DrawCall調用占了很大一部分,也會最難優化的,為了減少drawcall ,我們把UI模塊的靜態部分(一些UI的底板,背景等不會發生變化的)全部合并成了一個紋理,最后導致了DrawCall下降了,但是幀率卻也下降了,內存使用也增加了,原因就是這個。在項目中,不會同時出現的元素不要打包到一起,保證單張合并紋理不大于10241024一般就不會有問題了(王者榮耀最大紋理限制在了256256)。
DrawCall的優化大概就是這些,優化的目標其實是往一個目標上靠,cpu的DrawCall命令剛剛好能被GPU消化,不要讓CPU等待(帶寬限制),也不要讓GPU閑置。如果即使做到了這個,應用幀率還是上不去,那么就只能去削減場景,做有損優化了。
Physics
Unity內置NVIDIA PhysX物理引擎,來模擬物理世界的一些效果,比如說重力、阻力、彈性、碰撞這些,其中使用了一些內置的組件來實現這些模擬,用的比較多的如:剛體(Rigidbody) 各種碰撞器(Collider) 恒力 (Constant Force) 物理材質(Physic Material)鉸鏈關節(Hinge Joint)彈簧關節(Spring Joint)。
unity除了提供了一些重要的組件之外,在unity腳本中的生命周期中提供了一個專門為物理計算的刷新方法:FixedUpdate()。FixedUpdate跟Update的區別在于,這兩個函數處于不同的“幀循環”中,FixedUpdate處于Physics循環中,而Update不是。所以這兩個函數的使用也有了不同。Update的執行受場景GameObject的渲染的影響,三角形的數量越多,渲染所需要的時間也就越長。FixedUpate的執行則不受這些影響。所以,Update每個渲染幀之間的間隔是不相等的,而Fixedupdate在每個渲染幀之間的時間間隔是相等的。由于關系到物理模擬,所以一般涉及到物理組件,都需要放在Fixedupdate中進行計算。那么關于physics,一般的優化手段都有哪些呢?下面是一些經驗及總結:
1、將物理模擬時間步間隔設置到合適的大小。 Fixed Timestep是和物理計算有關的,所以若計算的頻率太高,自然會影響到CPU的開銷。同時,若計算頻率達不到軟件設計時的要求,有會影響到功能的實現,所以如何抉擇需要具體分析,選擇一個合適的值,一般大于16ms,小于30ms。可以通過Edit->Project Settings->Time來改變這個值。
2、謹慎使用網格碰撞器(Mesh Collider),過于消耗性能,一般使用更簡單的碰撞器,或者使用基本幾何碰撞器合并的組合碰撞器。在這個項目中,把所有的網格碰撞體都拋棄了,都換成了box collider。
3、真實的物理(剛體)很消耗,不要輕易使用,盡量使用自己的代碼(數學計算)模仿假的物理。
4、最小化碰撞檢測請求(例如ray casts和sphere checks),盡量從每次檢查中獲得更多信息。項目中涉及到物體的組件很少,關于physic的優化肯定還有很多可以說的,需要再去學習了。
VSync
簡單地說,這是CPU優化的最直接的一個方法。
科普:VSync垂直同步又稱場同步(Vertical Hold),垂直同步信號決定了CRT從屏幕頂部畫到底部,再返回原始位置的時間。從CRT顯示器的顯示原理來看,單個像素組成了水平掃描線,水平掃描線在垂直方向的堆積形成了完整的畫面。顯示器的刷新率受顯卡DAC控制,顯卡DAC完成一幀的掃描后就會產生一個垂直同步信號(決定于屏幕的刷新率)。我們平時所說的打開垂直同步指的是將該信號送入顯卡3D圖形處理部分,從而讓顯卡在生成3D圖形時受垂直同步信號的制約(注意是制約)。
如果我們選擇等待垂直同步信號(也就是我們平時所說的垂直同步打開),那么在游戲中或許強勁的顯卡迅速的繪制完一屏的圖像,但是沒有垂直同步信號的到達,顯卡無法繪制下一屏,只有等垂直同步的信號到達,才可以繪制。這樣FPS自然要受到操作系統刷新率運行值的制約。而如果我們選擇不等待垂直同步信號(也就是我們平時所說的關閉垂直同步),那么游戲中作完一屏畫面,顯卡和顯示器無需等待垂直同步信號就可以開始下一屏圖像的繪制,自然可以完全發揮顯卡的實力。但是不要忘記,正是因為垂直同步的存在,才能使得游戲進程和顯示器刷新率同步,使得畫面更加平滑和穩定。
取消了垂直同步信號,固然可以換來更快的幀率,但是在圖像的連續性上勢必打折扣。
四
Unity3D應用性能優化之GPU
一般人說DC的優化占了unity3D軟件優化的三分天下,那么GPU的優化也占了三分天下。在了解GPU優化都有哪些著手點之前,我們先了解一下GPU在3D軟件渲染中做了啥事:
[圖片上傳中。。。(10)]
頂點著色器
GPU接收頂點數據作為輸入傳遞給頂點著色器。頂點著色器的處理單元是頂點,輸入進來的每個頂點都會調用一次頂點著色器。(頂點著色器本身不可以創建或銷毀任何頂點,并無法得到頂點與頂點之間的關系)。頂點著色器是完全可編程的,它主要完成的工作有:坐標變換和逐頂點光照。 坐標變換:就是對頂點的坐標進行某種變換—把頂點坐標從模型空間轉換到齊次裁剪空間。頂點的多少直接決定了三角形面的多少,也直接決定了GPU的渲染流水線的工作量,所以減少頂點數是一個比較重要的優化點。那么減少頂點怎么操作呢,又有哪些途徑?1、優化基本幾何體3D軟件都是從模型制作開始,在設計師建模的時候就要想到應該盡可能地減少頂點數,一些對于模型沒有影響、或是肉眼非常難察覺到區別的頂點都要盡可能去掉。比如在項目中,對于用戶背后的環境模型,一些樹木和石頭,視頻背面永遠無法看見的神廟,能削減的都已經削減了。
2、使用LOD(Level of detail)技術LOD技術有點類似于Mipmap技術,不同的是,LOD是對模型建立了一個模型金字塔,根據攝像機距離對象的遠近,選擇使用不同精度的模型。它的好處是可以在適當的時候大量減少需要繪制的頂點數目。它的缺點同樣是需要占用更多的內存,而且如果沒有調整好距離的話,可能會造成模擬的突變。
3、使用遮擋剔除(Occlusion culling)技術遮擋剔除是用來消除躲在其他物件后面看不到的物件,這代表資源不會浪費在計算那些看不到的頂點上,進而提升性能。剛才神廟后面的剔除就屬于手動的遮擋剔除。
遮擋剔除是一個PRO版才有的功能, 當一個物體被其他物體遮擋住而不在攝像機的可視范圍內時不對其進行渲染。遮擋剔除在3D圖形計算中并不是自動進行的。因為在絕大多數情況下離 camera 最遠的物體首先被渲染,靠近攝像機的物體后渲染并覆蓋先前渲染的物體(這被稱為重復渲染"overdraw"). 遮擋剔除不同于視錐體剔除. 視錐體剔除只是不渲染攝像機視角范圍外的物體而對于被其他物體遮擋但依然在視角范圍內的物體,則不會被剔除. 注意當你使用遮擋剔除時,視錐體剔除(Frustum Culling)依然有效。
中間操作
1、曲面細分著色器:是一個可選的著色器,主要用于細分圖元。
2、幾何著色器:是一個可選的著色器,可用于執行逐圖元的著色操作,或者被用于產生更多的圖元。
3、裁剪:這一階段是可配置的。目的是把那些不在視野內的頂點裁剪掉,并剔除某些三角形圖元的面片。部分在視野內的圖元需要做裁剪處理,在裁剪邊緣產生新的頂點和三角形進行處理。
4、屏幕映射:這一階段是可配置和編程的,負責把每個圖元的坐標(三維坐標系)轉換成屏幕坐標(二維坐標系)。
5、三角形設置:開始進入光柵化階段,不再是數學上點了,而會把所有的點都映射到屏幕的具體像素坐標上,計算每條邊上的像素坐標而得到三角形邊界的表示方式即為三角形設置。
6、三角形遍歷:這一階段會檢查每個像素是否被一個三角風格所覆蓋。如果覆蓋的話,就會生成一個片元(一個片元并不是真正意義上的像素,而是包含了很多狀態的集合,這些狀態用于計算每個像素的最終顏色。
這些狀態包括了屏幕坐標、深度信息,及從幾何階段輸出的頂點信息,如法線和紋理坐標等。),這樣一個查找哪些像素被三角形覆蓋的過程就是三角形遍歷。
片元著色器
片元著色器的輸入就是上一階段對頂點信息插值得到的結果,更具體點說,是根據從頂點著色器中輸出的數據插值得到的。而這一階段的輸出是一個或者多個顏色值。這一階段可以完成很多重要的渲染技術,如紋理采樣,但是它的局限在于,它僅可以影響單個片元。片元著色器是比較花時間的,因為它是最終顏色的計算者,在某些情況下,例如復雜燈光環境下,片元著色器會出現GPU流水線主要的拖后腿的存在。為了讓片元著色器的計算更加快,我們需要從很多方面進行提前的優化:****1、盡量減少overdraw片元著色器最容易拖后腿的情況就是,overdraw!和Android app的開發一樣,就是同一個像素點繪制了多次,某些情況會造成計算力的浪費,增加耗電量。前面提到的遮擋剔除有減少overdraw非常有用。在PC上,資源無限,為了得到最準確的渲染結果,繪制順序可能是從后往前繪制不透明物體,然后再繪制透明物體進行混合。但是在移動平臺上,對于不透明物體,我們可以設置從前往后繪制,對于有透明通道的物體(很多UI紋理就是含有透明通道的),再設置從后往前繪制。unity中shader設置為“Geometry” 隊列的對象總是從前往后繪制的,而其他固定隊(如“Transparent”“Overla”等)的物體,則都是從后往前繪制的。這意味這,我們可以盡量把物體的隊列設置為“Geometry” 。對于GUI,尤其要注意和設計師商量,能用不透明的設計就用不透明的,對于粒子效果,也要注意不要引入透明值,多半情況下,移動平臺的粒子效果透明值沒有作用。
2、減少實時光照移動平臺的最大敵人。一個場景里如果包含了三個逐像素的點光源,而且使用了逐像素的shader,那么很有可能將Draw Calls提高了三倍,同時也會增加overdraws。這是因為,對于逐像素的光源來說,被這些光源照亮的物體要被再渲染一次。更糟糕的是,無論是動態批處理還是動態批處理(其實文檔中只提到了對動態批處理的影響,但不知道為什么實驗結果對靜態批處理也沒有用),對于這種逐像素的pass都無法進行批處理,也就是說,它們會中斷批處理。
所以當你需要光照效果時,可以使用Lightmaps,提前烘焙好,提前把場景中的光照信息存儲在一張光照紋理中,然后在運行時刻只需要根據紋理采樣得到光照信息即可。當你需要金屬性強(鏡面)的效果,可以使用Light Probes。當你需要一束光的時候,可以使用體積光去模擬這個效果。
3、不要使用動態陰影動態陰影很酷,但是對于片元著色器來說是災難,陰影計算是三角投影計算,非常耗性能。如果想要陰影,可以使用 a、簡單的使用一個帶陰影的貼圖 b、烘焙場景,拿到lightmaps c、創建投影生成器的方法 d、使用ShadowMap的方法(目前還沒有研究)。
4、盡量使用簡單的shadera、建議盡量實用Unity自帶mobile版本的(built-in)Shader,這些大大提高了頂點處理的性能。當然也會有一些限制。b、自己寫的shader請注意復雜操作符計算,類似pow,exp,log,cos,sin,tan等都是很耗時的計算,最多只用一次在每個像素點的計算,還有有些除法運算盡量該能乘法運算等。c、避免透明度測試著色器,因為這個非常耗時,使用透明度混合的版本來代替。d、浮點類型運算:精度越低的浮點計算越快。e、不要在Shader中添加不必要的Pass.
五
Unity3D應用性能優化之內存
unity中有兩類內存,一個是Mono托管的內存(相當于DVM的內存),一個是Unity3D使用的資源類類型的內存(Texture、Mesh這種)。
Mono內存
1、盡量不要動態的Instantiate和Destroy Object,使用Object Pool。
2、不要動態的產生字符串,使用字符串的直接拼接,使用System.Text.StringBuilder代替。
3、Cache一些東西,在update里面盡量避免search,如GameObject.FindWithTag("")、GetComponent這樣的調用,可以在Start中預先存起來。
4、盡量減少函數調用棧,用x = (x > 0 ? x : -x);代替x = Mathf.Abs(x)。
5、定時重復處理用 InvokeRepeating 函數實現。
6、減少GetComponent的調用,使用 GetComponent或內置組件訪問器會產生明顯的開銷。您可以通過一次獲取組件的引用來避免開銷,并將該引用分配給一個變量(transform用的最多)。
7、使用內置數組,內置數組是非常快的。ArrayList或Array類很容易使用,你能輕易添加元件。但是他們有完全不同的速度。 內置數組有固定長度,并且大多時候你會事先知道最大長度然后填充它。內置數組最好的一點是他們直接嵌入結構數據類型在一個緊密的緩存里,而不需要任何額外 類型信息或其他開銷。因此,在緩存中遍歷它是非常容易的,因為每個元素都是對齊的。
Unity3D類的內存
這類內存包括****1、AssetBundleUnity3D 里有兩種動態加載機制:一個是Resources.Load,另外一個通過AssetBundle,其實兩者區別不大。 Resources.Load就是從一個缺省打進程序包里的AssetBundle里加載資源,而一般AssetBundle文件需要你自己創建,運行時 動態加載,可以指定路徑和來源的。
AssetBundle運行時加載:
(1)來自文件就用CreateFromFile(注意這種方法只能用于standalone程序,就不提了)。
(2)也可以來自Memory,用CreateFromMemory(byte[]),這個byte[]可以來自文件讀取的緩沖,www的下載或者其他可能的方式。其實WWW的assetBundle就是內部數據讀取完后自動創建了一個assetBundle而已,Create完以后,等于把硬盤或者網絡的一個文件讀到內存一個區域,這時候只是個AssetBundle內存鏡像數據塊,還沒有Assets的概念。
下圖是AssetBundle的加載卸載示意圖:
AssetBundle是如何加載的呢?用AssetBundle.Load(同Resources.Load) 這才會從AssetBundle的內存鏡像里讀取并創建一個Asset對象,創建Asset對象同時也會分配相應內存用于存放(反序列化)。異步讀取用AssetBundle.LoadAsync,也可以一次讀取多個用AssetBundle.LoadAll。
AssetBundle如何釋放呢?
AssetBundle.Unload(flase)是釋放AssetBundle文件的內存鏡像,不包含Load創建的Asset內存對象。
AssetBundle.Unload(true)是釋放那個AssetBundle文件內存鏡像和并銷毀所有用Load創建的Asset內存對象。
2、Texture對于IOS選擇使用 PVRTC壓縮格式的,對于Android選擇ETC壓縮格式的,紋理可以節省大量內存和讀取速度快,但是會有所降低圖像的質量。
2D紋理如果沒有必要不要使用mimap(會約增加33%的內存開銷),曾經在IOS上吃過虧。3D模型的紋理一般是需要mimap的,但是如果確定了3D模型距離攝像機的距離,在GPU分析器上確定了unity使用的紋理,就可以保留,關閉mimap(比如項目中的avatar)。
**3.Mesh **有Mesh合并和Mesh壓縮(坑比較多,不建議使用)。
4.Particle粒子效果只要記住使用之后及時釋放銷毀就行。