轉(zhuǎn) https://zhuanlan.zhihu.com/p/43111806
最近在做Unity的項(xiàng)目,負(fù)責(zé)UI相關(guān)的工作,學(xué)習(xí)了一下Unity UGUI更新的原理,以及優(yōu)化相關(guān)的部分。本文主要參考UWA的分享,UWA專注性能優(yōu)化,感覺有很多值得學(xué)習(xí)的文章, UWA - 簡(jiǎn)單優(yōu)化、優(yōu)化簡(jiǎn)單 ,打好理論基礎(chǔ),少走彎路,后面實(shí)際項(xiàng)目中就是盡可能去實(shí)現(xiàn)這些細(xì)節(jié)了。
目錄
- 1.元素更新方式
- 2.Draw Call合并規(guī)則
- 3.網(wǎng)格更新機(jī)制
- 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();
}
有這樣一個(gè)VertexHelper類,和UI元素有一一對(duì)應(yīng)的關(guān)系,包含頂點(diǎn)信息,UV,顏色,等等 當(dāng)UI元素發(fā)生變化的時(shí)候,就會(huì)從位置,長寬等數(shù)組填充這些list。
對(duì)制作的影響
當(dāng)UI發(fā)生改變的時(shí)候,須要對(duì)數(shù)組的元素進(jìn)行更新, *“動(dòng)態(tài)元素”少用Outline,Tiled Sprite *盡量減少“動(dòng)態(tài)”長文本

如上圖Tiled生成了大量網(wǎng)格,在填充的時(shí)候耗時(shí)更長。 OutLine,是通過把一個(gè)四邊形重復(fù)5次,畫出的OutLine的效果,會(huì)使文本的定點(diǎn)數(shù)乘以5,使更新的數(shù)組過長。
更新方式
UIPanel.LateUpdate
輪詢
UIPanel.UpdateWidgets
Cavans.SendWillRenderCanvas
隊(duì)列
m_LayoutRebuildQueue
m_GraphicRebuildQueue
NGUI每幀更新UIPanel,輪詢,不管發(fā)生變化與否,哪怕是靜態(tài)的,還是會(huì)有開銷
UGUI更新包含2個(gè)隊(duì)列,渲染之前在SendWillRenderCanvas的回掉里面處理2個(gè)隊(duì)列的元素,如果大量靜態(tài),消耗幾乎為0。
對(duì)動(dòng)態(tài)HUD緩存機(jī)制的影響
NGUI
適量元素:Color.a= 0,移出
大量元素:SetActive(false)
Time + 二級(jí)緩存
UGUI
Scale = 0, Alpha Group = 0
如血條,傷害數(shù)字,經(jīng)常會(huì)出現(xiàn)消失的UI元素,如果出現(xiàn)就創(chuàng)建,消失就destory,開銷會(huì)非常大。所以通常的做法通過緩存,如果通過SetActive有時(shí)候會(huì)有額外的開銷,
UGUI通常的操作方式可以通過scale = 0 ,或則Alpha Group為0,可以快速隱藏,不要直接alpha = 0 ,在draw call 上是沒變化的,實(shí)際上還是畫了個(gè)透明度為0的面片。
NGUI中和UGUI相反,如果設(shè)置alpha = 0 ,是會(huì)把頂點(diǎn)移除掉,可以減少setActive的開銷。
2.DrawCall 合并規(guī)則
渲染順序
NGUI: Depth
設(shè)置depth值,以UIPanel為單位,按照大小進(jìn)行排序,相同材質(zhì)進(jìn)行合并
UGUI:hierarchy
重疊檢測(cè)
分層合并
存在優(yōu)勢(shì),也有一些問題,UGUI的合并規(guī)則是進(jìn)行重疊檢測(cè),然后分層合并。下面的例子中,不同顏色代表不同圖集。

第一個(gè)圖,4種顏色,左邊和右邊數(shù)序相同,藍(lán)色是0層,白色都是1層,這樣會(huì)分層合批成4個(gè)DrawCall。
第二個(gè)圖,左邊的藍(lán)色是0層,右邊的黑色是0層藍(lán)色是1層,這種情況下不會(huì)合批,所以會(huì)是9個(gè)drawCall
第三個(gè)圖,把黑色延長到重疊的地方,黑色同處0層, 所以DrawCall又降到了5。
所以在制作UI的時(shí)候,須要考慮層級(jí)關(guān)系,結(jié)合UGUI的合批規(guī)則,這樣可以達(dá)到對(duì)drawCall的優(yōu)化,
調(diào)試工具
- NGUI:DrawCall tool
- UGUI:Frame debugger
NGUI 可以通過DrawCall tool看到多少個(gè)三角面,多少個(gè)widgets,通過觀察widgets的關(guān)系,對(duì)NGUI層級(jí)直接調(diào)整,來進(jìn)行合批。
NGUI使用drawcall tool,通過調(diào)整index,把相同材質(zhì)的放在同一層。
UGUI用frame Debug看每個(gè)drawcall繪制了哪些東西,再做調(diào)整

對(duì)界面的影響
UGUI
不規(guī)則圖標(biāo)的擺放
UI元素的旋轉(zhuǎn)
動(dòng)態(tài)遮擋
3D UI
NGUI
手動(dòng)排序
UGUI中,對(duì)于不規(guī)則圖形,視覺上icon沒有重疊,但是UI層是包圍盒的形式,Icon重疊了,UGUI在判斷的時(shí)候沒辦法進(jìn)行合并。
UGUI對(duì)于發(fā)生旋轉(zhuǎn)的UI,包圍盒是會(huì)發(fā)生重疊,會(huì)限制UGUI在合并DrawCall的操作。
如下圖:

NGUI把不同的元素設(shè)在一個(gè)圖集中,進(jìn)行同批次繪制。
3.網(wǎng)格更新的機(jī)制
UIPanel.LateUpdate 兩種更新方式
UIPanel.FillDrawCall 更新單個(gè)DrawCall
UIPanel.FillAllDrawCall 更新所有DrawCall
Canvas.BuildBatch 更新所有DrawCall
WaitingForJob 子線程網(wǎng)格合并
PutGeometryJobFence
BatchRendere.Flush UI如果開多線程渲染,BatChRender.Flush會(huì)增高,主線程在等待子線程的結(jié)果時(shí)Flush會(huì)等待。
NGUI根據(jù)不同的DrawCall 合并不同的網(wǎng)格 UGUI以Canvas為單位,一個(gè)Canvas下的元素,合并成一個(gè)Mesh,不同的UI元素會(huì)以SubMeshes的形式存在。UGUI中如果一個(gè)Canvas中有很復(fù)雜的動(dòng)態(tài)元素,盡量將靜態(tài)元素拆分出來,確保更新的效率。

優(yōu)化方法:
UGUI
拆分Canvas
NGUI
控制FillAllDrawCalls
拆分UIPanel
性能比較
- 功能界面的DrawCall控制 NGUI>UGUI (NGUI通過DC樹,通過調(diào)整Index進(jìn)行調(diào)整)
- 功能界面的網(wǎng)格更新機(jī)制 NGUI>UGUI (UGUI更新任何一個(gè)UI,都會(huì)更新整個(gè)Canvas)
- 動(dòng)態(tài)HUD界面的網(wǎng)格更新機(jī)制 UGUI>>NGUI (UGUI在處理動(dòng)態(tài)UV的元素,如血條,動(dòng)態(tài)UI會(huì)更有優(yōu)勢(shì))
- 堆內(nèi)存控制 UGUI>>NGUI (NGUI堆內(nèi)存占用更高)
參考 https://blog.uwa4d.com/archives/Implosion.html
4.降低界面的渲染開銷
- Profiling 定位
- DrawCall 控制
- Mesh.CreateVBO UI變化的網(wǎng)格開銷
- Overdraw UI比較容易產(chǎn)生Overdraw
Profiling
UGUI 非多線程渲染Unity5.3 主要集中在RenderSubBatch,

DrawCall控制
Z值!=0
合并時(shí)只會(huì)合并相鄰層級(jí),相同圖集的元素
左邊的圖,4個(gè)血條紅色和白色的z值相同,共2個(gè)drawcall,但是右邊的圖,紅色和白色穿插,變成8個(gè)drawcall,在3D UI的時(shí)候尤其明顯,2DUI不要通過這種方法,改Z值,因?yàn)?D改了之后,

未“隱藏” 的元素
包含 Null Sprite, Color.a = 0 屏幕外
對(duì)于隱藏的元素,NGUI的image組件中,alpha為空和sprite為空,都是占用drawcall渲染的,而且會(huì)打斷前后的drawcall,穿插在上下2個(gè)元素中間的時(shí)候。

Hierarchy 穿插+重疊
如下圖紅點(diǎn)和Icon在不同圖集中,如果紅點(diǎn)稍微大一點(diǎn),遮擋了旁邊的Icon,就不能合批,須要調(diào)整Icon和紅點(diǎn)的節(jié)點(diǎn)關(guān)系,4個(gè)Icons放在一個(gè)節(jié)點(diǎn)下,4個(gè)紅點(diǎn)放在一個(gè)借點(diǎn)下。在同步位置的時(shí)候可能稍微麻煩有點(diǎn),須要寫個(gè)腳本同步位置。


圖集分離
可能因?yàn)閴嚎s方式的不同,導(dǎo)致UI的sprite在不同圖集中,也會(huì)影響渲染開銷,不同圖集中無法進(jìn)行合批

OverDraw
- 減少UI層疊
- 遮擋場(chǎng)景時(shí),關(guān)閉場(chǎng)景相機(jī)
- 不用Image檢測(cè)事件
參考: https://blog.uwa4d.com/archives/video_UI.html
5.降低界面的更新開銷
- 動(dòng)靜分離
- 降低更新頻率
- 避免“敏感”操作
- 優(yōu)化選項(xiàng)
動(dòng)靜分離
在UGUI中細(xì)分Canvas 下圖中,血量和經(jīng)驗(yàn)條會(huì)經(jīng)常更新,如果在一個(gè)canvas中,PutGeometryJbFence和WaitngForJob,buildBatch出現(xiàn)的時(shí)候,表示更新的開銷在子線程中,主線程處在一個(gè)等待的狀態(tài),差不多有5,6毫秒的等待。

拆分之后剛才的WaitingForJob等都沒有了,動(dòng)態(tài)的canvas開銷就會(huì)很小。

降低更新的頻率
- 設(shè)定移動(dòng)閾值
- 設(shè)定更新頻率

比如像小地圖這樣的界面,可能移動(dòng)了一小段距離,小地圖上更新了也不明顯,可以通過設(shè)定閾值的方法,降低開銷,或者直接設(shè)定更新時(shí)間。
避免“敏感”操作
- 元素的Position賦值->Canvas.BuildBatch
下面的一個(gè)例子是在Canvas中,所有元素基本是靜態(tài)的,但是有個(gè)元素,在Update中,會(huì)跟隨target的position,每次發(fā)送改變的時(shí)候,會(huì)重建整個(gè)canvas,導(dǎo)致資源的浪費(fèi)。

參考文獻(xiàn):