UGUI学习 - 画布刷新、重建
学习博客:
https://blog.csdn.net/qq_28820675/article/details/105746002
https://blog.csdn.net/gaojinjingg/article/details/103565840
更好的入门文章:
https://zhuanlan.zhihu.com/p/448293298
由Canvas控制,通过 ICanvasElement 接口,使用脏标记方法来统一更新CanvasElement
扫盲
摘自大佬博客。
- Canvas, 是Unity渲染系统给层状几何体( layered geometry )提供的可以被画入、被放在上面或者放在世界空间的底层Unity组件。Canvas负责将它包含的几何体组合成batch,生成合适的渲染命令发送给Unity图形系统。这个过程在底层的C++代码中完成,这个过程被称为一次rebatch或者一次batch build。当一个Canvas被标记为包含需要rebatch的几何体时,这个Canvas被认为是dirty的。
- layered geometry , 由Canvas Renderer组件提供给Canvas。[ Canvas 负责进行渲染, Canvas Renderer负责采集/接收. ]
- **动静隔离 **, 一个子Canvas仅仅是一个嵌套在父Canvas中的组件,子Canvas将它的子物体和它的父Canvas隔离,一个子Canvas下dirty的子物体不会触发父Canvas的rebuild,反之亦然。(这些在某些特殊情况下是不确定的,比如说改变父Canvas的大小导致子Canvas的大小改变。)
- Graphic , 是UGUI的C#库提供的一个基类。它是UGUI所有类的基类,给所有的UGUI类提供可以画在Canvas系统上的几何图形。大多数Unity内置的继承Graphic的类都是通过继承一个叫MaskableGraphic的子类来实现,这使得他们可以通过IMaskable接口来被隐藏。Drawable类的子类主要是image和text,已经提供了同名的组件。
- Layout , 组件控制着RectTransform的大小和位置,经常被用于要生成具有相似的大小和位置关系内容的复杂布局。它只依靠RectTransform,只影响与其相关的RectTransform的属性。这些layout组件不依赖于Graphic类,可以独立于UGUI的Graphic组件之外使用。
- CanvasUpdateRegistry , Graphic和Layout组件都依赖于CanvasUpdateRegistry类,它不会在Unity编辑器的界面中显示。这个类追踪那些Graphic和Layout组件必须被更新的时候,还有与其对应的Canvas触发了willRenderCanvases事件的时候。更新Graphic类和Layout类叫做rebuild。
- Rebuild , 过程是指Layout和UGUI的C#的Graphic组件的网格被重新计算,这是在CanvasUpdateRegistry类中执行的。这是一个C#类,它的源码可以在Unity的Bitbucket上找到。
CanvasUpdateRegistry , 类中,PerformUpdate方法,当一个Canvas组件触发它的WillRenderCanvases事件时,这个方法就会被执行。这个事件每帧调用一次。
PerformUpdate , 函数运行的三个步骤:
1- 通过ICanvasElement.Rebuild函数,请求rebuild被Dirty的Layout组件。
2- 所有被注册的裁剪组件(例如Mask),对需要被裁剪的组件进行剔除。这在ClippingRegistry.Cull中执行。
3- dirty的Graphic组件被要求rebuild其图形元素。 - Layout Rebuild , 要重新计算一个或者多个Layout组件所包含的UI组件的适当位置(以及可能的大小),有必要对Layout应用层次的排序。在GameObject的hierarchy中靠近root的Layout可能会影响改变嵌套在它里面的其他Layout的位置和大小,所以必须首先计算。
- Graphic Rebuild , 当Graphic组件被rebuild的时候,UGUI将控制传递给ICanvasElement接口的Rebuild方法。Graphic执行了这一步,并在rebuild过程中的PreRender阶段运行了两个不同的rebuild步骤:1.如果顶点数据已经被标为Dirty(例如组件的RectTransform已经改变大小),则重建网格。2.如果材质数据已经被标为Dirty(例如组件的material或者texture已经被改变),则关联的Canvas Renderer的材质将被更新。Graphic的Rebuild不会按照Graphic组件的特殊顺序进行,也不会进行任何的排序操作。
入门理解
这里对大佬的文章进行摘要。
Unity是怎么绘制UI元素的?
Unity中渲染的物体都是由网格(Mesh)构成的,而网格的绘制单元是图元(点、线、三角面)。在unity中添加一个Image和Text,并且将Shadings Mode设置为Wireframe模式,可以看到一个Image由四个顶点和两个三角面构成,Text也是由许多顶点和三角面构成。
绘制信息都存储在Vertexhelper类中,除了顶点外,还包括法线、UV、颜色、切线以及一些函数。
数据存储好了,那怎么绘制呢?
这是依靠CanvasRenderer来完成的,它听起来可能比较陌生,但实际上当我们在项目中创建的一些UI元素,比如Button、Image、Text时,都包含组件CanvasRenderer,这个类提供了许多关键绘制信息,比如被渲染物体的颜色、材质和Mesh等,主要作用就是渲染包含在Canvas中的UI对象,但是在Inspector界面中并不会展示任何属性。
总结一下就是,Unity会把要绘制的UI信息保存在Vertexhelper中,并且调用CanvasRenderer里面的方法进行绘制。
重建 Rebuild
UI重建分为两类,一类是布局重建(Layout Rebuild),另一类是图形重建(Graphic Rebuild)。
一个UI若要重建,必须继承自ICanvasElement接口,因为执行重建操作的时候会调用接口中的Rebuild函数。
Canvas
是Unity渲染系统给层状几何体( layered geometry )提供的可以被画入、被放在上面或者放在世界空间的底层Unity组件。
Canvas在渲染前会调用willRenderCanvases事件,也就是Registry的PerformUpdate方法,用委托的形式传进去的。
CanvasUpdateRegistry
画面刷新的注册工具类,在它的构造函数中会给Canvas注册回调:Canvas.willRenderCanvases += PerformUpdate;
内部维护2个队列(都是 ICanvasElement类型 的):
- LayoutRebuildQueue:布局重建队列
- GraphicRebuildQueue:图像重建队列
这2个队列提供了公开方法向其添加内容。
1 | //向m_LayoutRebuildQueue中添加元素 |
添加内容时机
那么什么时候对2个重建队列添加内容,也就是怎么确定哪些要重建呢?是通过脏数据实现的。
布局(Layout)、材质(Material)、顶点(Vertices)三部分,设置布局为脏,将进行布局重建;设置顶点或材质为脏,则进行图形重建。
1 | public virtual void SetAllDirty() |
触发重建
加入重建队列之后,CanvasUpdateRegistry就会在PerformUpdate
函数中调用它的Rebuild
进行重建。Graphic对Rebuild进行了实现:如果顶点或材质被标记为“脏”的话,会更新元素的几何网格(UpdateGeometry)和材质(UpdateMaterial)。
1 | public virtual void Rebuild(CanvasUpdate update) |
UpdateGeometry函数用于确定元素的网格(Mesh)信息,这些信息包括顶点、三角面、UV、颜色等,它们将会被填充到s_VertexHelper中,并最终调用canvasRenderer.SetMesh(workerMesh)设置Mesh信息。
1 | private void DoMeshGeneration() |
渲染前流程
- PerformUpdate函数对m_LayoutRebuildQueue中的元素进行排序,依据是父节点的多少。接下来依次将Prelayout、Layout和PostLayout作为参数传递给Rebuild进行布局重建,完成后通知布局队列中的元素重建完成。
- 调用ClipperRegistry的Cull函数进行裁剪。
- 进行图形重建,遍历m_GraphicRebuildQueue的值,分别将参数PreRender、LatePreRender作为参数传递给Rebuild函数进行图形重建。
- 最后通知图形重建完成。
Rebuild时机:脏标记
这里用脏标记,就是将重建的行为延迟到用户需要这个物体的时候才执行,一种优化重新渲染的手段。
在Graphic 中存在三种脏标记分别代表三种等待重建
尺寸改变时(RectTransformDimensions):LayoutRebuild 布局重建
尺寸、颜色改变时:Vertices to GraphicRebuild 图像顶点重建
材质改变时:Material to GraphicRebuild 图像材质重建
层级改变、应用动画属性(DidApplyAnimationProperties) :All to Rebuild 重建所有
案例1:Image
举例Image的情况,Image间接继承自Graphic,当它的Sprite发生变化时,会调用SetAllDirty函数;设置Sprite大小的时候也会调用。
1 | public Sprite sprite |
案例more
- Text控件 文本的内容及颜色变化、设置是否支持富文本、更改换行模式、设置字体最大最小值、变更文本使用的对齐锚点、设置是否通过几何对齐、变更字体大小、变更是否支持水平及垂直溢出、修改行间距、变更字体样式(正常、斜体…..)。
- Image控件 颜色变化、变更显示类型(Simple、Sliced、Tiled、Filled)、变更是否应保留Sprite宽高比(Image.preserveAspect属性的变更),FillCenter属性变更(是否渲染平铺或切片图像的中心)、变更填充方式(Horizontal、Vertical、Radial360….)、变更图像填充率(fillAmount)、变更图像顺逆时针填充类型(Image.fillClockwise)、变更填充过程的原点(Image.FillOrigin)。
- RawImage控件 设置Texture、变更纹理使用的UVRcet。
- Shadow效果 改变效果的距离(effectDistance)及颜色(effectColor)、变更是否使用Graphic中的Alpha透明度(useGraphicAlpha)。
- Mask控件 设置是否展示与Mask渲染区域相关的图形(showMaskGraphic),enable发生变化
- 所有继承MaskableGraphic的控件(Image、RawImage、RectMask2D、Text) 设置此图形是否允许被遮盖、enable发生变化、父节点发生变化(TransFromParentChanged)、在Hierachy面板上发生改变(HierachyChanged)。
- 所有继承自BaseMeshEffect的效果类(目前只看到Shadow及PositionAsUV1)的enable变化及应用动画属性的操作。
- 所有继承自Graphic的UI控件材质(material)发生变化。
一整轮Rebuild:PerformUpdate方法
1.在布局重建队列、图像重建队列中,剔除已销毁对象。
2.更新布局。根据父节点数量排序,先深后浅。更新类型依次为 Prelayout 、Layout 、PostLayout。详细见ugui_4。
3.执行LayoutComplete
回调,也就是通知LayoutRebuild队列的所有元素,通知布局已完成。
4.布局完成,可以对UI(IClipper)进行裁剪了,显示不到的就不渲染
5.更新图像。依次 PreRender、LatePreRender、MaxUpdateValue:1.如果顶点数据已经被标为Dirty(例如组件的RectTransform已经改变大小),则重建网格。2.如果材质数据已经被标为Dirty(例如组件的material或者texture已经被改变),则关联的Canvas Renderer的材质将被更新。
6.执行GraphicUpdateComplete
回调,通知图像更新完成。
顺序枚举
CanvasUpdate,一个枚举类,很核心,代表着Canvas对Layout、Render的处理顺序:
- Prelayout:Called before layout.
- Layout
- PostLayout:Called after layout.
- PreRender:Called before rendering.
- LatePreRender:Called late, before render.
- MaxUpdateValue:Max enum value. Always last.