Unity UI优化(一) - Unity UI的基本原理

发表于2018-09-03
评论5 1.08w浏览

Unity UI优化(一) - Unity UI的基本原理

英文原文:https://unity3d.com/cn/learn/tutorials/topics/best-practices/fundamentals-unity-ui

Unity的UI系统由几个不同的部分组成。这篇文章首先介绍了几个在后面会使用到的术语,然后对UI系统的一些关键部分的底层行为进行了讨论。

术语

画布(Canvas) 是以原生代码编写的Unity组件,它给Unity的渲染系统提供按层划分的几何系统,可以在其内部或其上层绘制其他几何形状。

画布负责将其内部的几何形状合并到批处理、生成合适的渲染指令并发送到Unity图形系统。这些操作都由原生C++代码完成,这被称为 重新批处理(rebatch) 或 批处理构建(batch build) 。当一个画布被标记为含有需要重新批处理的几何形状时,称这个画布为 脏(dirty) 画布。

由 CanvasRenderer 组件向画布提供几何形状。

子画布(Sub-canvas) 是嵌套在其他画布组件内部的画布组件。子画布能够将其孩子节点与其父画布隔离开,一个被标记为脏的子节点不会迫使其父画布重新构建几何内容,反之亦然。有几种特殊情况会使上述情形失效,比如,改变父画布导致子画布改变尺寸。

Graphic 类是由Unity UI系统的C#库提供的基类,所有的向画布系统提供可绘制几何内容的UI系统C#类都继承它。大多数内置的UI系统绘图类都是通过 MaskableGraphic 子类实现的,这个子类实现了 IMaskable 接口,可以被遮罩。Drawable类的主要子类是 Image 和 Text ,它们能提供与其名称相对应的内容。

Layout 组件控制RectTransform的尺寸和位置,它通常用于创建具有复杂布局并且内部组件需要相对尺寸或者相对位置的UI。Layout组件只依赖RectTransform并且只影响与其关联的RectTransform的属性。他们不依赖Graphic类,并且可以独立于UI系统的Graphic类使用。

Graphic和Layout组件都依赖 CanvasUpdateRegistry 类,该类没有在Unity编辑器中公开。这个类跟踪那些需要进行更新的Layout组件和Graphic组件集合,并在与其相关的画布调用 willRenderCanvases 事件时根据需要触发更新(update)。

Layout和Graphic组件的更新称为 重建(rebuild) 。关于重建过程的进一步详述会在下文中出现。

渲染细节

当使用UI系统构建用户界面时,要时刻注意——所有的几何形状都会在透明队列(Transparent queue)中绘制。也就是说,由UI系统生成的几何形状都带有Alpha混合,从后向前地绘制。站在性能地角度上,需要记住地一件重要的事情是,从多边形栅格化而得到的每个像素都会被采样,即使它们被其他不透明多边形完全遮盖。在移动设备上,这种高层重绘(overdraw)会迅速超出GPU的填充率(fill-rate)容量。

批处理构建(batch building)过程(画布)

在批处理构建过程中,画布合并用于表示UI元素的网格(mesh),生成合适的渲染指令发送到Unity的绘图管线。这一过程的结果会被缓存并重用,直到画布被标记为脏画布。脏画布会在画布的任一网格构成成员发送改变时产生。

画布所使用的网格是从附加到画布的CanvasRenderer组件集合中获取的,但其中不会包括子画布中的组件。

计算批处理需要根据深度(depth)对网格进行排序、检查网格的重叠、共享材质等情况。这个操作是多线程的,因此在不用的CPU架构上性能差异很大,尤其是在移动版Soc芯片(通常CPU核心数少)和现代桌面CPU(通常有4个或更多核心)之间。

重建过程(Graphics)

重建过程中进行了Graphic组件中的Layout和网格的重新计算,这一过程在 CanvasUpdateRegistry 类中执行。CanvasUpdateRegistry是一个C#类,它的源代码可以在Unity’s Bitbucket上查看。

在 CanvasUpdateRegistry 值得注意的方法是 PerformUpdate 。这个方法会在画布组件调用 WillRenderCanvases 事件时被调用。这个事件每帧调用一次。

PerformUpdate 会进行3步处理:

  • 脏Layout组件需要通过 ICanvasElement.Rebuild 方法重建它们的布局(layout)。
  • 所有已注册的裁剪组件(例如Mask)都需要剔除全部被裁减的组件,由ClippingRegistry.Cull方法完成。
  • 脏的Graphic组件需要重建它们的图形元素。

Layout和Graphic的重建过程会被拆分成多个部分。Layout重建分3步完成(PreLayout,Layout和PostLayout),Graphic重建分2步完成(PreRender和LatePreRender)。

Layout重建

必须根据Layout层级顺序计算那些包含在Layout中的组件的位置和尺寸。在Game Object层级中,离根节点近的Layout有可能会改变嵌套在在它里面的Layout的位置和尺寸,所以它需要被先计算。

为此,UI系统依据Layout在层级中的深度对脏Layout列表中的Layout进行排序,高层的(例如,父Transform更少)的项会被移动到列表的前面。

排序后的Layout组件列表接下来要重建布局。这时被Layout组件控制的UI元素的位置和尺寸会发生改变。有关Layout如何影响每个元素的位置的详细叙述,请查看Unity手册中的UI Auto Layout

Graphic重建

当Graphic组件重建时,UI系统将控制传递给ICanvasElement接口的Rebuild方法。Graphic类实现了这一方法并且在Rebuild过程的PreRender阶段执行两个不同的重建步骤。

  • 如果顶点数据被标记为脏数据(例如,组件的RectTransform改变尺寸),网格会重建。
  • 如果材质数据被标记为脏数据(例如,组件的材质或纹理改变),所附加的CanvasRenderer的材质会被更新。

Graphic重建不通过任何特定顺序的图形组件列表进行,也不需要进行任何排序操作。


Unity UI优化教程

二、填充率,画布和输入

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引