Unity UI優化小結

Unity UI優化小結

來自專欄司虎虎的專欄45 人贊了文章

最近在做Unity的項目,負責UI相關的工作,學習了一下Unity UGUI更新的原理,以及優化相關的部分。本文主要參考UWA的分享,UWA專註性能優化,感覺有很多值得學習的文章, UWA - 簡單優化、優化簡單 ,打好理論基礎,少走彎路,後面實際項目中就是儘可能去實現這些細節了。

目錄

  • 1.元素更新方式
  • 2.Draw Call合併規則
  • 3.網格更新機制
  • 4.降低界面的渲染開銷
  • 5.降低界面的更新開銷

1.元素更新方式

UGUI

public class VertexHelper:IDisposable{ private List<Vector3> m_Position = ListPool<Vector3>.Get(); private List<Color32> m_Colors = ListPool<Color32>.Get(); private List<Vector2> m_Uv0S = ListPool<Vector2>.Get(); private List<Vector2> m_Uv1S = ListPool<Vector2>.Get(); private List<Vector3> m_Normals = ListPool<Vector3>.Get(); private List<Vector4> m_Tangents = ListPool<Vector4>.Get(); private List<int> m_Indices = ListPool<int>.Get();}

有這樣一個VertexHelper類,和UI元素有一一對應的關係,包含頂點信息,UV,顏色,等等 當UI元素髮生變化的時候,就會從位置,長寬等數組填充這些list。

對製作的影響

當UI發生改變的時候,須要對數組的元素進行更新, 「動態元素」少用Outline,Tiled Sprite 盡量減少「動態」長文本

如上圖Tiled生成了大量網格,在填充的時候耗時更長。 OutLine,是通過把一個四邊形重複5次,畫出的OutLine的效果,會使文本的定點數乘以5,使更新的數組過長。

更新方式

  • UIPanel.LateUpdate
    • 輪詢
    • UIPanel.UpdateWidgets
  • Cavans.SendWillRenderCanvas
    • 隊列
    • m_LayoutRebuildQueue
    • m_GraphicRebuildQueue

NGUI每幀更新UIPanel,輪詢,不管發生變化與否,哪怕是靜態的,還是會有開銷

UGUI更新包含2個隊列,渲染之前在SendWillRenderCanvas的回掉裡面處理2個隊列的元素,如果大量靜態,消耗幾乎為0。

對動態HUD緩存機制的影響

  • NGUI
    • 適量元素:Color.a= 0,移出
    • 大量元素:SetActive(false)
    • Time + 二級緩存
  • UGUI
    • Scale = 0, Alpha Group = 0

如血條,傷害數字,經常會出現消失的UI元素,如果出現就創建,消失就destory,開銷會非常大。所以通常的做法通過緩存,如果通過SetActive有時候會有額外的開銷,

UGUI通常的操作方式可以通過scale = 0 ,或則Alpha Group為0,可以快速隱藏,不要直接alpha = 0 ,在draw call 上是沒變化的,實際上還是畫了個透明度為0的面片。

NGUI中和UGUI相反,如果設置alpha = 0 ,是會把頂點移除掉,可以減少setActive的開銷。

2.DrawCall 合併規則

渲染順序

  • NGUI: Depth
    • 設置depth值,以UIPanel為單位,按照大小進行排序,相同材質進行合併
  • UGUI:hierarchy
    • 重疊檢測
    • 分層合併

存在優勢,也有一些問題,UGUI的合併規則是進行重疊檢測,然後分層合併。下面的例子中,不同顏色代表不同圖集。

第一個圖,4種顏色,左邊和右邊數序相同,藍色是0層,白色都是1層,這樣會分層合批成4個DrawCall。

第二個圖,左邊的藍色是0層,右邊的黑色是0層藍色是1層,這種情況下不會合批,所以會是9個drawCall

第三個圖,把黑色延長到重疊的地方,黑色同處0層, 所以DrawCall又降到了5。

所以在製作UI的時候,須要考慮層級關係,結合UGUI的合批規則,這樣可以達到對drawCall的優化,

調試工具

  • NGUI:DrawCall tool
  • UGUI:Frame debugger

NGUI 可以通過DrawCall tool看到多少個三角面,多少個widgets,通過觀察widgets的關係,對NGUI層級直接調整,來進行合批。

NGUI使用drawcall tool,通過調整index,把相同材質的放在同一層。

UGUI用frame Debug看每個drawcall繪製了哪些東西,再做調整

對界面的影響

  • UGUI
    • 不規則圖標的擺放
    • UI元素的旋轉
    • 動態遮擋
    • 3D UI
  • NGUI
    • 手動排序

UGUI中,對於不規則圖形,視覺上icon沒有重疊,但是UI層是包圍盒的形式,Icon重疊了,UGUI在判斷的時候沒辦法進行合併。

UGUI對於發生旋轉的UI,包圍盒是會發生重疊,會限制UGUI在合併DrawCall的操作。

如下圖:

NGUI把不同的元素設在一個圖集中,進行同批次繪製。

3.網格更新的機制

  • UIPanel.LateUpdate 兩種更新方式
    • UIPanel.FillDrawCall 更新單個DrawCall
    • UIPanel.FillAllDrawCall 更新所有DrawCall
  • Canvas.BuildBatch 更新所有DrawCall
    • WaitingForJob 子線程網格合併
    • PutGeometryJobFence
    • BatchRendere.Flush UI如果開多線程渲染,BatChRender.Flush會增高,主線程在等待子線程的結果時Flush會等待。

NGUI根據不同的DrawCall 合併不同的網格 UGUI以Canvas為單位,一個Canvas下的元素,合併成一個Mesh,不同的UI元素會以SubMeshes的形式存在。UGUI中如果一個Canvas中有很複雜的動態元素,盡量將靜態元素拆分出來,確保更新的效率。

優化方法:

  • UGUI
    • 拆分Canvas
  • NGUI
    • 控制FillAllDrawCalls
    • 拆分UIPanel

性能比較

  • 功能界面的DrawCall控制 NGUI>UGUI (NGUI通過DC樹,通過調整Index進行調整)
  • 功能界面的網格更新機制 NGUI>UGUI (UGUI更新任何一個UI,都會更新整個Canvas)
  • 動態HUD界面的網格更新機制 UGUI>>NGUI (UGUI在處理動態UV的元素,如血條,動態UI會更有優勢)
  • 堆內存控制 UGUI>>NGUI (NGUI堆內存佔用更高)

參考 blog.uwa4d.com/archives

4.降低界面的渲染開銷

  • Profiling 定位
  • DrawCall 控制
  • Mesh.CreateVBO UI變化的網格開銷
  • Overdraw UI比較容易產生Overdraw

Profiling

UGUI 非多線程渲染Unity5.3 主要集中在RenderSubBatch,

DrawCall控制

Z值!=0

合併時只會合併相鄰層級,相同圖集的元素

左邊的圖,4個血條紅色和白色的z值相同,共2個drawcall,但是右邊的圖,紅色和白色穿插,變成8個drawcall,在3D UI的時候尤其明顯,2DUI不要通過這種方法,改Z值,因為2D改了之後,

未「隱藏」 的元素

包含 Null Sprite, Color.a = 0 屏幕外

對於隱藏的元素,NGUI的image組件中,alpha為空和sprite為空,都是佔用drawcall渲染的,而且會打斷前後的drawcall,穿插在上下2個元素中間的時候。

Hierarchy 穿插+重疊

如下圖紅點和Icon在不同圖集中,如果紅點稍微大一點,遮擋了旁邊的Icon,就不能合批,須要調整Icon和紅點的節點關係,4個Icons放在一個節點下,4個紅點放在一個借點下。在同步位置的時候可能稍微麻煩有點,須要寫個腳本同步位置。

圖集分離

可能因為壓縮方式的不同,導致UI的sprite在不同圖集中,也會影響渲染開銷,不同圖集中無法進行合批

OverDraw

  • 減少UI層疊
  • 遮擋場景時,關閉場景相機
  • 不用Image檢測事件

參考: blog.uwa4d.com/archives

5.降低界面的更新開銷

  • 動靜分離
  • 降低更新頻率
  • 避免「敏感」操作
  • 優化選項

動靜分離

在UGUI中細分Canvas 下圖中,血量和經驗條會經常更新,如果在一個canvas中,PutGeometryJbFence和WaitngForJob,buildBatch出現的時候,表示更新的開銷在子線程中,主線程處在一個等待的狀態,差不多有5,6毫秒的等待。

拆分之後剛才的WaitingForJob等都沒有了,動態的canvas開銷就會很小。

降低更新的頻率

  • 設定移動閾值
  • 設定更新頻率

比如像小地圖這樣的界面,可能移動了一小段距離,小地圖上更新了也不明顯,可以通過設定閾值的方法,降低開銷,或者直接設定更新時間。

避免「敏感」操作

  • 元素的Position賦值->Canvas.BuildBatch

下面的一個例子是在Canvas中,所有元素基本是靜態的,但是有個元素,在Update中,會跟隨target的position,每次發送改變的時候,會重建整個canvas,導致資源的浪費。

參考文獻

blog.uwa4d.com/

[《聚爆Implosion》性能精析 UI部分]

UGUI研究院之全面理解圖集與使用


推薦閱讀:

TAG:Unity遊戲引擎 |