在日常开发过程当中,类似于HUD或者是弹幕之类大量的体积小但是不断在移动的UI,在不断的重建的过程当中会产生大量的GC,导致游戏卡顿到不能玩。

今天就研究一下Unity中UGUI的绘制方法以及规则:

UGUI的源码地址(C#部分):

下载地址

利用底层API进行绘制


首先我们要搞清楚,到底UGUI是如何对UI进行绘制的。我们首先从最稀疏平常的Image开始。

Image源码尝试阅读

我们首先盯上的是Image,我们发现首先,其基类为MaskableGraphic,其他的接口我们先暂时不管,并且最终我们看到其最终的基类依旧是Monobehavior。

经过阅读,其实最主要的绘制部分其实不多,主要是对Graphic基类中几个成员对象的重写。

  • mainTexture属性 主要负责提供贴图
  • material属性 主要提供材质
  • OnPopulateMesh函数 主要提供顶点以及UV信息(也就是Mesh信息)

首先是mainTexture,我们发现就是简单从sprite中取出Texture而已,如果没有sprite则提供默认的白色贴图

然后是material属性,我们可以看到,也是非常简单,首先判断有没有材质,如果没有就提供默认材质。

最后就是比较重头的OnPopulateMesh,这个函数的主要作用就是用于构建Mesh。

在这之前我们可以先看看VertexHelper这一个类的作用。

打开VertexHelper的源码,我们可以很清楚看到,其实VertexHelper只是一个临时的容器,用于存放需要渲染的Mesh信息。

值得注意地是其中所用到的ListPool,性能高度敏感的代码块,需要大量的使用对象池,这是优化代码效率减少GC的好技巧,无论是网络模块还是底层的组件重用,对象池都是必不可少的!

那这个时候我们其实自己可以想到,我们的合批需要怎么做了,在我们使用UGUI的时候其实每一个Image都是使用了PopulateMesh来进行Mesh生成之后进行合批,我们其实可以将这一步直接放在这一个函数里面,由我们自己来重写就可以了,这样我们就不需要调用N次PopulateMesh之后进行合并,而只需要调用一次自己编写的PopulateMesh函数就可以了。

我们再看一看Graphic基类当中对该函数的编写:

上面图中我们可以看到,其仅仅是画了一个正方形的Mesh而已。

具体如何通过Vertex画Mesh其实和OpenGL中VAO EBO是一样的,就不再赘述了。

我们回过头来看Image当中的OnPopulateMesh:

我们会发现,当Sprite为空的时候画Image的方式就是Graphic,与我们平时的使用方式相同。

当Sprite不为空则会通过不同的sprite类型来进行绘画,实际上就是获取不同的UV,通过何种方式来将贴图绘制到Mesh上面。

我们这里就只看一下最简单的sprite绘制方法,我们会看到

其实也是画个矩形……

GetDrawingDimensions在获取Rect,而通过GetOuterUV来获得UV。

获得UV的时候主要是为了将Sprite中空白的地方进行裁切,主要是通过从Native层得到的padding来计算的,具体内部如何计算我们不得而知,但是我们拿到padding值就已经可以完全算出Sprite的实际大小了。

由于我们只是绘制Simple类型的sprite,所以对UV不需要进行比较复杂的计算,实际上在其他几种绘制方式之中,除了OuterUV还会用到InnerUV,详细算法也就得自己慢慢看了……

至此,我们已经大致明白了如何通过顶点来对UI进行绘制。是时候自己实践了。

自定义Graphic

我们建立一个新的类:MyGraphic

其中我们重写三个对象,也就是上面提到的:

  • mainTexture属性
  • material属性
  • OnPopulateMesh函数

因为自己很懒惰,所以直接将DrawingDimension函数拿过来用了。

获得了Rect以及UV之后我们就可以像普通的Image一样绘制出图片了,

当然我们也可以把其中一句AddTriangle删除,我们就可以发现,我们只绘制了一个三角面

UGUI底层绘制规则


接下去,我比较好奇的是,OnPopulateMesh到底是什么时候进行调用的,因为我们都知道,在UI当中消耗比较大的用于都是UI的重建操作,如果我们能够搞明白UI重建规律的话那我们应该可以大大避免UI重建的情况。

Graphic当汇总的DoMeshGeneration调用了OnPopulateMesh

我们可以看到,当RectTransform的尺寸为0的时候就不再调用OnPopulateMesh了,与以前总结出来的优化方案相同。

下面则是对Mesh进行调整并且通过VertexHelper将数据实装到Mesh当中最后将Mesh赋值给CanvasRenderer进行渲染,这下能理解为何所有显示组件都要带一个CanvasRenderer了吧。

UpdateGeometry调用到了DoMeshGeneration

除了Text在字体变换的时候会进行更新以外,入口都是统一的:Graphic中的Rebuild。

Rebuild实际上是实现了ICanvasElement的接口。

当顶点数据遭到修改,则立刻会进行重建,实际上我们只需要关心何时SetVerticesDirty会被修改就可以明白到底什么时候UI会进行重建。

不过在此之前,我们先看看Rebuild在何时会被调用。

查找引用,发现是在CanvasUpdateRegistry中的PerformUpdate中进行调用,这个PerformUpdate是挂载在Canvas.willRenderCanvases这个事件上的,也就是Canvas进行渲染之前便会进行调用。

SetVerticesDirty被调用的情况

我们回过头来看一看何处会修改SetVerticesDirty:

当调用了SetVerticesDirty则会修改该值并且添加到Canvas的重建列表当中。

调用该函数的地方就不少了,查找之后总共34处调用。

文字的以下属性进行变化都会进行Mesh重建,所以如果有大量需要经常变动的Text就要小心了。

同样的图片也有属性一旦改变就会引发改变,其中我们比较常用的可能就是尺寸了,例如CD、位置修改等等,都会造成重建。

RawImage中进行修改的时候也会产生重建

当蒙版进行修改的时候也会进行重建

采用Shadow的时候也会产生重建

了解到何处会对Mesh重建进行调用我们就可以尽可能减少重建消耗。

总结


例如弹幕或者是MMO中飘血文字都可以通过自己重写OnPopulateMesh来自己对Mesh进行合批而不需要通过Canvas对所有元素进行逐一的合批,提高效率。

UGUI源码之绘制初探相关推荐

  1. [UGUI源码剖析]—Rebuild 网格重建(画布刷新)系统

    几个比较重要的类和接口: Canvas.CanvasUpdateRegistry.ClipperRegistry.LayoutRebuilder.LayoutGroup.Graphics.Maskab ...

  2. Unity中的UGUI源码解析之事件系统(6)-RayCaster(下)

    Unity中的UGUI源码解析之事件系统(6)-RayCaster(下) 接上一篇文章, 继续介绍投射器. GraphicRaycaster GraphicRaycaster继承于BaseRaycas ...

  3. ugui源码_UGUI 源码笔记(一)文件结构和部分组件使用

    这是我阅读 UGUI 源码记录的相关笔记,共三部分.文件结构和部分组件使用.输入事件.核心部分 ZeroyiQ:UGUI 源码笔记(一)文件结构和部分组件使用 ZeroyiQ:UGUI 源码笔记(二) ...

  4. UGUI源码分析:开关组件Toggle与ToggleGroup

    系列 UGUI源码分析系列总览 相关前置: UGUI EventSystem源码分析 UGUI源码分析:Selectable交互组件的基类 文章目录 系列 Toggle Toggle组件属性介绍 初始 ...

  5. UGUI源码分析:GridLayoutGroup网格布局组件与ContentSizeFitter尺寸调节组件

    系列 UGUI源码分析系列总览 相关前置: UGUI CanvasUpdateSystem源码分析 UGUI源码分析:LayoutSystem布局系统 UGUI源码分析:LayoutGroup中的纵横 ...

  6. UGUI源码分析:LayoutGroup中的纵横布局组件(HorizontalOrVerticalLayoutGroup)

    系列 UGUI源码分析系列总览 相关前置: UGUI CanvasUpdateSystem源码分析 UGUI源码分析:LayoutSystem布局系统 文章目录 系列 UML图一览 LayoutGro ...

  7. 如何把UGUI当做一个插件使用(删除Unity中的UGUI,导入UGUI源码进入项目)

    最近闲着没事,一直也都知道UGUI是开源的,所以就想着把UGUI的源代码放到Unity里面,看一看能不能用,经过一番调试,终于弄好了,有兴趣的同学可以看一下,欢迎交流沟通. 欲练神功,必先自宫.第一步 ...

  8. Unity中的UGUI源码解析之事件系统(2)-EventSystem组件

    Unity中的UGUI源码解析之事件系统(2)-EventSystem组件 今天介绍我们的第一个主角: EventSystem. EventSystem在整个事件系统中处于中心, 相当于事件系统的管理 ...

  9. Unity中的UGUI源码解析之事件系统(8)-输入模块(中)

    Unity中的UGUI源码解析之事件系统(8)-输入模块(中) 接上一篇文章, 继续介绍输入模块. Unity中主要处理的是指针事件, 也就是在2d平面上跟踪指针设备输入坐标的的事件, 这一类事件有鼠 ...

  10. Unity中的UGUI源码解析之事件系统(9)-输入模块(下)

    Unity中的UGUI源码解析之事件系统(9)-输入模块(下) 接上一篇文章, 继续介绍输入模块. StandaloneInputModule类是上一篇文章介绍的抽象类PointerInputModu ...

最新文章

  1. arm优化编译参数选项解释
  2. 关于高德地图Android开发时地图只显示一次、第二次打开不定位的解决办法
  3. UTF-8带BOM和不带BOM的转换
  4. python自定义变量名标识符,【python】3 标识符和关键字
  5. boot定时任务开启和关闭 spring_Spring-Boot 下定时任务通过配置文件控制开关和执行时间...
  6. 使用Android自带DownloadManager下载文件
  7. STM32的JTAG下载模式
  8. Qt4项目迁移到Qt5问题:greaterThan(QT_MAJOR_VERSION, 4): QT += widgets .
  9. Ansible@一个高效的配置管理工具--Ansible configure management--翻译(十一)
  10. 函数解素数求距离问题
  11. java断点上传分片保存方案_分片上传与断点续传解决方案
  12. 备考分享!第十一届CDA考试Level Ⅱ 优秀考生采访
  13. 项目管理笔记-第十章 项目沟通管理
  14. cpu过剩是什么意思_现在为什么人们都说CPU性能过剩,而不说显卡性能过剩?
  15. 19-Python基础知识学习-----迭代器与生成器
  16. Unity项目-黑魂复刻(四)玩家控制器(翻滚以及跳跃操作改动)
  17. win10/11下wsl2安装gpu版的pytorch(避坑指南)
  18. 编程实现顺序表的基本操作
  19. sql server 学习教程
  20. typora笔记使用base64编码图片

热门文章

  1. Rust: flat_map、filter_map、for_each
  2. Linux宝库名人轶事栏目 | 笨叔与Linux的那些事(下)
  3. 【优化算法】白鲨优化算法(WSO)【含Matlab源码 623期】
  4. 毕设题目:Matlab通信
  5. 【光学】基于matlab GUI带切趾的光线布拉格光栅滤波特性仿真【含Matlab源码 1505期】
  6. 【三维路径规划】基于matlab遗传算法无人机三维路径规划【含Matlab源码 1268期】
  7. 【旅行商问题】基于matlab免疫算法求解旅行商问题【含Matlab源码 195期】
  8. 中国ai人工智能发展太快_新的AI计算遥远行星的速度快100,000倍
  9. 星球大战telnet_重制星球大战:第四集(1977)
  10. mysql存储food_Mysql存储过程