专栏/【Unity】渲染性能优化---经验总结(一)

【Unity】渲染性能优化---经验总结(一)

2021年10月23日 07:48--浏览 · --喜欢 · --评论
粉丝:6087文章:49

工作了一段时间,打算写几篇文章记录下在实际项目中对于渲染部分优化的一些经验之道吧。

渲染性能点的确定

在开发周期比较紧凑的情况下,在不确定性能点的情况下做优化是比较忌讳的,因为你可能优化了很久结果优化的全是不重要的地方,得到的性能提升也很小,这样就浪费了大量的开发时间。

本篇先粗略的介绍一下如何确定渲染性能点以及大致的性能问题产生原因,至于具体的操作与细节会在后面的篇节进行介绍。

1、渲染优化的几大性能点

我们来简单浏览一下渲染的主要过程:

CPU计算和收集渲染所需数据组装描述符和材质--->CPU向GPU传递渲染所需数据--->CPU发起DrawCall--->GPU进行渲染和计算--->在一些情况下GPU会向CPU回传数据(例如一些ComputeShader)

这里首先引出一个关键点:渲染流程中实际上大部分阶段都是需要CPU参与的!!!! 有时程序在定位性能热点时,觉得项目中没有多少GC和复杂运算,那么性能问题就一定是发生在GPU上,实际上不然。就我个人而言,不论是PC、主机还是移动端的GPU,其性能我还是比较自信的,如果没有什么骚操作,GPU中的运算通常不会引发严重的性能问题。

那么根据上面的渲染流程,也就可以得出渲染中的几大性能点:

1、对于 CPU计算和收集渲染所需数据组装描述符和材质 阶段:项目中存在大量零散琐碎的物体;有大量可以共用材质的物体却没有共用材质;有大量复杂的动画运算和蒙皮运算等,这些都会使CPU计算和收集渲染数据的时间延长。

2、对于 CPU向GPU传递渲染所需数据 阶段:该阶段产生的性能问题就是常说的 带宽问题,CPU 与 GPU 用于传递数据的通道,其传输速率有限(而这个传输速率就称为带宽),当一帧内传输的内容大小大于一帧的传输速率时,就会出现传输排队,导致后续渲染延迟。 尤其对于移动端,由于移动端的 CPU 和 GPU 间的带宽本来就较小,且移动端 CPU 和 GPU 都在一块芯片上,共用一整块功率,当出现带宽问题时,使用功率上升,导致手机发热较快。 贴图占用内存过大、网格数据占用内存过大、一些大物体在CPU端视锥体检测无法被筛掉等,这些都会触发带宽问题。

3、对于 CPU发起DrawCall 阶段:这一阶段导致性能问题的就是 DrawCall 的数量 和 DrawCall 本身的复杂度了。DrawCall 就是 CPU 的一种指令,所以其耗时就是本身指令执行的耗时。通常我们认为渲染在GPU上有性能问题时,往往最终是由于 DrawCall 数过多导致GPU开始渲染的时间节点被大幅延迟导致的。该合批的没有合批、该用同一个材质的没有用同种材质、美术资源的制作上导致合批困难、材质有大量的属性和变量等,都会导致 DrawCall 数量的上升 和 DrawCall本身指令的复杂化。

4、对于 GPU进行渲染和计算 阶段:这一阶段才算彻底的走到了GPU的内部,也就是我们觉得要改Shader的时候。但实际上,这一阶段出现的一些问题也不是需要简化Shader运算才能解决的。这一阶段会产生性能问题的主要原因有:渲染顺序的不合理和特效面片的不合理导致过多的Overdraw、Shader中冗余或复杂的计算、贴图或网格数据过大导致运算时加载数据过慢、对于移动端 一些骚操作或者不合理的渲染流程还会触发 TileBase 架构的 GMEM_Load 操作导致渲染过程变慢。

5、对于 在一些情况下GPU会向CPU回传数据 阶段:通常游戏开发中不会遇到这一阶段。对于这一阶段我们只需要留意使用 GPU 做计算功能时,其结果应该尽可能的是留在GPU的内存中,作为GPU后面计算或渲染的输入,如果一定要把结果传给CPU,那就要注意结果的大小,避免产生带宽问题。


2、如何确定渲染性能点是在CPU还是GPU?

通过上面我们知道,对于渲染流程有的过程是在CPU、有的过程是在GPU,因此渲染的性能点也是有的在CPU、有的在GPU。那么究竟怎么确定到底是CPU的问题还是GPU的问题呢?

这里我推荐的是使用 Unity 自带的性能分析器:Profiler (注意:如果游戏目标平台不是PC那么 Profiler 一定要是真机测试!!!)

Profiler 的 Timeline 视图展示了每一帧中 CPU 和 GPU 进行的主要阶段。在一帧中,大致的流程为:CPU执行一系列运算后确定动画、网格和材质信息--->CPU发起DrawCall--->GPU收到DrawCall和数据开始渲染--->在GPU渲染的同时 CPU 可继续向后运行

那么如果在 CPU 发起 DrawCall 之前,GPU 没有正在运行的渲染任务,那么GPU就会处在等待状态。而如果 CPU 在发起 DrawCall 时,GPU还有渲染任务没有处理完,那么 CPU 就会处在等待状态,等待GPU将当前任务完成后再发起DrawCall。(这里是一种简单的说法,对于 Vulkan 和 DX12 这些现代图形API情况会有些不同,但其实也是大同小异)

对于等待的阶段,Profiler 会用 灰色块 表示:

图2.1 灰色块 Gfx.WaitForGfxCommandsFromMainThread 表示当前GPU正在等待CPU
图2.2 灰色块 Semaphore.WaitForSignal 表示当前 CPU 正在等待 GPU

那么如果 GPU 等待 CPU 的 灰色块(如上图2.1 渲染线程的 Gfx.WaitForGfxCommandsFormMainThread),所占时间过长,那就说明 CPU 在渲染阶段运行时间过长,渲染性能点出现在 CPU 端。

而如果 当前帧内 渲染线程(RenderThread) 的开头出现了灰绿色块(如图2.2 渲染线程开头的灰绿色块) 且自身的绿色块也很长一直到排到最后;或者出现了CPU等待GPU的灰色块(如图2.2 的 Semaphore.WaitForSignal) 这就说明 GPU 在渲染时花费了大量时间,甚至在一帧的时间内都没有处理完任务,一直到下一帧还在处理。 也就是说渲染性能点出现在了 GPU 端。

这里也引出了 Profiler 的一个迷惑人的点:当 CPU 等待 GPU 时,Profiler 会一直拉长 CPU 当前所处阶段的时间。例如图2.2,Semaphore.WaitForSingle 上面的 MeshSkinning.Update ,其显示执行了 7.33ms 但实际上它的执行可能只花了 0.2ms,而之后都是在等待 GPU 任务的完成。因此当我们发现 CPU 某些任务执行时间莫名过长时,要检查一下其下面是否有 Semaphore.WaitForSignal 这个灰色块,如果有就说明不是CPU执行这项任务过慢,而是GPU出现了渲染性能问题。


3、CPU端渲染性能热点确定

知道了是CPU还是GPU的问题后我们就来进一步的确定问题。

对于CPU端的渲染性能点,其实只要资源规范设定好,资源创作流水线定制好,渲染流水线设定好,那么就不会有太大的问题。

主要是多大的贴图会产生带宽问题?多少个DrawCall会造成明显卡顿?多少个多少帧的动画或者多少个多少骨骼的角色会造成明显卡顿?这些定量的问题,我还都没有具体的去研究过,平常也没有去记录相关的数据。所以就算拿来一份指标报告,你让我看着这些数据也很难直接的确定出问题 X...X

所以对于这一块我的想法就是做好资源管理和规范,尽量避免出现问题,真出现问题了就用排除法,其它地方没问题,那一定就是这里有问题喽。 所以在后面的资源管理章节我会再细说一这部分。

当然,对于这一块性能热点的确定我也不是完全没有办法,这里我还是推荐使用 Untiy 的 Profiler,其 Timeline 把每个阶段的耗时都标出来了,我们只需要结合经验看看那一块的用时过高就可以确定问题所在啦。

Profiler 显示的 动画计算用时


4、GPU端渲染性能热点确定

这一部分首先根据自身经验和直觉,有些东西是可以直接定位出来的。 如当我们看到项目里有大量特效叠加,且特效面片很大时,就会知道 GPU 渲染时可能产生了大量的 Overdraw,之后在用 Unity 自身的 Overdraw 窗口检查一下,就知道当前 Overdraw 是否需要优化了。

在Unity内检测Overdraw情况,越红表示Overdraw越高。图中的其实不算太高,因为项目这里我已经优化过一波了

而对于渲染顺序引起的Overdraw,如果能和美术制定好流程规范,让美术能够理解 渲染队列、Early-Z、Overdraw 这些规范那就会非常Nice,不然的话就是在美术制作好场景后自己检测一遍然后做修改咯。

而对于其它直观上难以确定的 GPU 性能点,这里如果是移动端我推荐使用 SnapdragonProfiler 进行抓帧分析,其它平台推荐 RenderDoc。SnapdragonProfiler 是高通出的性能分析器,因此其只有搭配使用高通芯片的手机才能完全的发挥它的作用,这里推荐小米和三星的手机。

SnapdragonProfiler
RenderDoc

关于怎么使用 SnapdragonProfiler 来确定 GPU 端 具体的性能点,我会放在后面的抓帧工具篇细说。而至于 RenderDoc 由于我实际上用的不多,所以就先不谈了。


好啦,那么这一篇先到这里吧,后面的篇章我会更加具体的介绍如何确定渲染性能点,以及为什么会产生渲染性能点、怎么解决性能点等等问题~

投诉或建议