游戏人生
About Me
  • 你好
  • Math
    • Number
      • Float IEEE754对确定性的影响
      • Pairing Function及其用途
    • Vector and Matrix
      • TRS基础概念
      • LossyScale深入分析
    • Quatenion
      • FromToRotation实现细节
    • Lerp and Curve
      • Slerp球形插值
      • Bezier Curve为什么重要
      • Interpolation和Extrapolation实现细节
  • Programming
    • C#
      • 学习资料
      • C# struct灵魂拷问
      • CIL的世界:call和callvirt
      • .NET装箱拆箱机制
      • .NET垃圾回收机制
    • Go
      • 基础特性
      • 如何正确的判空interface
      • 如何用interface模拟多态
      • 如何定制json序列化
      • 如何安全在循环中删除元素
      • 如何安全关闭channel
      • 如何集成c++库(cgo+swig)
      • 如何性能测试(benchmark, pprof)
    • Lua
      • 基础特性
  • General Game Development
    • Game Engine
      • 学习资料
      • 关于游戏引擎的认知
    • Networking
      • 帧同步
      • 状态同步
      • 物理同步
    • Physics
      • PhysX基本概念
      • PhysX增加Scale支持
      • PhysX场景查询
      • PhysX碰撞检测
      • PhysX刚体动力学
      • PhysX角色控制器
      • PhysX接入项目工程
      • 物理同步
      • 物理破坏
    • Design Pattern
      • 常用设计模式
      • MVP 架构模式
      • ECS 架构模式
  • Unity
    • Runtime
      • Unity拥抱CoreCLR
      • 浅析Mono内存管理
    • UGUI
      • 浅析UGUI渲染机制
      • 浅析UGUI文本优化
      • 介绍若干UGUI实用技巧
    • Resource Management
      • 浅析Unity堆内存的分类和管理方式
      • 深入Unity资源
      • 深入Unity序列化
      • 深入Assetbundle机制
    • Async
      • 深入Unity协程
      • 介绍若干Unity协程实用技巧
      • 异步动作队列
    • Hot Reload
      • Unity+Xlua
      • Xlua Examples学习(一)
      • Xlua Examples学习(二)
    • Editor Extension
    • Performance
      • 浅析Unity Profiler
      • 介绍一个Overdraw分析工具
  • Platform
    • WebGL
  • Real-world Project
    • Souce Engine
    • DOOM3 BFG
Powered by GitBook
On this page
  • 浅析UGUI的渲染机制
  • 渲染器
  • 渲染层级(TODO)
  • 相机层级
  • 画布层级
  • 物体层级
  • 渲染流程
  • (一)rebatch
  • (二)rebuild
  • (三)drawcall
  • (四)fillRate
  • 优化技巧举例
  • (一)隐藏UI的正确方法
  • (二)降低overdraw的技巧
  1. Unity
  2. UGUI

浅析UGUI渲染机制

PreviousUGUINext浅析UGUI文本优化

Last updated 2 years ago

浅析UGUI的渲染机制

本篇简要介绍UGUI的渲染方面的概念、常用技巧和优化方法。可能写的不太系统,而意在写一些重点。

渲染器

UGUI的渲染器是 Canvas Renderer(下文简称为CR),首先来看看其与同样是渲染2D物体的 Sprite Renderer(下文简称为SR) 的区别和联系()。

相似点

  • 都有一个渲染队列来处理透明物体,从后往前渲染。

  • 都可以通过图集(Atlas)来合并渲染批次,降低Drawcall。

不同点

  • CR与RectTransform配合,因此可以有专门的布局适配,但必须在Canvas中使用,常用于UI。SR与Tranform配合,没有适配,常用于GamePlay。

  • CR是基于矩形分割的三角网格,一张网格最少有两个三角形,透明部分也占用空间。SR的三角网格比较复杂,不过也是自动生成的,能剔除大部分透明部分。

之所以使用CR渲染一张图最少有2个三角形,是因为仅在ImageType设置为simple时成立。设置为sliced(九宫格),将会有18个三角形,设置为tiled可能更多。

对于sprite renderer,若非透明的部分不连通,则为每个非连通部分各自生成网格。在Unity编辑器中观察三角形网格可以在scene中选择Shaded Wireframe。

小结

可以看到,三角形数和网格面积是一对“trade-off”。

网格三角形数和顶点数成正比,影响的是GPU的几何运算量。网格所占面积影响的是GPU的片元计算量。

而对于现代对于移动处理平台,对顶点操作的代价要显著小于对片元操作的代价。尤其是处理半透明图片的重叠时,需要反复对同一个像素位置进行渲染,产生overdraw问题。

SR用更复杂的顶点运算为代价,剔除了大量透明区域,减少了overdraw,换取了更少的片元处理,节省了GPU带宽。而且,变化的SR不会像CR那样引起整个canvas去rebuild。本人认为,在显示不需要使用布局功能的2D物体时,可优先考虑用SR而不是通通放到Canvas下做成UI,这样既可以减轻overdraw、也能防止潜在的过量rebuild。

补充

另外,渲染3D物体用的是MeshRenderer(简称MR),它和SR的区别在哪里呢?MR可以接受光照并且投射阴影。而SR不能原生支持这一点( https://forum.unity3d.com/threads/why-cant-sprites-gameobjects-cast-shadows.215461/ )。

渲染层级(TODO)

我们说的渲染层级高,是指它盖在其他物体的上面,也就是最后被渲染(颜色混合)的那个。

渲染的层级控制本身也是分层的。其从高到低分为几个层次: (1)相机的layer和depth (2)画布的layer和order (3)物体的hierachy关系。

越高的层次,控制力就越大。下面,我们具体说明每个层次的使用。

相机层级

上图的红框中,CullingMask可以决定该相机能看到什么Layer的物体(Layer在Inspector窗口的右上角设置,可以自定义)。

Depth越高的相机,其视野内能看到的所有物体的渲染层级都更高(无视其他低层次的规则)

至于ViewPort Rect,可以实现同一个屏幕内显示多个相机视野的功能,通过设置起始点和宽高,可以实现类似监控台多个监视器的效果。下面贴一张图做例子。

画布层级

物体层级

父子parent在下面 兄弟sibling

渲染流程

这里重点说CPU的部分。

(一)rebatch

什么是rebatch

将mesh转化为渲染指令的过程叫做rebatch,也叫batch build。Unity中,主要是由canvas相关组件完成rebath,大致步骤是:

geometry -> canvas renderer -> meshes -> canvas -> rendering command -> unity graphic pipeline。

rebatch何时触发

当一个canvas中包含的mesh发生改变时就触发,例如SetActive、transform的改变、 颜色改变、文本内容改变等等。注意,这些变化影响的范围是以一个个canvas为界限的,即其他canvas、子canvas内的变化与当前canvas无关(后面会说到,这也是用多个canvas或子canvas能减少rebatch和rebuild的原因)。

若canavs内含有需要rebatch的物体,则该canvas会被标记为dirty,供后续流程使用。

rebatch很慢吗

虽然rebatch这一步是多线程的,但不必要的rebatch,会给后续步骤带来很大负担。rebatch本身对计算力的消耗体现在:对meshes按照深度和重叠情况排序、共享材质的检测等。

(二)rebuild

什么是rebuild

布局和网格被重新计算的过程叫做rebuild。Unity中,是指c#中Graphic组件的重新计算(可以理解为rebatch的后续)。大致步骤是:每一帧都会触发WillRenderCanvases事件,然后由CanvasUpdateRegistry响应并执行PerformUpdate。

PerformUpdate分三步:

  1. Dirty Layout components rebuild ,有一个根据Hierachy排序过程

  2. Masks Clipping component

  3. Dirty Graphic components rebuild, 不需要排序

rebuild很慢吗

Unity官方没说用的是多线程还是单线程处理。不过可以肯定的是,因为Layout components需要排序,所以要尽量少用Layout components,而用rectranform的锚点来布局。

(三)drawcall

什么是drawcall

CPU和GPU之间的通信内容主要有两类:数据和命令。其中,命令又分为两种: (1)设置渲染状态(2)drawcall。

渲染状态主要包含所用shader、材质(包含纹理、uv信息、光照等)。

这里补充一点:Image组件绑定的材质不要设置为none,否则unity会采用默认的材质。该材质的shader过于复杂,会降低性能。没啥需求就选成sprite-default。

drawcall的确切含义是:在渲染的第一阶段,CPU准备好数据和渲染状态后,真正向GPU发送的一次渲染请求:开始渲染一个或一批图元。

为什么drawcall太高不好

drawcall太高,意味着有频繁地改变渲染状态,这是很慢的操作(相对于GPU执行其内部的渲染流水线来说)。当存在两个相同渲染状态、仅仅是顶点数据不同的图元时,我们应当尽量将其一起渲染,这样就减少了一次drawcall。而GPU的空闲时间也会减少,最终完成渲染整个画面需要的总时间就会减少,帧数才能提高。

如果场景中drawcall太高,那么得从两个方面入手:检查hierachy的合理性;使用图集。具体方法这里后面有文章会专门介绍。

但要记住一点,虽然通常我们都在尽量降低drawcall,但并不是越低越好。所以不要一味追求低drawcall而牺牲了其他方面的性能。

(四)fillRate

这个是显卡里面的概念,这里单独拎出来,是因为overdraw导致的fillRate过大问题通常是移动设备GPU渲染的瓶颈(而不是顶点数太多)。关于这块的优化会单独抽一篇文章去说,这里给出fillrate的公式。

fillRate = 像素数 x overdraw x shader复杂度

另外,fillrate的概念可细分为pixel fillrate和texture fillrate。

pixel fillrate

显卡每秒可渲染到屏幕上的像素点数量。有点像带宽的概念。在片元着色性能成为显卡性能评估标准之前,像素填充率一直是最准确的标准之一。

texture fillrate

显卡每秒可渲染到屏幕上的纹理化了的像素点数量。摘一段wiki:

To render a 3D scene, textures are mapped over the top of polygon meshes. This is called texture mapping and is accomplished by texture mapping units (TMUs) on the videocard. Texture fill rate is a measure of the speed with which a particular card can perform texture mapping.

优化技巧举例

这里是指写几个简单明了的技巧,UGUI还有诸多方面的全面优化参见后文。

(一)隐藏UI的正确方法

如果用setactive(false),会导致所在canvas的VBO数据失效。所以再次setactive(true)的时候,会导致整个canvas去rebuild和rebatch,从而对CPU造成负担。

推荐方法:多使用sub-Canvas(注意不仅要添加Canvas组件,还有Graphic Raycaster组件),隐藏目标采用将对应的CanvasRenderer.enabled = false。

但注意挂在上面的脚本还是在运行的,而且虽然看不见了,还是能被Graphic Raycast检测到。如果这样会对逻辑行为造成影响,那么应该自己实现一个manager去管理CanvasRenderer.enabled 下的脚本行为。

(二)降低overdraw的技巧

首先观察当前场景的overdraw:unity编辑器中scene视图中选择OverDraw(默认是shaderd),越亮的区域,overdraw越高。

对于降低overdraw,有这么几种办法: (1)全屏遮挡的情况,则为被遮挡的canvas添加CanvasGroup组件,然后在被挡住时将其alpha值设为0,就不会传给GPU渲染了。Canvas Group还有一些其他设置,意味着该节点以及所有的子节点都可以统一地更改一些行为。

Blocks Raycasts是一个很有用的设置,取消勾选,则意味着该canvas里面的UI都不会接收点击等事件了。

Ignore Parent Group如果勾选,则表明该canvas无视上层CanvasGroup的设置。

(2)非全屏遮挡,这就要从美术资源上想办法。

上面提到了SR的网格是自动生成的。其实,Unity自带的SR网格生成算法并不好,更好的算法。

见这里
参考资料
原文链接
非连通sprite mesh