UGUI 性能优化系列:第三篇——渲染与像素填充率优化
UGUI 性能优化系列:第一篇——基础优化与资源管理
UGUI 性能优化系列:第二篇——Canvas 与 UI 元素管理
UGUI 性能优化系列:第三篇——渲染与像素填充率优化
在 Unity UGUI 性能优化之旅中,我们已经学习了基础的资源管理和 Canvas 与 UI 元素的管理。现在,我们将把目光转向更深层次的 渲染层面,特别是如何优化 像素填充率(Pixel Fill Rate)。在这个环节中,Overdraw(过度绘制) 是一个我们必须理解和解决的关键问题,因为它直接关系到 GPU 的工作效率。
一、Overdraw(过度绘制)的危害与检测
1. 什么是 Overdraw?为什么会影响性能?
想象一下你在纸上画画。如果你先画了一个绿色的圆,然后又用一个蓝色的方块完全覆盖了它,最后再用一个黄色的三角形覆盖了方块的一部分。那么在最终的作品上,你只看到了黄色的三角形、蓝色的方块和绿色圆形的未被覆盖部分。但是,在绘制过程中,你实际上在某些区域画了不止一次。
在 GPU 渲染中,Overdraw(过度绘制) 就是指在一个像素点上,GPU 进行了多次绘制操作。当多个 UI 元素重叠时,处于上层的 UI 元素会遮挡下层的 UI 元素,但 GPU 仍然可能需要对被遮挡的像素进行绘制。
危害:
- 增加 GPU 像素填充率压力: GPU 的一个主要工作是填充像素。每次绘制操作都需要 GPU 消耗计算资源来执行像素着色器(Fragment Shader),写入颜色、深度、模板等数据到缓冲区。如果一个像素被绘制多次,GPU 就需要执行多次这些操作,导致不必要的性能开销。
- 增加内存带宽: 每次像素写入都需要占用内存带宽。Overdraw 意味着相同位置的像素被多次写入,从而加剧带宽压力,尤其是在移动设备上,带宽往往是稀缺资源。
- 功耗增加: 更高的 GPU 负载和内存带宽使用会直接导致设备功耗增加,从而缩短电池续航时间,并可能导致设备发热。
- 帧率下降: 当 Overdraw 严重时,GPU 可能会成为渲染瓶颈,导致游戏帧率显著下降。
Overdraw 在 UI 渲染中尤为常见,因为 UI 元素往往是层叠放置的,特别是背景图、面板、文本、图标等常常会发生重叠。
2. 在 Unity 中如何可视化 Overdraw?
Unity 提供了强大的工具来帮助我们检测和可视化 Overdraw,从而找出问题所在。
a. Scene View 中的 Overdraw 模式
这是最直观的检测 Overdraw 的方法。
- 在 Unity 编辑器中,进入 Scene 视图。
- 在 Scene 视图上方的工具栏中,找到
Draw Mode
下拉菜单(通常默认为Shaded
)。 - 点击
Draw Mode
下拉菜单,选择Overdraw
。 - 此时,Scene 视图中的 UI 元素将以不同的颜色显示,颜色越亮(越接近白色),表示该区域的 Overdraw 越严重。
- 蓝色/绿色: 通常表示绘制了一次或两次。
- 黄色/橙色: 表示中等程度的 Overdraw。
- 红色/白色: 表示严重的 Overdraw,这些区域是需要重点优化的地方。
通过这个模式,你可以非常直观地看到哪些 UI 区域存在严重的 Overdraw 问题,并且可以旋转摄像机,检查 3D 场景中的 Overdraw 情况(尽管我们这里主要关注 UGUI)。
b. Unity Profiler 中的 GPU Usage 分析
Profiler 是更深入分析性能问题的工具,可以帮助我们量化 Overdraw 的影响。
- 打开 Unity Editor,选择
Window > Analysis > Profiler
。 - 在 Profiler 窗口中,将
Active Profiler
设置为CPU Usage
,同时确保Deep Profile
(如果需要,但会增加开销)和Record Editor
是勾选的。 - 点击
Add Profiler
按钮,添加GPU Usage
模块。 - 运行你的游戏。
- 在
GPU Usage
模块中,你可以看到UI.Render
相关的耗时。更重要的是,你可以看到Draw Calls
的数量。虽然Draw Calls
增加不一定直接意味着 Overdraw,但过多的 Draw Calls 往往会伴随着 Overdraw。 - 在
GPU Usage
的详细视图中,你可以看到Overdraw
的具体百分比,以及像素填充率的图表。虽然没有像 Scene 视图那样直观的颜色显示,但这里提供了量化的数据,可以帮助你评估优化效果。
c. Frame Debugger
Frame Debugger 是一个强大的工具,可以逐帧地查看渲染过程中的每一个 Draw Call。虽然它不能直接显示 Overdraw 颜色图,但你可以通过它观察 Draw Call 的顺序和内容,从而推断出哪些 Draw Calls 导致了 Overdraw。
- 打开 Unity Editor,选择
Window > Analysis > Frame Debugger
。 - 点击
Enable
按钮。 - 运行游戏。
- 在 Frame Debugger 窗口中,你可以看到当前帧的所有 Draw Call 列表。
- 点击每一个 Draw Call,在 Scene 视图中会高亮显示该 Draw Call 渲染了哪些几何体。通过观察这些高亮显示,你可以看到哪些 UI 元素在不必要地重叠绘制。
- 留意 Draw Call 旁边的
State Change
信息。如果看到频繁的Blend State
变化(例如,从不透明渲染切换到半透明渲染),这也可能是 Overdraw 的信号。
结合使用: 推荐先用 Scene View 的 Overdraw 模式 快速定位问题区域,然后用 Profiler 量化性能影响,最后用 Frame Debugger 深入分析 Draw Call 顺序和状态变化,找出具体的优化点。
二、减少 Overdraw 的策略
减少 Overdraw 的核心思想是:让 GPU 绘制每个像素的次数尽可能少。 这可以通过合理组织 UI 元素、使用裁剪和剔除、优化透明度处理等多种方式来实现。
1. UI 排序与层级
UI 元素的渲染顺序直接影响 Overdraw。Unity 的 UGUI 渲染顺序遵循以下规则(从后往前绘制):
- Canvas 的
Render Mode
:World Space
Canvas 先于Screen Space
Canvas 渲染。- 在
Screen Space
模式下,Screen Space - Camera
Canvas 的渲染顺序由其所引用的摄像机的Depth
属性决定。 Screen Space - Overlay
Canvas 总是最后渲染,位于所有 3D 物体之上。
Canvas
组件的Sorting Layer
和Order in Layer
: 用于控制不同 Canvas 之间的渲染顺序。- Hierarchy 窗口中的顺序: 在同一个 Canvas 下,
GameObject
在 Hierarchy 窗口中的顺序决定了它们的渲染顺序。下层的GameObject
会覆盖上层的GameObject
。
优化建议:
- 不透明 UI 优先,透明 UI 靠后:
- 将所有 不透明的 UI 元素 放在最底层或最靠前的 Canvas 上,并确保它们在 Hierarchy 中的排序也尽可能靠前(即先渲染)。这样,它们可以利用 GPU 的早期深度测试(Early-Z)机制。如果一个像素已经通过深度测试被绘制,那么后续绘制该像素的透明物体可能会被剔除,从而减少 Overdraw。
- 将所有 半透明或透明的 UI 元素 放在不透明 UI 之后渲染。因为半透明物体不能进行 Early-Z,它们需要与后面的物体进行混合,所以必须在不透明物体之后渲染。
- 避免半透明与不透明 UI 元素交错:
- 这是导致 Overdraw 和 Draw Call 增加的常见陷阱。如果一个不透明的按钮被一个半透明的面板覆盖,然后面板又被另一个不透明的图标覆盖,GPU 需要频繁切换渲染状态(不透明 -> 半透明 -> 不透明),每次切换都可能打断合批,并增加 Overdraw。
- 最佳实践:
- 将所有不透明背景、面板等放在一个 Canvas 下,并尽可能先渲染。
- 将所有不透明图标、文本等放在另一个 Canvas 或同一 Canvas 的靠后层级。
- 将所有半透明元素(如渐变、透明蒙版、粒子效果)放在一个独立的 Canvas 下,并确保它在渲染层级上位于所有不透明 Canvas 之后。
- 示例:
- UI Root- Background_Opaque_Canvas (最底层,所有不透明背景)- MainUI_Opaque_Canvas (中间层,所有不透明按钮、图标、文本)- Overlay_Transparent_Canvas (最高层,所有半透明特效、提示、弹窗背景)
2. 合理裁剪与剔除
裁剪(Clipping)和剔除(Culling)是减少不必要绘制的关键手段,尤其对于大型可滚动列表。
a. Rect Mask 2D
或 Mask
组件
- 功能: 这两个组件用于裁剪子 UI 元素,使其只在父级定义的矩形区域内可见。
- 原理:
Mask
组件通过在渲染管线中修改模板缓冲区(Stencil Buffer)来实现裁剪。只有模板缓冲区中对应像素的值符合条件,像素才会被绘制。 - 优点: 完美实现 UI 裁剪,例如滚动视图中的内容。
- 缺点:
- 打破合批:
Mask
组件会修改渲染状态,导致其内部和外部的 UI 元素无法合批。即使是Rect Mask 2D
也会引入额外的 Draw Call。 - 可能增加 Overdraw: 尽管裁剪了显示范围,但对于被裁剪的子元素,其 Mesh 仍然可能被生成并传递给 Shader,只是最终的像素被模板测试剔除。这意味着 GPU 仍然可能进行一部分工作。
- 打破合批:
- 优化建议:
- 减少 Mask 使用: 除非绝对必要(例如滚动视图),尽量避免使用
Mask
组件。 - 限制 Mask 范围: 尽量缩小
Mask
的作用范围,不要让一个Mask
影响过多的 UI 元素。 - 替代方案: 对于简单的图片裁剪,可以考虑使用
Image
组件的Type
设置为Filled
或Sliced
。Image.Type = Filled
:可以将图片按比例填充(圆形、水平、垂直等),实现裁剪效果。Image.Type = Sliced
:用于九宫格切图,可以拉伸中间部分而保持四角不变,常用于按钮背景、面板。这两种类型本身不会引入Mask
的额外 Draw Call。
- 减少 Mask 使用: 除非绝对必要(例如滚动视图),尽量避免使用
b. 剔除屏幕外 UI 元素
对于大型滚动列表,除了 Rect Mask 2D
提供的裁剪,我们还可以通过代码来 手动剔除(Culling) 那些完全不在屏幕或视口范围内的 UI 列表项。
- 原理: 列表中的所有子项即使被
Rect Mask 2D
裁剪了,它们的GameObject
和组件仍然是激活的,仍然可能产生布局计算和 Mesh 生成的开销。手动剔除是指将那些完全移出可见区域的列表项GameObject.SetActive(false)
,并将其放回对象池。 - 实现方式:
- 滚动视图优化组件: 许多 UI 框架或插件(如
EnhancedScroller
,Loop ScrollRect
)都实现了这种虚拟化列表(Virtual Scroll List)的功能。它们只创建和维护可见区域内的少量列表项,并复用这些项来显示数据。 - 手动实现:
- 监听
ScrollRect
的onValueChanged
事件。 - 在事件回调中,遍历所有列表项,判断它们的
Rect Transform
是否与ScrollRect
的viewport
矩形区域有交集。 - 如果一个列表项完全超出
viewport
范围,就将其SetActive(false)
并放回对象池。 - 当新的数据进入可见范围时,从对象池中获取对象并
SetActive(true)
。
- 监听
- 滚动视图优化组件: 许多 UI 框架或插件(如
- 优势:
- 减少 CPU 负载: 被禁用的
GameObject
不会参与布局计算和渲染。 - 减少 Draw Call 和 Overdraw: 不可见的 UI 元素不会被绘制。
- 节省内存: 活跃的对象数量减少。
- 减少 CPU 负载: 被禁用的
- 适用场景: 聊天列表、背包、商店列表、排行榜等包含大量可滚动元素的 UI。
3. 合并透明与不透明 UI
正如前面提到的,透明和不透明对象的渲染方式是不同的。GPU 通常会先渲染所有不透明对象,然后渲染所有半透明对象。
- 不透明对象: 可以利用深度缓冲进行 Early-Z 剔除,即在像素着色器执行之前,如果该像素已经被更近的物体覆盖,就直接跳过绘制。这能有效减少 Overdraw。
- 半透明对象: 必须进行混合(Blending)操作,需要读取现有颜色和深度,然后计算新的颜色,再写入。这意味着它们不能利用 Early-Z 剔除,因此必须从后往前绘制(从距离摄像机最远到最近)。
优化建议:
- 物理分离: 在 Canvas 层面,将不透明的 UI 元素和半透明的 UI 元素放在不同的 Canvas 上。
- 逻辑分离: 如果必须在同一个 Canvas 上,尽量将不透明元素排列在 Hierarchy 的前面(先渲染),半透明元素排列在后面(后渲染)。
- 减少半透明元素数量: 尽量避免使用过多的半透明 UI 元素,特别是那些层层叠加的半透明效果。如果可能,将多个半透明背景图合并成一张具有合适透明度的背景图。
- 选择合适的透明度处理:
- 完全不透明: 如果 UI 元素不需要任何透明度,确保其图片的 Alpha 通道是完全不透明的,或者使用 RGB 格式的图片。这样 GPU 就可以将其视为不透明对象进行处理。
- 完全透明(Alpha Test): 对于一些只有完全透明或完全不透明两种状态的图片(例如,镂空的图标),可以尝试使用 Alpha Test 而不是 Alpha Blending。Alpha Test 通过设置一个阈值,像素的 Alpha 值低于阈值则完全丢弃,高于阈值则完全绘制。这使得 GPU 可以利用 Early-Z,从而减少 Overdraw。在 Shader 中设置
Blend Off
和AlphaTest Greater 0.5
可以实现。不过 UGUI 默认 Shader 并不直接支持 Alpha Test,可能需要自定义 Shader。 - Canvas Group Alpha: 前面提到,
Canvas Group
的alpha
属性通常不会触发 Canvas 重建。然而,它仍然会改变渲染状态,导致所有子 UI 元素都以半透明模式渲染,从而可能增加 Overdraw。因此,只有在需要淡入淡出效果时才使用它。
4. 像素完美(Pixel Perfect)的重要性
Pixel Perfect
是 UGUI 中一个经常被忽视的属性,但它对 UI 渲染质量和性能都有影响。
-
什么是 Pixel Perfect?
当一个 UI 元素的原始像素与屏幕上的渲染像素精确对齐时,就是 Pixel Perfect。如果不对齐,Unity 可能会进行抗锯齿处理(Anti-aliasing)或模糊处理,以使图像看起来更平滑。 -
为什么重要?
- 渲染质量: Pixel Perfect 的 UI 看起来更清晰、更锐利,没有模糊或锯齿感。
- Overdraw 影响: 如果 UI 元素没有做到 Pixel Perfect,GPU 为了平滑边缘可能会绘制更多的像素,或者导致在子像素级别上的多次绘制,从而增加 Overdraw。同时,非整数坐标和尺寸也可能影响合批效率。
- 字体渲染: 对于字体,非 Pixel Perfect 的渲染会使文字边缘模糊,可读性下降。
-
如何实现 Pixel Perfect?
- Canvas Scaler 设置:
UI Scale Mode
: 选择Scale With Screen Size
,并设置一个Reference Resolution
(参考分辨率)。Screen Match Mode
: 选择Expand
或Shrink
,或者Match Width Or Height
,根据你的适配策略来选择。Reference Pixels Per Unit
: 默认是 100。在导入 Sprite 时,也需要确保其Pixels Per Unit
与这个值匹配,这样Image
组件的尺寸单位才能与原始像素对应。
Canvas
组件的Pixel Perfect
属性:- 在
Canvas
组件的 Inspector 窗口中,勾选Pixel Perfect
复选框。 - 当勾选
Pixel Perfect
时,Unity 会尝试调整所有子 UI 元素的Rect Transform
坐标和尺寸,使它们的像素与屏幕像素对齐。这可以消除由于非整数坐标导致的模糊和潜在的 Overdraw。 - 注意: 勾选
Pixel Perfect
可能会导致 UI 元素的位置和大小与设计图有微小偏差,在某些情况下可能会导致 UI 抖动或闪烁(通常是由于频繁的四舍五入操作)。因此,需要根据实际情况权衡。对于静态、不动的 UI 元素,这是一个很好的优化。对于频繁移动的 UI 元素,可能需要测试其副作用。
- 在
- 原始图片尺寸:
- 确保你的 UI 原始图片尺寸是合理的,并且在 UI 中以其最佳尺寸显示。例如,一个 64x64 的图标,最好在 UI 中也以 64x64 的像素尺寸显示。
- 避免缩放: 尽量避免对
Image
或Text
组件进行过度的缩放。如果需要不同大小的同一张图片,考虑提供不同分辨率的图片或使用九宫格切图。
- Canvas Scaler 设置:
三、Graphic Raycaster 的优化
Graphic Raycaster
是 UGUI 事件系统用来检测 UI 交互的关键组件。它的性能开销也需要被关注。
1. Graphic Raycaster
的原理与性能开销
Graphic Raycaster
挂载在 Canvas 上,负责将输入事件(如鼠标点击、触摸)转换为射线检测,并判断射线是否击中了 Canvas 上的可交互 UI 元素(如 Button
, Toggle
, ScrollRect
等)。
- 工作原理: 每当有输入事件发生时,
Graphic Raycaster
会从事件点发出一条射线。然后,它会遍历 Canvas 上所有设置为Raycast Target
的 UI 元素(通常是Image
和Text
组件),判断射线是否与这些元素的Rect Transform
相交,如果相交,还会进一步判断是否与 UI 元素的实际像素(如果有透明像素)相交。 - 性能开销:
- 遍历
Raycast Target
:Graphic Raycaster
需要遍历所有Raycast Target
的 UI 元素,这在 UI 元素数量庞大时会带来可观的 CPU 开销。 - 像素级检测: 如果
Image
或Text
的Raycast Target
被勾选,并且它们是透明图片,Graphic Raycaster
还会进行像素级的 Alpha 测试,以判断射线是否击中了非透明像素。这会增加额外的计算量。
- 遍历
2. 如何通过 Ignore Reversed Graphics
和 Blocking Objects
等属性进行优化
Graphic Raycaster
提供了一些属性来帮助我们优化性能。
a. Ignore Reversed Graphics
- 功能: 勾选此项后,
Graphic Raycaster
将忽略那些被反向绘制的 UI 元素。当 UI 元素被旋转 180 度或被负缩放时,它们的“正面”可能会朝向相反方向,此时它们仍然会拦截射线。勾选此项可以避免这种情况,减少不必要的射线检测。 - 优化建议: 通常情况下,我们希望 UI 元素正面朝向摄像机并响应事件。因此,建议勾选此项,以提高射线检测效率。
b. Blocking Objects
- 功能: 这个属性用于控制
Graphic Raycaster
是否会与 3D 对象或 2DCollider
发生阻塞。None
:不与任何 3D/2D 对象阻塞。2D Physics
:射线会与 2DCollider
发生阻塞。3D Physics
:射线会与 3DCollider
发生阻塞。All
:同时与 2D 和 3DCollider
阻塞。
- 优化建议:
- 根据需求设置: 如果你的 UI 是
Screen Space - Overlay
模式,并且不与游戏世界中的 3D/2D 对象交互,那么将Blocking Objects
设置为None
。这可以避免不必要的物理射线检测,减少开销。 - 谨慎使用
All
: 如果 UI 需要与 3D/2D 对象交互,例如游戏世界中的 UI 按钮,那么才需要设置Blocking Objects
为All
或相应类型。
- 根据需求设置: 如果你的 UI 是
3. 避免在不需要交互的 UI 上添加 Graphic Raycaster
Graphic Raycaster
应该只挂载在需要接收输入的 Canvas 上。 如果一个 Canvas 只是用于显示静态信息或背景,它就不需要Graphic Raycaster
。Raycast Target
属性:Image
和Text
组件都有一个Raycast Target
复选框。默认情况下是勾选的。- 重要优化: 对于那些不需要交互的
Image
和Text
组件,务必取消勾选Raycast Target
。- 例如:背景图、图标、纯文本显示(如血条上的数字,只显示不点击)、装饰性图片。
- 理由: 每多一个勾选了
Raycast Target
的 UI 元素,Graphic Raycaster
在每次输入事件发生时就需要多遍历一个元素,进行射线检测。取消勾选可以显著减少遍历开销。 - 注意: 如果一个父级
GameObject
的Image
或Text
是Raycast Target
,而其子级是可交互组件(如Button
),那么子级依然可以接收事件。但如果父级不需要拦截射线,就取消其Raycast Target
。
示例:
一个复杂的聊天界面,包含了背景图、很多聊天气泡(每个气泡有背景图和文本),以及输入框和发送按钮。
- 聊天界面的背景图:
Image
的Raycast Target
取消勾选。 - 每个聊天气泡的背景图:
Image
的Raycast Target
取消勾选。 - 聊天气泡内的文本:
Text
的Raycast Target
取消勾选。 - 输入框的
InputField
和发送按钮的Button
:它们会默认勾选Raycast Target
,这是正确的。
通过这种方式,可以大大减少 Graphic Raycaster
的遍历范围,从而优化 UI 交互的性能。
四、减少 Mesh 顶点和三角形
尽管 UGUI 自动生成 Mesh,但我们可以通过一些方式来控制 Mesh 的复杂度,从而降低 GPU 的渲染负担。
1. Image
组件的 Type
选择
Image
组件的 Type
属性会影响其生成的 Mesh 顶点数量。
Simple
:- 最简单的类型,生成一个四边形 Mesh (4 顶点, 2 三角形)。
- 优化建议: 除非需要特殊的图片处理(如九宫格、填充),否则优先使用
Simple
类型。
Sliced
(九宫格):- 用于九宫格切图,可以拉伸中间部分而保持四角不变。
- 会生成更多顶点 (通常 16 顶点, 18 三角形),因为需要额外的顶点来定义九宫格的边界。
- 优化建议: 仅在确实需要九宫格拉伸效果时使用。如果只是简单的背景,且不需要拉伸,使用
Simple
类型。
Tiled
(平铺):- 用于将图片平铺填充
Rect Transform
区域。 - 会根据平铺数量生成大量顶点。
- 优化建议: 除非 UI 界面需要图片平铺效果,否则避免使用。对于大部分情况,可以通过美术制作平铺好的大图来替代。
- 用于将图片平铺填充
Filled
(填充):- 将图片按百分比填充,可用于进度条、冷却时间显示等。
- 生成的 Mesh 顶点数量取决于填充类型(径向、水平、垂直)和填充量。
- 优化建议: 仅在需要填充效果时使用。
核心思想: 选择最简单的 Type
类型,以减少不必要的顶点生成。
2. 避免不必要的 Mask
组件
再次强调 Mask
组件(包括 Rect Mask 2D
):
- 问题:
Mask
会导致其内部的 UI 元素 Mesh 生成后,再通过模板测试进行裁剪。这意味着即使被裁剪掉的像素,其 Mesh 仍然被生成和上传。 - 优化建议:
- 如果只是圆角或不规则形状的裁剪: 考虑在美术资源阶段就将图片制作成带 Alpha 透明通道的圆角或不规则形状,而不是在运行时使用
Mask
组件。 - 重新评估需求: 某些设计效果是否可以通过更简单的 UI 布局或图片处理来实现,而不是通过
Mask
。 - 裁剪图片本身: 如果图片内容需要裁剪,可以在美术工具中直接裁剪图片,然后导入 Unity。
- 如果只是圆角或不规则形状的裁剪: 考虑在美术资源阶段就将图片制作成带 Alpha 透明通道的圆角或不规则形状,而不是在运行时使用
3. 使用 UI Builder
等工具优化 UI 布局和 Mesh
Unity 提供了 UI Builder
工具来创建 UXML 和 USS 布局,类似于 Web 开发中的 HTML/CSS。
-
优势:
- 声明式 UI: 更易于组织和管理复杂 UI 布局。
- 解耦: UI 布局与逻辑分离。
- 潜在优化:
UI Builder
生成的 UI 可能在底层优化方面做得更好,例如自动减少冗余的GameObject
和Rect Transform
嵌套。 - 可视化编辑: 可以更直观地调整布局,观察效果。
-
如何帮助优化 Mesh:
- 通过 UXML/USS 的布局系统,可以更精确地控制元素的尺寸和位置,从而避免不必要的
Rect Transform
变化。 - 虽然
UI Builder
本身并不直接优化 UGUI 的 Mesh 生成,但它提供了一种更结构化的方式来构建 UI,这有助于开发者避免手动构建 UI 时可能犯的导致 Mesh 复杂化的错误。
- 通过 UXML/USS 的布局系统,可以更精确地控制元素的尺寸和位置,从而避免不必要的
注意: UI Builder
主要用于 UI Toolkit
系统,与传统的 UGUI (Canvas, Image, Text) 并不是完全相同的技术栈。虽然你可以用 UI Toolkit
来实现游戏 UI,但目前 UGUI 仍然是 Unity 游戏 UI 的主流选择。这里提到 UI Builder
更多是作为未来发展方向的参考,或者针对一些特定工具界面的开发。对于纯 UGUI 项目,重点还是在于前面提到的针对 Canvas 和组件的优化。
五、利用 GPU Instancing 减少 Draw Calls (特定场景)
虽然 UGUI 默认不直接支持 GPU Instancing
,但对于某些重复性高、渲染状态相同的 UI 元素(例如大量相同的血条、地图上的标记),如果能够将它们转化为 World Space
UI,并使用自定义 Shader 支持 GPU Instancing
,就可以大幅减少 Draw Calls。
- 原理:
GPU Instancing
允许 GPU 一次性渲染多个相同的 Mesh,但每个 Mesh 可以有不同的位置、旋转、缩放或颜色等属性。这大大减少了 CPU 到 GPU 的 Draw Call 数量。 - UGUI 适用性:
- 限制:
Screen Space - Overlay
和Screen Space - Camera
模式下的 UGUI 默认不支持GPU Instancing
。 - 适用场景: 主要适用于
World Space
的 UI。例如,在 3D 世界中,所有怪物头顶的血条,或者地图上大量的固定标记点。如果这些 UI 元素的 Mesh 结构相同,并且可以通过Material Property Block
来传递位置、颜色等少量差异数据,就可以考虑GPU Instancing
。
- 限制:
- 实现方式:
- 创建一个
World Space
Canvas。 - 将需要 Instancing 的 UI 元素(例如一个血条的背景和填充)做成预制件。
- 创建一个自定义 Shader,并确保其
Render Queue
是正确的(例如Transparent
)。 - 在 Shader 中启用
GPU Instancing
(#pragma instancing_options
for
_Graphics
)。 - 在 C# 代码中,通过
Graphics.DrawMeshInstanced
或Graphics.DrawMeshInstancedIndirect
来批量渲染这些 UI 元素的 Mesh,而不是通过Image
组件。这需要更底层的图形编程知识。
- 创建一个
- 复杂性: 这是一种高级优化手段,需要对 Shader 编程和图形渲染管线有深入理解。对于大部分 UGUI 应用来说,通过 Canvas 分层、图集和剔除等方式已经能达到很好的优化效果,不一定需要用到
GPU Instancing
。但如果你遇到了海量同类 UI 元素(数百上千个)的 Draw Call 瓶颈,这会是一个非常强大的解决方案。
六、总结与展望
本篇文章深入探讨了 UGUI 渲染层面的优化,特别是如何对抗 Overdraw(过度绘制) 这一 GPU 性能杀手:
- 我们学会了如何使用 Scene View 的 Overdraw 模式、Unity Profiler 的 GPU Usage 和 Frame Debugger 来检测和可视化 Overdraw 问题。
- 掌握了通过 UI 排序与层级 优化渲染顺序,优先渲染不透明 UI,并避免透明与不透明 UI 元素交错。
- 理解了 裁剪与剔除 的重要性,包括
Rect Mask 2D
的优缺点以及如何通过手动剔除屏幕外 UI 元素来优化滚动列表。 - 认识到 像素完美(Pixel Perfect) 对于渲染质量和潜在 Overdraw 优化的影响。
- 学习了如何优化
Graphic Raycaster
,包括合理设置Ignore Reversed Graphics
和Blocking Objects
,以及最重要的是 取消不需要交互的 UI 元素的Raycast Target
属性。 - 探讨了如何通过
Image
组件的Type
选择 和 避免不必要的Mask
组件 来减少 Mesh 的顶点和三角形数量。 - 最后,我们触及了 GPU Instancing 这一高级优化手段,虽然它对 UGUI 的适用场景有限,但在特定情况下可以带来巨大的 Draw Call 优化。
通过本篇的学习,我们现在应该能够更精细地控制 UGUI 的渲染过程,减少不必要的 GPU 负载,从而大幅提升游戏的帧率和流畅度。
在最终章,我们将涵盖一些更高级的优化技巧,如动画、Shader、内存优化,以及性能分析工具的深度使用和编码实践的最佳实践。
UGUI 性能优化系列:第一篇——基础优化与资源管理
UGUI 性能优化系列:第二篇——Canvas 与 UI 元素管理
UGUI 性能优化系列:第三篇——渲染与像素填充率优化