UnityUGUI源码阅读之Graphic

关于Graphic组件

Graphic组件是UGUI中比较重要的一个组件,例如Image,RawImag,MaskableGraphic 可遮罩的图形组件 这些都是继承自Graphic的

UnityUGUI源码阅读之Graphic_第1张图片
它必须要有 CanvasrRenderer组件以级RectTransform 组件,并且一个对象只允许挂载一个。
RectTransform:是用来 存储Rect的信息的,锚点,轴心,宽高,
缩放,旋转,这个组件几乎所有组件多会使用,除了一些事件层的,比如各种事件处理器,anchoredPosition以级sizeDeltal 用的比较多。它是用来渲染 画布元素的一个比较重要的组件,可以理解为UGUI里的Transform,当然和UnityEngine里的Transform还是不一样的。

CanvasRenderer:当然不用说了,如果 没有这个组件,是不会去通知根节点的Canvas 进行渲染的;

关于它的继承关系

Graphic->UIBehaviour,ICanvasElement

关于它的一些使用 例子

这里有个简单的例子:
重写了这个 onPopulateMesh 方法,它的作用是计算元素网格数据
填充网格,计算上下左右四个角的

 [ExecuteInEditMode]
 public class SimpleGraphic : Graphic
 {
      
     protected override void OnPopulateMesh(VertexHelper vh)
     {
         Debug.Log("vh"+vh.currentIndexCount+"VertCount"+vh.currentVertCount);
         //两个对角 (0,0)  (0.5,0.5)是中心点 (1,1)
         Vector2 corner1 = Vector2.zero;
         Vector2 corner2 = Vector2.zero;
         
         corner1.x = 0f;
         corner1.y = 0f;

         corner2.x = 1f;
         corner2.y = 1f;  //宽高比

         corner1.x -= rectTransform.pivot.x;
         corner1.y -= rectTransform.pivot.y;

         corner2.x -= rectTransform.pivot.x;
         corner2.y -= rectTransform.pivot.y;
         //宽高保持一致
         corner1.x *= rectTransform.rect.width;
         corner1.y *= rectTransform.rect.height;
         corner2.x *= rectTransform.rect.width;
         corner2.y *= rectTransform.rect.height;
         //重新计算
         vh.Clear();

         UIVertex vert = UIVertex.simpleVert;

        //添加四个点  左下左上,右上右下
        vert.position = new Vector2(corner1.x, corner1.y);
         vert.color = color;
         vh.AddVert(vert);

         vert.position = new Vector2(corner1.x, corner2.y);
         vert.color = color;
         vh.AddVert(vert);

         vert.position = new Vector2(corner2.x, corner2.y);
         vert.color = color;
         vh.AddVert(vert);

         vert.position = new Vector2(corner2.x, corner1.y);
         vert.color = color;
         vh.AddVert(vert);
         
         //添加三角形到 缓冲区中
         vh.AddTriangle(0, 1, 2);
         vh.AddTriangle(2, 3, 0);
     }
 }

大概效果就是这样:

UnityUGUI源码阅读之Graphic_第2张图片

那么它是如何被渲染出来的呢?

首先这个方法会在填充网格数据的时候调用。

在onEnable时 ,
会把自己graphic和对应的Canvas的引用注册到GraphicRegistry的 m_Graphics 字典里 ,
GraphicRegistry. RegisterGraphicForCanvas

如果没有canvas就会去上级节点获取所有的canvas
UnityUGUI源码阅读之Graphic_第3张图片

然后
关于它的调用顺序:
onEnble->SetAllDirty-> Rebuild->UpdateGeometry> DoMeshGeneration

关于Rebuild

判断当前是哪个Dirty 然后进行更新,首次会SetAllDirty 因此
UpdateGeometry 以级UpdateMaterial 都会被调用UnityUGUI源码阅读之Graphic_第4张图片

Graphic.UpdateGeometry 更新几何

判断是否使用遗产mesh生成
遗产用Mesh 不是用VertextHelperUnityUGUI源码阅读之Graphic_第5张图片
接着是 做网格数据的生成
UnityUGUI源码阅读之Graphic_第6张图片
OnPopulateMesh 填充网格数据 也就是顶点 数据 填充到 s_vertexHelper变量里,然后 取得 所有的IMeshModifier 组件
把s_vertexHelper作为参数 传递过去。
然后进行通知网格数据改变,进行网格数据修改
然后再填充到workerMesh上

最后 调用SetMesh 把workerMesh作为参数传递到canvasRender组件里,
最后再提交数据到根对象canvas上,进行判断合批等等

最后再提交数据出去 进行渲染。

关于Unity UGUI做的合批

合批这部分Unity貌似没公开,然后可以通过下面这个文章参考
基于4.6的 有点旧
UGUI优化源码合批
canvas底下的 更新方法,通过过一个静态的 委托 willRenderCanvases进行注册 UnityUGUI源码阅读之Graphic_第7张图片
关于它的 注册的时机,在CanvasUpdateRegistry的构造函数里
UnityUGUI源码阅读之Graphic_第8张图片

关于 CanvasUpdateRegistry.PerformUpdate

CanvasUpdateRegistry它是这个单例类,负责所有画布 元素的rebuild注册;
因为所有画布元素都是继承自 ICanvasElement 接口的。

CanvasUpdateRegistry在构造出来的时候,就把PerformUpdate注册进去了
UnityUGUI源码阅读之Graphic_第9张图片
willRenderCanvases 就是底层的SendWillRenderCanvas,网格重建,
关于它的调用次数的监听,以及那些元素导致了这个方法调用
可以查考这个 链接,具体触发机制我也不太清楚,一般来说应该是 Canvas的 layout或者Graphic发生变化时,Unity3D 底层帮我们进行 网格合批,等一系列判断处理后,再发送这个事件给UGUI,然后触发这个PreformUpdate方法。 然后它会去通知注册进来的 元素,layoutQueue,以及GraphicQueue,然后调用它们的Rebuild方法, 关于Graphic的Rebuild方法是会根据本身的 dirty 标志位,判断是否真的需要Rebuild。

关于PerformUpdate的事情:

  1. Layout的Rebuild
  2. Graphic的Rebuild
    1与2的不同点就在于 Layout要先按照子物体多少进行排序,而2并没有。

具体逻辑:
会从m_LayoutRebuildQueue 按子物体的多少进行排序,越多的越靠前,然后进行遍历 调用 rebuild,分别是
UnityUGUI源码阅读之Graphic_第10张图片
顺序是 preLayout ,layout ,postLayout,0,1,2 意思就是 布局Rebuild 以级先后
UnityUGUI源码阅读之Graphic_第11张图片
最后再 是 layoutComlete.
UnityUGUI源码阅读之Graphic_第12张图片
接着是进行Render了 ,从preRender 到LaterRender的 Rebuild
render的 Rebuild 是存在 m_GraphicRebuildQueue 这个队列里的

然后遍历 调用 graphicUpdateComplete 表示渲染 完成
UnityUGUI源码阅读之Graphic_第13张图片

以上这些 RebuildQueue是如何添加的呢

其实是在 调用SetXXXDirty的时候,标志脏,在Graphic中
UnityUGUI源码阅读之Graphic_第14张图片
SetVerticesDirty 会添加到 m_GraphicRebuildQueue 当中,
SetLaoutDirty 会添加到 m_LayoutRebuildQueue 中
SetMaterialDirty 会添加到m_GraphicRebuildQueue 中

而这些脏标志会 什么时候调用呢

一个是OnTransformParentChanged
在父亲Transform发生变化时, 旋转,位置,缩放等等;

以级OnRectTransformDimensionsChange
在RectTransform维度发生变化时

如果关联的RectTransform的维度被更改,则调用此回调。这个调用也会对所有的子rect转换进行调用,即使子转换本身没有改变——它可能会改变,这取决于它的锚定

SetVerticesDirty还会在 颜色发生变化时调用.
SetMaterialDirty还会在 材质进行替换时调用

SetAllDirty 会在 OnEnable,Reset,OnDidApplyAnimationProperties 动画属性变化时调用

因此呢,项目里隐藏,可以改变Alpha,或者移到看不见的地方,这样就不会 触发SetAllDirty了 而是render 或者是vertices的更新了。

此外
画布分离,动静分离的优化就很有必要了。

总结下, Graphic的SetDirty 会添加到 CanvasUpdateRegistry的 单例的 m_layoutRebuildQueue 以级 m_GraphicRebuildQueue中
在这里插入图片描述

这个单例在构造出来的时候,会把 performUpdate 注册到
canvas的 静态事件变量 willRenderCanvases 里,
在这里插入图片描述
UnityUGUI源码阅读之Graphic_第15张图片

PerformUpdate 会先处理 layOutReBuildQueue,进行按子物体量进行排序,再判断是否可以ReBuild再依次调用 preLayout,layout,lateyout 3个Rebuild

最后才是layoutComplete , 然后 类似的 处理GraphicRebuildQueue;

而这个Rebuild 实际上是由Graphic处理的,Graphic是继承自ICanvasElement的,而对于Graphic而言呢,只在PreRender的时候,进行了 几何学,材质的处理。
UnityUGUI源码阅读之Graphic_第16张图片
ICanvasElement 接口
UnityUGUI源码阅读之Graphic_第17张图片
最后 OnPopulateMesh 只是 在UpdateGeometry 时,调用了;

渲染过程的调用堆栈,在UnityEditor模式下
UnityUGUI源码阅读之Graphic_第18张图片
因为我使用了快捷键进行了激死激活,因此可以看到调用了key事件
HanleKeyEvent的派发过程
UnityUGUI源码阅读之Graphic_第19张图片
因此它的调用顺序在编辑器下是
UnityEditor.DrivenRectTransformUndo:ForceUpdateCanvases ()
UnityEngine.Canvas:ForceUpdateCanvases ()
UnityEngine.Canvas:SendWillRenderCanvases 事件
UnityEngine.UI.CanvasUpdateRegistry:PerformUpdate
UnityEngine.UI.Graphic:Rebuild (CanvasUpdate)

ImeshModifier mesh改变接口
UnityUGUI源码阅读之Graphic_第20张图片

关于Graphic 的优化Tips 技巧:

  1. 减少SetXXXDirty 次数,减少激死激活次数,例如消失隐藏,可以使用alpha或者位置的偏移来代替 激死激活,
  2. Canvas动静分离
  3. UI界面 动画尽量不要使用 动画控制器,使用DOTween等动画插件来实现界面动画。

你可能感兴趣的:(S3_Unity学习,源码,Unity,UGUI,Graphic,源码)