思考并回答以下问题:
- DrawCall的汉译是什么意思?主语是谁,对象是谁?
- DrawCall造成的性能问题元凶是CPU。怎么理解?
- 如何在Unity中查看DrawCall?
- 游戏性能的消耗主要是在两方面,一个是CPU,一个是GPU。DrawCall属于哪方面?
DrawCall
绘制调用,谁调用,调用谁,谁执行?
DrawCall是CPU对图形绘制接口的调用,CPU通过调用图形库(DirectX/OpenGL)接口,命令GPU进行渲染操作。
Draw Call与性能
很多时候我们会误以为DrawCall造成的性能问题是GPU切换渲染状态导致,其实这里的元凶是CPU。要理解这一点,我们就需要明白下面2个问题。
- CPU和GPU是如何进行并行工作和交互的?
试想,渲染流程没用采用流水线的工作方式:CPU发送一个渲染命令之后,GPU立即执行渲染命令绘制图形,等到渲染任务结束之后,CPU才可以继续发送下一个渲染命令,这样显然影响工作效率。
采用渲染流水线后,CPU与GPU并行工作,独立而不相互依赖。这是通过命令缓冲区来实现的:命令缓冲区维护一个命令队列,CPU向其中发送命令,GPU从中取出命令并执行。命令有很多种,DrawCall是一种,其他命令还有改变渲染状态、设置渲染数据流等。
这种方式就类似于游戏开发的网络通信:维持一个消息队列,网络线程接收解析消息并将之添加到消息队列,游戏主线程更新时从中取出消息并做派发处理。
- DrawCall是如何影响性能的?
每一次绘制CPU都要调用DrawCall,而在调动DrawCall前,CPU还要进行很多准备工作:检测渲染状态、提交渲染所需要的数据、提交渲染所需要的状态。
而GPU本身具有很强大的计算能力,可以很快就处理完渲染任务。
当DrawCall过多,CPU就会很多额外开销用于准备工作,CPU本身负载,而这时GPU可能闲置了。
做个试验:拷贝1000个总大小1M的文件(1000次准备和收尾工作)和单个大小为1M的文件,明显拷贝1000个文件要慢很多,DrawCall调用和这个很类似。
DrawCall优化:减少DrawCall
既然,我们已经知道DrawCall导致的性能问题在于DrawCall数量过多,那么我们优化的思路就是减少DrawCall。这里我们只讨论批处理(Batching)。
过多的DrawCall会造成CPU的性能瓶颈:大量时间消耗在DrawCall准备工作上。很显然的一个优化方向就是:尽量把小的DrawCall合并到一个大的DrawCall中,这就是批处理的思想。
使用批处理我们需要在CPU和RAM中合并网格,而合并网格本身是需要计算消耗,而且创建新网格也会占用内存。因此批处理的频次不宜太高,不然造成的消耗可能得不偿失。
使用批处理的注意事项:
- 1、合并的网格会在一次渲染任务中进行绘制,他们的渲染数据,渲染状态和shader都是一样的,因此合并的条件至少是:同材质、同贴图、同shader。最好网格顶点格式也一致。
- 2、尽量避免使用大量小的网格,当确实需要时,考虑是否要合并。
- 3、避免使用过多的材质,尽量共享材质。
- 4、网格合并的顶点数量有上限(Unity中好像是65535)。
- 5、合并本身有消耗,因此尽量在编辑器下进行合并。
- 6、确实需要在运行时合并的,将静态的物体和动态的物体分开合并:静态的合并一次就可以,动态的只要有物体发生变换就要重新合并。
Statistics统计面板
Statistics窗口,全称叫做Rendering Statistics Window,即渲染统计窗口(或渲染数据统计窗口),窗口中罗列出关于声音、图像、网络状况等多种统计信息。
FPS:Frames Per Second。1秒=1000毫秒,渲染一帧需要14ms,则1秒大约渲染70帧,70*14 = 980。表示引擎处理和渲染一个游戏帧所花费的时间,该数字主要受到场景中渲染物体数量和GPU性能的影响,FPS数值越高,游戏场景的动画显示会更加平滑和流畅。一般来说,超过30FPS的画面人眼不会感觉到卡,由于视觉残留的特性,光在视网膜上停止后人眼还会保持1/24秒左右的时间,因此游戏画面每秒帧数至少要保证在30以上。另外,Unity中的FPS数值仅包括此游戏Scene里更新和渲染的帧,编辑器中绘制的Scene和其它监视窗口的进程不包括在内。
CPU:获取到当前占用CPU进行计算的时间绝对值,或时间点,如果Unity主进程处于挂断或休眠状态时,CPU time将会保持不变。(计算每一帧的时间。)
Render thread:GPU渲染线程处理图像所花费的时间,具体数值由GPU性能来决定。
Batches:即Batched Draw Calls,是Unity内置的Draw Call Batching技术。
首先解释下什么叫做“Draw Call”,CPU每次通知GPU发出一个glDrawElements(OpenGl中的图元渲染函数)或者DrawIndexedPrimitive(DirectX中的顶点绘制方法)的过程称为一次Draw call,一般来说,引擎每对一个物体进行一次DrawCall,就会产生一个Batch,这个Batch里包含着该物体所有的网格和顶点数据,当渲染另一个相同的物体时,引擎会直接调用Batch里的信息,将相关顶点数据直接送到GPU,从而让渲染过程更加高效,即Batching技术是将所有材质相近的物体进行合并渲染。
对于含有多个不同Shader和Material的物体,渲染的过程比较耗时,因为会产生多个Batches。每次对物体的材质或者贴图进行修改,都会影响Batches里数据集的构成。因此,如果场景中有大量材质不同的物体,会很明显的影响到GPU的渲染效率。这里说几点关于Batches优化相关的方案。
- 虽然Unity引擎自带Draw Call Batching技术,我们也可以通过手动的方式合并材质接近的物体;
- 尽量不要修改Batches里物体的Scale,因为这样会生成新的Batch。
- 为了提升GPU的渲染效率,应当尽可能的在一个物体上使用较少的材质,减少Batches过多的开销;
对于场景中不会运动的物体,考虑设置Static属性,Static声明的物体会自动进行内部批处理优化。
Verts:摄像机视野(field of view)内渲染的顶点总数。
Tris:摄像机视野(field of view)内渲染的的三角面总数量。
◆ Camera的渲染性能受到Draw calls的影响。之前说过,对一个物体进行渲染,会生成相应的Draw call,处理一个Draw Call的时间是由它上边的Tris和Verts数目决定。尽可能得合并物体,会很大程度的提高性能。举个很简单例子,比如场景1中有1000个不同的物体,每个物体都有10个Tris;场景2中有10个不同的物体,每个物体有1000个Tris。在渲染处理中,场景1中会产生1000个Draw Calls,它的渲染时间明显比场景2慢。
◆ Unity stats视图中的Tris和Verts并不仅仅是视锥中的梯形内的Tris和Verts,而是Camera中 field of view所有取值下的tris和verts,换句话说,哪怕你在当前game视图中看不到这个cube,如果当你把field of view调大到179过程中都看不到这个cube,stats面板才不会统计,GPU才不会渲染,否则都会渲染,而且unity不会把模型拆分,这个模型哪怕只有1个顶点需要渲染,unity也会把整个模型都渲染出来。
◆新建一个空的场景,里边没有添加任何物体,为什么Status面板上显示有1.7k Tris以及5.0kVerts。这是因为空的场景自带默认的天空盒。点击Windows---Lighting打开Lighting下的Scene面板,把Skybox里的材质设为空,比如像我下图这样:
可以看到,场景中的Tris数量变为2,Verts数量变为了4,这是由于摄像机存在的关系,删掉它,你就会发现Tris 和 Verts 都变为0了。
★Screen:获当前Game屏幕的分辨率大小,后边的2.1MB表示总的内存使用数值。
★SetPass calls:又碰到一个神奇的词“SetPass calls”。如果你是一个Unity的老用户,你可能会注意到原来的Stats面板的第一项是“Draw calls”,然而到了Unity5.X版本,Stats上没有了“Draw calls”,却多出来一项”SetPass calls“,那么这个玩意到底是做什么的???
之前有讲到Batches,比如说场景中有100个gameobject,它们拥有完全一样的Material,那么这100个物体很可能会被Unity里的Batching机制结合成一个Batch。所以用“Batches”来描述Unity的渲染性能是不太合适的,它只能反映出场景中需要批处理物体的数量。那么可否用“Draw calls”来描述呢?答案同样是不适合。每一个“Draw calls”是CPU发送个GPU的一个渲染请求,请求中包括渲染对象所有的顶点参数、三角面、索引值、图元个数等,这个请求并不会占用过多的消耗,真正消耗渲染资源的是在GPU得到请求指令后,把指令发送给对应物体的Shader,让Shader读取指令并通知相应的渲染通道(Pass)进行渲染操作。
我的上篇博文【U3D】进入shader编程的世界里有简单介绍过Shader,不懂的童鞋可以去看看。接着上边的问题进行说明,场景上有1个gameobject,希望能显示很酷炫的效果,它的Material上带有许多特定的Shader。为了实现相应的效果,Shader里或许会包含很多的Pass,每当GPU即将去运行一个Pass之前,就会产生一个“SetPass call”,因此在描述渲染性能开销上,“SetPass calls”更加有说服力。
★Shadow casters:表示场景中有多少个可以投射阴影的物体,一般这些物体都作为场景中的光源。
★visible skinned meshed:渲染皮肤网格的数量。
★Animations:正在播放动画的数量。
目前渲染统计窗口的参数就只有这些,如果你想了解更多的渲染信息,可以打开Unity的Profiler窗口(右键—AddTab—-Profiler),这儿你可以获取到更多的渲染数据,比如“Draw Calls”、”VBO Totals”、”VB Uploads”等等,还能实时观察CPU、内存和音频的使用情况。