76.合理使用更多的Canvas组件

76.性能优化-GPU-UI系统优化-更多的Canvas组件


76.1 知识点

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. 自动布局组件的更新,比如 LayoutGroupContentSizeFitter 等自动布局组件在元素变化时会重新计算。
  3. 频繁改变 UI 元素父子关系、排列顺序、失活激活等。
  4. 文本发生变化时、自动调整大小时。
  5. 更改 UI 元素纹理、材质等。
  6. 修改 UI 元素颜色、透明度等。
  7. 禁用/启用遮罩。
  8. 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 并不是越多越好,它也存在一些潜在的缺点:

  1. Canvas 会打断合批处理,会增加 DrawCall。
  2. 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

×

喜欢就点赞,疼爱就打赏