76.性能优化-GPU-UI系统优化-更多的Canvas组件
76.1 知识点
Canvas(画布)组件的基本工作原理
任何 UGUI 控件都必须挂在一个 Canvas 下,Unity 才会把它们作为 UI 元素渲染出来。
Canvas 的本质作用包括:
- 收集子节点的 UI 元素(
Graphic)信息。 - 处理渲染顺序(Sorting、Camera、RenderMode)。
- 管理重建,监听子物体的变动,决定哪些需要更新。
- 批量生成 UI 顶点网格、更新材质信息。
- 最终在渲染阶段合批并提交给 GPU。
Canvas 内部维护了一个重建队列。当子元素发生变化时,它会标记自己为“脏”,在本帧渲染前统一执行重建(布局重建 / 图形重建 / 材质重建):
- 布局重建:主要进行布局计算。
- 图形重建:主要进行网格生成(比如合并 UI 元素网格信息,前提是材质相同)。
- 材质重建:主要进行材质状态更新。
所谓“画布污染”,就是有子元素打了脏标记,导致整个 Canvas 的顶点缓存需要刷新。如果 Canvas 很大,就会让大量 UI 元素一并重建,带来性能消耗。
可能带来画布污染的情况包括:
- 修改 UI 元素的
RectTransform相关属性,比如位置、缩放、角度等。 - 自动布局组件的更新,比如
LayoutGroup、ContentSizeFitter等自动布局组件在元素变化时会重新计算。 - 频繁改变 UI 元素父子关系、排列顺序、失活激活等。
- 文本发生变化时、自动调整大小时。
- 更改 UI 元素纹理、材质等。
- 修改 UI 元素颜色、透明度等。
- 禁用/启用遮罩。
- Shader 参数修改,比如文本边缘线、阴影效果等。
简单来说,一个 Canvas 统一管理它下面的所有子 UI 元素,若更新其中一个子元素会让整个 Canvas 进行重建,造成性能消耗。
更多的 Canvas(画布)组件
为了避免 Canvas 重建带来的性能消耗,可以把会频繁变化的 UI 元素单独放在一个小 Canvas 当中,避免一个小元素的更新导致整个大 Canvas 重建。
可以把 UI 元素分成三大类:
- 静态:永远不会改变的 UI 元素,比如背景图、说明文字、Logo、装饰性分割线等。
- 偶尔动态:只会偶尔为做出响应而改变,比如 UI 按钮按下、拖动条、拥有金币数等。
- 持续动态:会持续甚至每帧发生变化的元素,比如战斗中血条流动动画、计时器、秒表文本、摇杆 UI 等。
通过这个分类,可以将 UI 元素进行动静分离,拆分到不同的画布中,让高频变化区域使用独立 Canvas,从而达到节约性能的目的。
举例说明:
- 大 Canvas:500 个 UI(背景 + 血条 + 按钮等),血条变化会导致整个 500 个 UI 重建。
- 拆分后:大 Canvas 管理不变的内容(450 个 UI),小 Canvas 管理变化的血条(50 个 UI)。血条变化时只重建 50 个 UI,大 Canvas 部分缓存复用不会重建,从而性能更好。
注意:Canvas 并不是越多越好,它也存在一些潜在的缺点:
- Canvas 会打断合批处理,会增加 DrawCall。
- Canvas 本身需要进行排序、合批、DrawCall 提交等操作,如果拆得太碎,反而会因为本身的开销带来性能影响。
76.2 知识点代码
Lesson76_性能优化_GPU_UI系统优化_更多的Canvas组件.cs
public class Lesson76_性能优化_GPU_UI系统优化_更多的Canvas组件
{
#region 知识点一 Canvas(画布)组件的基本工作原理
//任何 UGUI 控件都必须挂在一个 Canvas 下
//Unity 才会把它们作为 UI 元素渲染出来
//Canvas的本质作用
//1.收集子节点的 UI 元素(Graphic)信息
//2.处理渲染顺序(Sorting、Camera、RenderMode)
//3.管理重建,监听子物体的变动,决定哪些需要更新
//4.批量生成 UI 顶点网格、更新材质信息
//5.最终在渲染阶段合批并提交给 GPU
//等等
//Canvas内部维护了一个重建队列
//当子元素发生变化时,它会标记自己为"脏"
//在本帧渲染前统一执行重建(布局重建 / 图形重建 / 材质重建)
//布局重建:主要进行布局计算
//图形重建:主要进行网格生成(比如合并UI元素网格信息、前提是材质相同)
//材质重建:主要进行材质状态更新
//所谓"画布污染",就是有子元素打了脏标记,导致整个 Canvas 的顶点缓存需要刷新
//如果 Canvas 很大,就会让大量 UI 元素一并重建,带来性能消耗
//可能带来画布污染的情况
//1.修改UI元素的RectTransform相关属性,比如位置、缩放、角度等
//2.自动布局组件的更新,比如LayoutGroup、ContentSizeFitter等自动布局组件在元素变化时会重新计算
//3.频繁改变UI元素父子关系、排列顺序、失活激活等
//4.文本发生变化时,自动调整大小时
//5.更改UI元素纹理、材质等
//6.修改UI元素颜色、透明度等
//7.禁用启用遮罩
//8.Shader参数修改,比如文本边缘线、阴影效果等
//等等
//说人话:
//一个 Canvas 统一管理它下面的所有子 UI 元素
//若更新其中一个子元素会让整个Canvas进行重建
//造成性能消耗
#endregion
#region 知识点二 更多的Canvas(画布)组件
//为了避免Canvas的重建带来的性能消耗
//我们可以把会频繁变化的UI元素单独放在一个小Canvas当中
//避免一个小元素的更新导致整个大Canvas重建
//我们可以把UI元素分成三大类
//静态:静态UI元素永远不会改变,比如背景图、说明文字、Logo、装饰性分割线等等
//偶尔动态:只会偶尔为了做出响应时而改变,比如UI按钮按下,拖动条,拥有金币数 等等
//持续动态:会持续甚至每帧发生变化的元素,比如战斗中血条流动动画、计时器、秒表文本、摇杆UI等等
//通过这个分类
//我们可以将UI元素进行 动静分离
//将UI元素拆分到不同的画布中
//高频变化区域独立Canvas
//从而达到节约性能的目的
//举例说明:
// 大 Canvas:
// 500 个 UI(背景 + 血条 + 按钮....)
// 血条变化 导致 整个 500 个 UI 重建
// 拆分后:
// 大 Canvas 管理不变的内容(450 个 UI)
// 小 Canvas 管理变化的血条(50 个 UI)
// 血条变化时,只重建 50 个 UI,大Canvas部分缓存复用不会重建
// 从而性能更好
//注意:
//Canvas并不是越多越好
//它也存在一些潜在的缺点
//1.Canvas会打断合批处理,会增加DrawCall
//2.Canvas本身需要进行排序、合批、DrawCall提交等事情
// 如果拆的太碎,反而会因为本身的开销带来性能影响
#endregion
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com