Drawcall 是什么:是 CPU 调用图像编程的接口,一个进行渲染操作的函数,调用一次一个 Drawcall
在每次调用Drawcall 之前, CPU 都需要向 GPU 发送很多数据,状态,命令等,在这个阶段 CPU 需要做很多工作,完成了 GPU 就会开始本次的渲染, GPU 渲染能力是很强的,都是并行操作,所以渲染速度往往快于 CPU 的命令提交速度,如果 Drawcall 数量太多, CPU 就会把大量的时间花在提交 Drawcall 上造成 CPU 过载,这也是为什么要减少 Drawcall 的原因。
Overdraw 是什么:过度绘制,指在一帧的时间内像素点被绘制了多次,理论上一个像素每次只绘制一次是最优的,但是重叠布局导致像素被多次绘制,这些每次绘制的过程都会对应到 CPU 提交 Drawcall 命令和 GPU 的一些操作,假设一帧的时间为 20 毫秒,而在 CPU 准备绘图命令和 GPU 的操作大大超过 20 秒的时候,就会掉帧,出现卡顿。
Batching 是什么:对场景中相同材质的物体 ( 包含 2D,3D 物体 ) 进行 Mesh 合并达到降低 Drawcall 的目的
Canvas 画布 , 管理所有子物体的几何图形,负责下面所有的组件先进行批处理能合并的合并然后发送渲染指令到图形系统 那是怎么管理的呢 -- 》 CanvasRender, 由 CanvasRender 向 Canvas 提供几何图形。 Canvas 对所有子物体进行整理和批处理的过程叫做 ReBatch
Graphic 图形基类所有 UI 组件的基类,继承自 ICanvasElement,UI 组件实现了该接口才能在 Canvas 画布上显示 , 并且 Mesh 重建也是包含在 ICanvasElement 中的 -- 》也就是 Rebuild 方法,同时在 Graphic 中还有个脏标记的概念 Dirty, 如果某个物体发生定点信息改变有任何修改就会被设置成 Dirty 脏标记 , 如果没有脏标记就认为没有被修改就不会进行重建,如果标记了就会进行重建然后显示出来 主要方法 SetLayoutDirty,SetVerticesDirty,SetMaterialDirty
Graphic中重要的子类
MaskableGraphic->继承了 IMaskable 接口,可以进行遮罩功能
ILayoutElement接口 -> 基本 UI 组件也都实现了 ILayoutElement 接口,我们经常使用的 GridLayoutGroup 有可能会控制 Transform 组件中的位置,大小,旋转等,并且同时 GridLayoutGroup 下面也会同时有很多个的元素,那么在进行重建的时候涉及的东西是非常多的从而消耗也是比较大的
CanvasUpdateRegistry 负责Canvas更新注册会监听 --willRenderCanvases 事件主要负责重建工作分为 Layout 的重建和 Graphic 的重建
深度测试
主要做物体间的前后关系处理,通过深度绘制物体,在 CPU 准备渲染数据存放在缓存中,在缓存中会保存物体的颜色值和深度值,深度值其实就是物体到相机的投影距离。深度值大离相机更远。
渲染队列 --也就是渲染顺序
透明物体和半透明物体--在 UI 中经常会使用透明和半透明得物体,或者粒子中使用这种透明得效果
而透明物体是不会写入深度,两个透明物体放在一起在前在后看起来都是一样得,所以在渲染半透明物体得时候是从后向前渲染,所以重叠部分就会造成多次渲染,也就是Overdraw,所以在谈到优化这块我们经常说少用透明或者半透明得效果。然后我们所有得 UI 都是在透明队列中绘制的,都是带有 Alpha 混合的,全部都是从后向前绘制的,这也是 UI 为什么会造成 Overdraw 的原因。
我们知道深度测试有遮挡剔除的效果起到一定的优化作用也就是把重叠被挡住的部分不会被渲染出来,而半透明物体不写入深度从后往前渲染的时候,那么重叠的部分就会出现过度绘制Overdraw
网格重建
1:合批网格重建 (Rebatch)Canvas 会把下面所有的元素进行合批生成新的 mesh ,消耗最大,任何一个物体发生改变都会重新绘制 Canvas 下的所有物体,如果我们如果做项目把所有的 UI 都放在一个 Canvas 下那基本完蛋
2:单个物体重绘 (Rebuild) 在 Canvas 进行合批操作时会计算出哪些元素需要重建,然后发给 Rebuild
网格重建的主要逻辑在:CanvasUpdateRegistry类中绑定的事件 PerformUpdate 执行逻辑
主要分3部分逻辑: 1.Layout 的重建, 2. 裁剪剔除的逻辑, 3.Graphic 的重建
Layout的重建计算时消耗非常高特别是 Layout 的嵌套 , 它的计算方式必须是从父节点往下内部计算包括子组件的位置和尺寸的改变等,建议如果是几个元素的话能不用 Layout 就不用 Layout
Graphic的重建涉及两个部分:设置顶点脏标记和材质设置成脏标记
而设置材质脏标记只在替换材质的时候才会生成,所以我们在修改材质的时候是不会导致网格重建的
而设置定点脏标记是在所有Graphic 子类中凡是涉及定点修改都会设置,另外SetAllDirty有在OnEnable 中调用所以在调用 UI 的显示隐藏也会进行重建,这也是为什么之前说到对于需要反复打开关闭的界面最好不用 SetActive(false Or true) 的原因
网格重建的执行流程
Graphic中重建涉及到的方法 SetPropertyUtility.SetColor 来摸索执行流程
举例:
1:比如一个不会改变的大背景,和一个动态元素比较多的面板,他们在同一个 Canvas 下,面板中所有元素的改变都会造成整个 Canvas 的合批,需要合理的使用动静分离
2: Text 组件,在 UI 中的 Text 组件每一个字都是 4 个顶点一个面片,如果字数非常的多的一个 Text ,或者很多的 Text, 如果控制显示隐藏 SetActive(false Or true) 就会导致所有的 Text 的重建,可能就会出现有点卡顿的情况。一般是需要优化的,因为这里面数会特别的多,并且是不需要变动的,而且重建是毫无意义的,是一个比较大的消耗
UI射线处理 --GraphicRaycaster
执行逻辑主要是GraphicRaycaster.Raycast 射线响应的方法,首先通过 Canvas 中的 Display 属性获取到你在第几个显示器上的显示界面就能获取到这个显示界面的尺寸在把响应事件的位置跟屏幕尺寸进行转换就能知道射线响应位置在屏幕当中的位置 | 左下 (0,0) 右上 (1,1) | ,
接着判断当前的响应事件有没有超出显示器的范围,之后在通过设置主射的参数来获取射线命中的距离,在接下来判断是否在Graphic的 RectTransform 上面和是否超出了摄像机的最远面,然后就进入了射线的主要处理逻辑,最先会判断 Graphic 的物体是否被绘制会出来如果深度为 -1 就不会绘制,另外一个判断参数就是 RaycastTarget 是否开启,还一个 CanvasRender 中的 cull 属性书否启用剔除,如果是不进行绘制的,然后进入 Graphic 的 Raycast 方法进行计算返回排序队列也就是把当前的 Graphic 添加到数组中然后进行排序,然后判断是否是一个反面的目标并过滤一些在相机背后渲染的元素,最后判断当前距离是否小于击中的距离,然后得到正确的击中目标并加入击中列表中
Mask与 RectMask2D 的实现区别
为什么在Unity中经常说到不要使用 Mask 组件?
Mask 实现遮罩方法 GetModifiedMaterial 主要使用模板缓存的方式实现 , 模板缓存主要是 Shader 方面的知识不多做介绍
RectMask2D 用于 2D 遮罩 , 遮罩功能来源于IClipper(裁剪接口 ) 接口