关于DrawCall和Batches
一个游戏如果要运行流畅,需要保持FPS在30次以上(FPS是指游戏运行时的帧率,表示每秒要绘制多少帧)。而每一帧的渲染不是一次就能完成的,而是需要多次渲染进行叠加(CPU告诉GPU场景中需要渲染哪些物件,以及物件的材质,顶点,图片等信息,一帧当中每次的渲染都是一次DrawCall)。
Batches代表每帧有多少次DrawCall(即每帧渲染多少次) 从上面的FrameDeBug可以看到Batcher的DrawCall具体的信息, 流程大概如下: 1.绘制前的每次Clear清楚数据都会占用一次DrawCall, 2.相机对每个物体进行深度的drawcall, 3.相机进行shadows阴影的绘制, 4.材质的绘制,天空盒,以及图像处理, 5.UGUI的绘制。
DrawCall一般包含了要画什么(点/线/三角形),顶点数据在哪里(显存地址),是怎么组织的。图形API把DrawCall丢给驱动,驱动丢给GPU前端,GPU开始解释并执行。
当对象,材质和纹理越多,处理起来需要的时间也越多。所以过多的drawcall就会影响游戏的优化,这对于瓶颈在GPU上的游戏影响特别大,也就是我们的游戏已经给GPU太大的压力了。
单个DrawCall的代价本身并不大,但提交的任务可大可小,如果用大量的DrawCall每次提交少量任务,那么问题就来了,原因在于GPU处理任务的速度可能会大于CPU提交任务的速度,导致pipeline经常空闲下来。
要优化有两种办法,一是合并任务,减少提交次数,二是改革图形API和驱动,进一步减少单个DrawCall的代价,比如避免频繁的context switch而是采用多个任务提交队列,并发挥多核CPU优势,优化提交任务速度。
合并任务的技术就是批处理,这也是unity里面提供给我们的比较好的方法。
批处理
1.批处理的目的就是为了减少DrawCall。DrawCall即CPU命令GPU去绘制。
2.如果我们需要渲染一千个三角形,那么把它们按一千个单独的网格进行渲染所花费的时间要远大于直接渲染一个包含了一千个三角形的网格。
3.要想使用批处理,需要物体有相同的材质。这是因为,对于使用同一个材质的物体,它们的不同仅仅在于顶点数据的差别,我们可以把这些顶点数据合并在一起,再一起发送给GPU,就可以完成一次批处理。
4.在unity中,有两种批处理:一是动态批处理,二是静态批处理。
对于动态批处理,unity会自动完成,不需要我们进行操作,而且物体是可以移动的,但是动态批处理有许多限制条件。
对于静态批处理,物体不可移动,但是限制条件很少。
静态批处理
只在运行开始阶段,把需要进行静态批处理的模型合并到一个新的网格中,这意味着这些模型数据不可以在运行时刻被移动。因为它只需要进行一次合并操作,因此比动态批处理更加高效。
如果我们在运行时要添加静态对象,可以看一下 StaticBatchUtility.Combine() 的API。
限制: 标记为static后不能被移动。
如果标记为static后修改了位置,那批处理就会失效。因为静态批处理相当于一次预先处理,之后运行时都采用这些信息,他是一次性的,如果修改了位置,就会需要重新计算。
静态批处理
每一帧把可以进行批处理的模型网格进行合并,再把合并后模型数据传递给GPU,然后使用同一个材质对其渲染。
限制: 1.顶点属性要小于900。例如,如果shader中需要使用顶点位置、法线和纹理坐标这三个顶点属性,那么要想让模型能够被动态批处理,它的顶点数目不能超过300。因此,优化策略就是shader的优化,少使用顶点属性,或者模型顶点数要尽可能少。 2.多Pass的shader会中断批处理。 3.使用光照纹理的物体需要小心处理。为了让这些物体可以被动态批处理,需要保证它们指向光照纹理中的同一位置。
动态批处理测试
处理前
图中有四个相同材质的方块,可以看到Batches为32,saved by batches为0,没有启用批处理。 查看Frame Debug的绘制过程可以详细的查看所有的绘制过程。可以看到4个立方体都进行了单独的DrawCall渲染。
运行前标记为Static
将四个物体标记为Static。
可以看到Batches变成23了,同时Saved by batching值为9。 两者相加刚好为未开启批处理前的32。 从Frame Debug可以看到四个Cude的绘制合并为了一个StaticBatch,DrawCall明显减少。
运行时添加静态对象
有时候我们可能需要在载入场景的时候动态创建一些物体,因此就需要在脚本里添加静态对象。
动态批处理测试
待续…
|