1. 程式人生 > >[Unity 3D] Unity 3D 效能優化-粒子優化

[Unity 3D] Unity 3D 效能優化-粒子優化

原貼地址:

http://blog.csdn.net/lijing_hi/article/details/11657887

聽到過很多用Unity 3D開發遊戲的程式設計師抱怨引擎效率太低,資源佔用太高,包括我自己在以往專案的開發中也頭疼過。最近終於有了空閒,可以仔細的研究一下該如何優化Unity 3D下的遊戲效能。其實國外有不少有關U3D優化的資料,Unity官方的文件中也有簡略的章節涉及這方面的內容,不過大多都是以優化美術資源為主,比如貼圖的尺寸,模型靜態及動態的batch以減少draw call,用lightmap替代動態光影,不同渲染模式在不同環境下的效能等等。鑑於此,加上美術資源方面的東西本人不是特別瞭解,所以都撇開不談,這裡先試著分析分析U3D指令碼中常用程式碼段的執行效率

GetComponent

這是一個U3D指令碼中使用頻率最高的函式之一,這一族函式包括GetComponent,GetComponents,GetComponentInChildren,GetComponentsInChildren以及他們的泛型版本,此外GameObject類以及Component類上的很多屬性也可以歸於這一範疇,比如Component類的gameObject屬性,GameObject類和Component類都有的transform屬性等等這一系列從GameObject例項以及Component例項上獲取其他掛載的內建元件的屬性介面。

先來看看GetComponent函式的幾種過載形式:

  1. Component GetComponent(Type type);  
  2. T GetComponent<T>() where T : Component;  
  3. Component GetComponent(string type);  

通過ILSpy檢視UnityEngine部分原始碼,發現泛型形式的GetComponent其實不過是在函式體中對泛型型別T呼叫了typeof,然後就直接呼叫了非泛型形式的GetComponent,因此在此不對泛型形式的GetComponent函式做討論。下面設計一個小實驗來看看兩種不同GetComponent函式的效率,以及對GetComponent的不同使用方式會帶來什麼樣的影響:

設計實驗——實驗執行的主要過程是對同一個gameObject連續獲取同一型別的Component 8×1024×1024次,統計不同方法下的時間開銷,單位是毫秒。在實驗用的gameObject上一共掛在了五個各不相同的元件,所有的實驗操作都是獲取這五個元件中的第一個。

方案一,最直接的方式,直接在迴圈中對gameObject呼叫GetComponent(Type type)方法;

方案二,同樣直接的方式,直接在迴圈中對gameObject呼叫GetComponent(string type)方法;

方案三,在迴圈外事先以GetComponent獲取gameObject上的Component並快取引用,然後在迴圈中直接訪問快取的引用;

方案四,利用C#擴充套件方法,對GameObject類新增擴充套件方法,以一個靜態字典Dictionary<GameObject, Component>儲存gameObject和gameObject上要取用的Component的鍵值對,然後在擴充套件方法裡做字典查詢以獲得Component;

實驗結果——方案一約1700ms,方案二約18500ms,方案三約30ms,方案四約1500ms。

(可能有人會對方案四抱有懷疑,擔心字典中gameObject數量會影響查詢效率,雖然我可以直接告訴你正常遊戲裡可能同時存在的GameObject資料量下對字典查詢根本沒有能夠被覺察到的影響,但還是以資料來說明問題:

繼續設計子實驗,針對方案四,調整場景中gameObject的數量,每個gameObject上都掛載上述實驗裡的五個元件,並且都向字典中註冊,對每種gameObject數量的情況都執行上述實驗裡的8×1024×1024次元件訪問。

子實驗結果——1個gameObject時約1500ms,5個gameObject時約1500ms,10個gameObject時約1500ms,100個gameObject約1500ms,1000個gameObject時約1500ms,10000個gameObject時還是約1500ms,此時向字典中註冊所消耗的時間已經遠遠大於之後進行的迴圈的消耗。其實熟悉C#字典表的人根本不會有疑問,字典是散列表,查詢複雜度O(1)。)

由上述實驗可以得出結論,如果要獲取一個gameObject上掛載的某個元件,在邏輯允許或者架構允許的情況下儘量事先快取這個元件的引用,這是最高效的做法,開銷可以忽略不計;假如情況不允許事先快取引用,那麼在呼叫頻率不是很頻繁的情況下可以使用GetComponent<T>()或者GetComponent(Type type)的過載形式;如果確實呼叫比較頻繁,那麼最好是自己對GameObject或者Component類進行擴充套件,以字典查詢代替每次的GetComponent呼叫,畢竟效率稍微高那麼一點點(當然了,如果元件是動態的,那麼這個辦法就不適用了,還是乖乖的用GetComponent);而GetComponent(string type)這個過載如無必要就不要使用,因為它每次呼叫時都必須進行型別反射,以至於效率只有另外兩個過載形式的十分之一不到,即便是隻能以字串的形式得知所需元件的型別,也可以事先手動進行型別反射,而不是在頻繁的GetComponent時直接傳遞字串引數,只有一種情況下不得不使用GetComponent(string type)這個過載形式,那就是:每一次呼叫前都只能以字串的形式的到元件型別,而且每一次呼叫前所獲得到的元件型別是無法預測的,這中情況下手動做型別反射跟直接呼叫GetComponent沒有區別。

看完GetComponent族函式之後,接下來就是GameObject類和Component類內建的元件訪問屬性。

在實際指令碼程式碼編寫中,你是否經常這樣一長串程式碼就輕易寫出來了:

  1. Vector3 pos = gameObject.transform.position;  
  2. gameObject.collider.enabled = false;  

以我們的直覺,GameObject類和Component類所提供的這些屬性應該都是直接訪問的事先快取好的元件引用,因此對這些屬性的使用便無所顧忌。但是事情真的是如我們所想的那樣嗎?如果我告訴你,有時候哪怕是用GetComponent函式的string引數形式都會比使用這些屬性來的要快,你相信麼?還是用實驗資料說話吧。

設計實驗——對某gameObject上的Transform元件,採用不同的方法,訪問8×1024×1024次。

方案一,實現快取gameObject上transform元件的引用,然後所有訪問都直接取用快取的引用;

方案二,在指令碼中直接以Component類的transform屬性呼叫的方式訪問(U3D指令碼都是從MoniBehaviour類派生,而MonoBehaviour又派生自Component類,所以在指令碼中可以直接訪問transform屬性,這一點相信很多人都知道);

方案三,在指令碼中以gameObject.transform的形式訪問元件(注意哦,很多人都有這個習慣,覺得元件是gameObject的元件,所以訪問時都喜歡加上gameObject);

方案四,在指令碼中以GetComponent<Transform>()函式訪問元件;

實驗結果——方案一約30ms,方案二約550ms,方案三約850ms,方案四約1700ms。

吃驚吧?transform屬性訪問的開銷居然比直接訪問引用要大這麼多!而且通過gameObject轉一道手之後開銷居然又增加了這麼多!不過還好,直接屬性呼叫還是比用GetComponent要快的多……別太早下結論,Transform元件在每個GameObject例項上都有,對它的訪問是不會失敗的,那麼如果被訪問的元件在GameObject上不存在的時候呢?比如訪問一個Rigidbody元件,而gameObject上沒有掛載這樣的元件,這時有會怎樣?接著看實驗。

設計實驗——嘗試對某gameObject上的Rigidbody元件進行訪問8×1024×1024次。

方案一,gameObject上確實掛載了Rigidbody元件,事先快取元件的引用,訪問時取用快取的引用;

方案二,gameObject上確實掛載了Rigidbody元件,指令碼中以Component類的rigidbody屬性訪問元件;

方案三,gameObject上確實掛載了Rigidbody元件,指令碼中以gameObject.rigidbody的方式訪問元件;

方案四,gameObject上確實掛載了Rigidbody元件,指令碼中以GetComponent<Rigidbidy>()訪問元件;

方案五,gameObject上沒有Rigidbody元件,事先快取元件(當然獲取到的是null),訪問時取用引用;

方案六,gameObject上沒有Rigidbody元件,指令碼中以Component類的rigidbidy屬性訪問元件;

方案七,gameObject上沒有Rigidbody元件,指令碼中以gameObject.rigidbody方式訪問元件;

方案八,gameObject上沒有Rigidbody元件,指令碼中以GetComponent<Rigidbody>()訪問元件;

實驗結果——方案一約30ms,方案二約800ms,方案三約1200ms,方案四約1700ms,方案五約30ms,方案六不少於60000ms,方案七不少於60000ms,方案八約1700ms。

更吃驚了吧?這一次的實驗,前四組跟上一次實驗差別不太大,但對rigidbody屬性的訪問還是要比transform屬性慢了一點,後四組資料才是吃驚的根源,在元件不存在的情況下,通過屬性訪問元件居然會有如此大的額外開銷!相比之下,GetComponent方法倒是不在乎元件是否真的存在,開銷一如既往。

由於屬性實現的程式碼無法通過ILSpy檢視,所以在這裡我只能用猜的了。首先是,U3D在實現這些元件訪問屬性的時候,必然做了各種查詢和容錯處理,絕非簡單的快取和取用引用那麼簡單,這也是屬性訪問比事先快取引用的訪問方式要慢那麼多的原因;其次,Transform元件在每個GameObject例項上都必然存在,因此transform屬性的實現比其他元件訪問屬性的實現必然要少那麼一些步驟,這就造成對transform屬性的訪問要比其他元件屬性快上一些;最後,當元件不存在時,對元件屬性的訪問應該是走入了大量的容錯處理程式碼,這就造成這種情況下屬性訪問開銷大增。

從這個實驗又可以得出結論,我們的指令碼程式碼裡經常會需要訪問gameObject引用或者某個元件的引用,最好的方式當然是在指令碼Awake的時候就把這些可能訪問的東西都快取下來;如果需要訪問臨時gameObject例項的某屬性或者臨時某元件的gameObject例項,在能夠確保元件一定存在的情況下,可以用屬性訪問,畢竟它們比GetComponent要快上一倍,但是如果不能確定元件是否存在,甚至是需要對元件的存在性做判斷時,一定不要用對屬性訪問結果判空的方式,而要用GetComponent,這裡面節省的開銷不是一點半點。

IsAlive

U3D的粒子系統指令碼介面相信很多人都用過,ParticleSyetem類的一系列介面都有一個bool型別的引數——withChildren,通過這個引數可以直接將相同的判斷或者操作應用到一整個通過Transform父子關係樹關聯起來的ParticleSystem例項集合上。然而,但凡方便的功能,裡面就必然有效能陷阱…… 以IsAlive這個介面為例(用來判斷粒子系統是否所有粒子都已經消亡,一般用在非loop的例子發射器上),看看U3D裡是如何實現這個介面的:
  1. publicbool IsAlive()  
  2. {  
  3.     bool withChildren = true;  
  4.     returnthis.IsAlive(withChildren);  
  5. }  
  1. publicbool IsAlive(bool withChildren)  
  2. {  
  3.     if (withChildren)  
  4.     {  
  5.         ParticleSystem[] particleSystems = ParticleSystem.GetParticleSystems(this);  
  6.         ParticleSystem[] array = particleSystems;  
  7.         for (int i = 0; i < array.Length; i++)  
  8.         {  
  9.             ParticleSystem particleSystem = array[i];  
  10.             if (particleSystem.Internal_IsAlive())  
  11.             {  
  12.                 returntrue;  
  13.             }  
  14.         }  
  15.         returnfalse;  
  16.     }  
  17.     returnthis.Internal_IsAlive();  
  18. }  
可以看到,如果傳遞的withChildren引數為true,那麼函式會先嚐試呼叫GetParticleSystems(this)來獲取包括下級gameObject在內的所有能找得到的粒子系統元件,然後對這些粒子系統元件依次再呼叫IsAlive判斷。而如果withChildren為false,就僅僅會判斷自身。那麼自然,開銷大小與否,關鍵就在GetParticleSystems的實現上了。
  1. internalstatic ParticleSystem[] GetParticleSystems(ParticleSystem root)  
  2. {  
  3.     if (!root)  
  4.     {  
  5.         returnnull;  
  6.     }  
  7.     List<ParticleSystem> list = new List<ParticleSystem>();  
  8.     list.Add(root);  
  9.     ParticleSystem.GetDirectParticleSystemChildrenRecursive(root.transform, list);  
  10.     return list.ToArray();  
  11. }  
  1. privatestaticvoid GetDirectParticleSystemChildrenRecursive(Transform transform, List<ParticleSystem> particleSystems)  
  2. {  
  3.     foreach (Transform transform2 in transform)  
  4.     {  
  5.         ParticleSystem component = transform2.gameObject.GetComponent<ParticleSystem>();  
  6.         if (component != null)  
  7.         {  
  8.             particleSystems.Add(component);  
  9.             ParticleSystem.GetDirectParticleSystemChildrenRecursive(transform2, particleSystems);  
  10.         }  
  11.     }  
  12. }  

U3D對獲取所有下級gameObject例項上的粒子系統元件使用了遞迴的方式,並在遞迴結束後返回結果列表時做了一次列表元素複製(List.ToArray()),並且在獲取粒子系統元件的時候用的是transform2.gameObject.GetComponent<ParticleSystem>(),而不是transform2.GetComponent<ParticleSystem>(),從上一篇文章裡我們已經用實驗證實了,前一種方式開銷更大。看到這裡,我們心裡大概已經有譜了,那就是——效率絕對不會高到哪裡去,影響效能的地方太多了……還是設計一個小實驗來看看這種情況下應該用什麼樣的方式更好吧: 設計實驗——一個兩層結構,一個父gameObject掛載一個ParticleSystem元件,兩個子gameObject分別掛載一個PariticleSystem元件,採用兩種不同的方式對這個組合判斷IsAlive各8×1024×1024次。 方案一,直接對父gameObject上的PariticleSystem呼叫IsAlive(true); 方案二,在迴圈前,先用GetComponentsInChildren將所有的PariticleSystem存入一個List,迴圈中對這個List做遍歷,對List裡每一個ParticleSystem呼叫IsAlive(false); 實驗結果——方案一約3900ms,方案二約65ms。

結果對比很明顯。其實,U3D提供的這個介面的意義在於,當不是需要進行那麼頻繁的呼叫時,可以用IsAlive(true)來省掉手動獲取所有子粒子系統的過程,讓程式碼簡潔一些,雖然U3D目前對這個介面的實現有的地方還值得斟酌。ParticleSystem提供的這一族介面(IsAlive只是其中之一,此外還有Play,Pause,Stop等等),如果使用頻率不是很高,比如僅僅是初始化或者銷燬的時候做一次性呼叫,那麼即便是withChildren引數是true也沒有什麼大不了的,還能少些很多程式碼,何樂而不為;但如果需要頻繁呼叫,比如每幀都對粒子系統集合判斷IsAlive,這種情況下,一定不能懶惰,該寫的東西還是要寫的。另外值得注意的一點,IsAlive這一族介面的無參形式,是預設withChildren為true的,使用的時候可別搞錯了。 PS,留意一下GetDirectParticleSystemRecursive的實現方式,你會發現它有一個遞迴條件,就是節點上必須要有PariticleSystem元件,在遞迴過程中,一旦發現某個節點上沒有ParticleSystem元件時,父子關係樹上的這一枝就算遍歷到頭了,再往下即便是還有ParticleSystem存在也會被忽略。因此,如果你面對的ParticleSystem集合就恰好存在這樣的斷層,那最好還是自己勤快一點,自己動手用GetComonentsInChildren來查詢所有的粒子系統元件。

CenterOfMass

在處理物理碰撞時,尤其是OnTrigger族訊息,由於要手動計算碰撞點會經常要用到碰撞體的質心。獲取質心常用的有三種方式: 1、Collider.bounds.center 2、Collider.rigidbody.worldCenterOfMass 3、Collider.attachedRigidbody.worldCenterOfMass 第一種方式其實是認為碰撞體是均勻幾何體,所以取碰撞盒中心作為質心。注意,這裡的Collider並不是collider屬性,而是表示一個Collider引用,所以不必考慮collider屬性訪問引起的效能開銷(其實在處理碰撞時,Collider引用是能夠直接得到的,也不需要做collider屬性訪問)。 設計實驗——針對同一個碰撞體,分別呼叫以上三種方式各8×1024×1024次 實驗結果——第一種約4500ms,第二種約2500ms,第三種約2000ms。 在碰撞不涉及剛體的情況下,要獲取質心只能使用效能最差的第一種方式。而有剛體的情況下,自然推薦使用第三種方式。

相關推薦

[Unity 3D] Unity 3D 效能優化-粒子優化

原貼地址: http://blog.csdn.net/lijing_hi/article/details/11657887 聽到過很多用Unity 3D開發遊戲的程式設計師抱怨引擎效率太低,資源佔用太高,包括我自己在以往專案的開發中也頭疼過。最近終於有了空閒,可以仔細

unity shader 中的效能優化

1 過量的 shader 會使得載入速度變慢  -- 處理方法  批量處理  合併這些shader  放在一個指令碼中  但是隻有在使用相同的渲染狀態下才可以 在subshader中的pass 過多也

關於Unity中的3D拾取

for screen 畫面 原理 epo per touch date 檢測 3D拾取 3D遊戲實際上看到的是2D畫面,我們在屏幕上點擊,想要找到哪個3D物體,我們實際上是在一個2維平面內做3D拾取。 3D拾取實際上是,當玩家點擊屏幕的時候,會從顯示屏幕的攝像頭發射一條射線

Unity技術支持團隊性能優化經驗分享

當前 via 很多 fps 外部 但是 部分 col 場景 https://mp.weixin.qq.com/s?__biz=MzU5MjQ1NTEwOA==&mid=2247490321&idx=1&sn=f9f34407ee5c5d0d1edb4

Unity優化資源優化之紋理優化

Unity3D引擎處理紋理:不論你匯入的是PNG,PSD還是TGA,它們都會被自動轉換成Unity自己的Texture2D格式。(美術資源規範:貼圖長寬應為4的倍數) 不同平臺的圖集格式選擇方案(TextureImporterFormat): Android:

[Unity小專案]3D畫素跑酷遊戲

FFFFFlipping 1. 遊戲截圖 [待補充]>>>emm錄製gif的時候出錯了… 2. APK下載 3. 怎麼玩? 點鍵help按鈕, 會顯示操作提示 你可以向左跳, 向右

UnityUnity資源池的動態載入釋放和記憶體優化處理

需求環境         在上一級的【解決方案】文章中,我們設計出了動態載入資源的業務流程,而這一節,我們就通過一些簡單的程式碼,來實現出業務流程中的效果。        吸取之前文章的經驗,如

unity優化—資源優化

Texture檔案 ----------------------------------------------------------------------------------------------- 術語“Texture” 和“Sprite”新手在遊戲開發中經常會產生困惑,所以值得區分下,在

unity 手遊 3D人物的移動控制

[RequireComponent(typeof(CharacterController))]public class MoveTest : MonoBehaviour { private Rigidbody myRigidbody; priva

unity如何實現3D物體疊加到攝像頭畫面上

實現效果如下: 功能: 1 開啟攝像頭,攝像頭畫面作為軟體的背景 2 3d物體放在攝像頭畫面之上 具體實現如下: 具體程式碼實現如下: using System.Collections; using Sy

#遊戲unity-VR場景漫遊#遊戲中的優化(一)

基於之前製作專案的經歷教訓——沒有報錯的程式碼合在一起整體執行的時候就執行特別卡,深刻認識到了專案優化的重要性;VR遊戲的流暢度是影響使用者體驗的一個很重要的因素,而且,三區的場景模型也是一個有很多面的模型,執行起來會相當佔資源,為了解決執行卡頓的問題,就必須在

#遊戲unity-VR場景漫遊#遊戲中的優化(二)

這篇文章接著上一篇的內容進行整理。 畫素優化 畫素優化的重點在於減少overdraw。之前提過,overdraw指的就是一個畫素被繪製了多次。關鍵在於控制繪製順序。 Unity還提供了檢視overdraw的檢視,在Scene檢視的Render Mode-&

Unity優化方向——優化Unity遊戲中的垃圾回收

傳遞 必須 compare 消息傳遞 適用於 間歇性 連接 ria dir 介紹 當我們的遊戲運行時,它使用內存來存儲數據。當不再需要該數據時,存儲該數據的內存將被釋放,以便可以重用。垃圾是用來存儲數據但不再使用的內存的術語。垃圾回收是該內存再次可用以進行重用的進程的名稱。

Unity實現在3D模型上塗鴉

Paste_Image.png using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; public class DrawOnModel : Mono

Unityunity中關於遊戲場景的優化——遮擋剔除

遮擋剔除,當一個物體被其他物體遮擋住而不在攝像機的可視範圍內時不對其進行渲染。.遮擋剔除在3D圖形計算中並不是自動進行的。因為在絕大多數情況下離camera最遠的物體首先被渲染,靠近攝像機的物體後渲染並覆蓋先前渲染的物體(這被稱為重複渲染"overdraw").遮擋剔除不

UnityUnity優化總結

優化概括的說可以分為:CPU,GPU,記憶體; 開發上的細節可以分為:資源,引擎,程式碼,著色器; 1.資源方面;         》動態物體;遊戲主角、怪物、NPC等;                    控制面片數量 300 - 2000面片;        

Unity Optimization UNITY優化關註列表

xtu dump name ice dir gpu top map pda 這裏主要羅列Unity引擎進行開發的應用或遊戲,可以進行優化的各個關註點。(此文會持續更新) C# GC alloc Update LateUpdate Serialize String ToS

面向 Unity* 軟件和虛擬現實的優化:運行時生成內容

實的 我們 嘗試 tel zh-cn 介紹 虛擬 面向 content 優化遊戲以實現高性能一直是遊戲開發過程中的一個重要因素。雖然開發人員一直嘗試將硬件推向極致,但當移動遊戲成為主流時,優化技術變得尤為突出。Unity* 軟件、Unreal* 等常見引擎最初都是面向 PC

HTTP2.0,HTTP1.1,HTTP1.0三者在通性效能上的優化方法

本文從從通訊效能角度,來分析對比HTTP1.0和HTTP1.1之間的區別。以及HTTP1.1與HTTP2.0之間的區別。本文詳細內容組織如下 目錄 一丶HTTP1.0與HTTP1.1通訊效能上的區別 持久化連線 管線化技術 二丶HTTP2.0與HTTP1.1通訊效能上的區別 多路

React.js 一次動畫效能的簡單優化

背景 前段時間公司產品為了拉新活動,仿照Facebook的Creators頁面決定製作一套自己的HelpCenter頁面。 Facebook的Creators頁面其實大體上只有兩個功能點: 卡片移入和移出時的動畫效果 左側選單欄跟隨當前卡片變換的效果 這個頁面的重點在於