Unreal Open Day 2017 活動上 Epic Games 開發人員支援工程師郭春飆先生為到場的開發人員介紹了在 Unreal Engine 4 中 UI 的最佳化技巧,以下是演講實錄。
大家好,我是 Epic Games 的開發人員支援工程師郭春飆,今天給大家介紹的是 UE4 的 UI 最佳化經驗。我們之前一直有接到國內開發人員的一些抱怨,他們覺得在手機上面開了 UI 以後效能下降的很快,今天就專門給大家介紹一下怎麼在 UE4 上做 UI 最佳化。本文介紹的 UI 最佳化方式,不僅適用於移動平台,在其它平台上(如 PC 和主機)對於複雜的 UI 系統也會有很大的效能提升。
文章目錄:
1 UI 的基本概念
1.1 名詞解釋
1.2 渲染流程
1.3 效能指標
2 最佳化方案
2.1 遊戲線程最佳化
2.1.1 Invalidation Box
2.1.2 可見度(Widget Visibility)
2.1.3 Widget Binding
2.2 渲染線程最佳化
2.2.1 合并批次
2.2.2 Retainer Box
2.2.3 事件驅動的 Retainer Box
2.2.4 切換材質
2.3 其它最佳化
2.3.1 C++ 開發
2.3.2 Manager Class
2.3.3 釋放貼圖記憶體
2.3.4 3D RTT 最佳化
2.3.5 新功能
3 效果測試
4 總結
- UI的基本概念
1.1 名詞解釋
User Widget:對應一個使用者介面。\
Widget Tree:每一個 User Widget 都是儲存成樹狀結構。
Panel Widget:不會渲染出來,用於對 Child Widget 進行布局,如 Canva Panel, Grid Panel, Horizontal Box 等。
Common Widget:用於渲染,會產生到最後的 Draw Elements 中,如 Button, Image, Text 等。
1.2 渲染流程
基本渲染流程示意圖:
在遊戲線程 (Game Thread),Slate Tick 每一幀會遍曆兩次 Widget Tree。
Prepass:從下到上遍曆樹,計算每一個Widget的理想尺寸 (Desired Size)。
OnPaint:從上到下遍曆樹,計算渲染所需的 Draw Elements 。這個過程中,會根據 Common Widget 的類型和參數產生相應的 Vertex Buffer,將 Common Widget 的 Render Transform 計算到 Vertex Buffer 中,並根據 Layer ID 和 Material 等資訊進行批次合并。最後一個 User Widget 會產生1個或多個 Draw Element,並將 Draw Elements 傳遞給渲染線程進行渲染,其中每個 Draw Element 對應一個 Draw Call。
在渲染線程 (Render Thread),Slate 渲染分為兩步:
Widget Render:執行 UI 的 RTT,如果使用了 Retainer Box,這裡會將 Draw Elements 渲染到 Retainer Box 的 Render Target。
Slate Render:將 Draw Elements 渲染到 Back Buffer,如果使用了 Retainer Box,會將 Retainer Box 對應的 Texture Resource 渲染到 Back Buffer。
1.3 效能指標 Stat.Slate命令列舉了一些主要的Slate績效參數:
Num Painted Widgets:在遊戲線程執行 OnPaint 的 Widget 數量。
Num Batches:Draw Element(也即 Draw Call)數量。
Stat.Slate 會建立一個未最佳化的 UI,並且統計線程會將這個 UI 的效能資料算入 Slate 開銷,因此表格中的時間資料和真實資料相差很大。建議通過如下命令查看統計線程變數的時間開銷:
stat dumpave –num=120 –ms=0.5
三個關鍵計量的統計資料分別是:
Slate Tick:統計線程變數 STAT_SlateTickTime。
Slate Render:統計線程變數 STAT_SlateRenderingRTTime。
Widget Render:統計線程變數 FWidgetRenderer_DrawWindow。
如果希望在項目中Just-in-Time 偵錯效能,可以從統計線程直接擷取資料,並做一個簡單的調試面板進行查看。
遊戲線程代碼:
統計線程代碼:
調試面板效果:
2 最佳化方案
2.1 遊戲線程最佳化
2.1.1 Invalidation Box
使用 Invalidation Box 封裝 User Widget,從而緩衝 Slate Tick 資料,不需要每幀都進行計算。操作方式如下所示:
在 Invalidation Box 下的所有 Prepass 和 OnPaint 計算結果都會被緩衝下來。如果某個 Child Widget 的渲染資訊發生變化,就會通知 Invalidation Box 重新計算一次 Prepass 和 OnPaint 更新緩衝資訊。\
下圖示範了一種特殊情況,英雄表徵圖是一個重複使用的 User Widget,每個都被封裝進了 Invalidation Box。整個英雄列表是一個 Scroll Box,當 Scroll Box 上下滑動時,英雄表徵圖對應 User Widget 的 Transform 資訊也會發生變化。
此時可以勾選 Invalidation Box 對應的 Cache Relative Transforms,如下所示:
那麼當 User Widget 的位置變化時,引擎不會去更新所有的 Draw Element(即 Vertex Buffer ),而會通過修改 Shader 參數(View * Projection Matrix)來反應位置變化。這種方式僅適用於位置變化,如果縮放發生變化,仍然需要重新計算 Draw Element。Cache Relative Transforms 會在 Game Thread 增加少量額外的計
Related Article: 翻譯《虛幻引擎4藝術大師 - 藍圖 II 》 中文版