美文网首页
Batch, Draw Call, Setpass Call 汇

Batch, Draw Call, Setpass Call 汇

作者: 雄关漫道从头越 | 来源:发表于2020-08-04 21:16 被阅读0次

    Batch, Draw Call, Setpass Call
    Unity3D之DrawCalls、Batches和SetPassCalls的关系

    转发大佬的文章。


    我们都知道,现代PC也好,手机也好,至少有一颗CPU,负责处理综合逻辑运算,还有一颗GPU,负责处理图形运算。当然CPU也能用来处理图形图像,GPU也能用来进行通用计算,但是这并不是我们今天探讨的范畴内。所以,渲染是CPU和GPU各自发挥所长,协作完成的任务。理想状况下,CPU尽最大努力所能提供的渲染指令正好可以被GPU执行,但是这种理想状况几乎是不现实的,绝大多数情况下,二者当中其中一个肯定会遇到瓶颈,使得帧率总有一个上限,或者故意限制帧率,使得二者都不会满负荷运转。

    如果GPU侧遇到了瓶颈,那么我们首先要入手考虑的点就是纹理填充率和显存带宽。纹理填充率主要是GPU渲染像素的速度,实际工作当中需要从shader复杂度,overdraw开销,屏幕分辨率,后处理等着手改善,显存带宽则主要是GPU和显存进行数据交换的速度,实际工作当中需要从顶点数,顶点复杂度,纹理大小,纹理采样数量,后处理等着手改善。如果CPU侧遇到了瓶颈,那么我们要入手考虑的点主要是程序逻辑执行的复杂度,drawcall等着手考虑。而我们今天要谈论的话题,显然就是从CPU侧入手尝试改善渲染效率。

    Set Pass Call代表渲染状态切换,主要出现在材质不一致的时候,进行渲染状态切换。我们知道一个batch包括,提交vbo,提交ibo,提交shader,设置好硬件渲染状态,设置好光源属性等(注意提交纹理严格意义上并不包括在一个batch内,纹理可以被缓存并多帧复用)。如果一个batch和另一个batch使用的不是同种材质或者同一个材质的不同pass,那么就要触发一次set pass call来重新设定渲染状态。例如,Unity要渲染20个物体,这20个物体使用同种材质(但不一定mesh等价),假设两次dynamic batch各自合批了10个物体,则对于这次渲染,set pass call为1(只需要渲染一个材质),batch为2(向GPU提交了两次VBO,IBO等数据)。

    Draw call严格意义上,CPU每次调用图形API的渲染函数(使用OpenGL举例,是glDrawElements或者DrawIndexedPrimitive)都算作一次Draw Call,但是对于Unity而言,它可以多个Draw Call合并成一个Batch去渲染。

    真正造成开销较大的地方,第一个在于在于切换渲染状态,第二在于整理和提交数据。在真正的实践过程当中,可以不用过于介意Draw call这个数字(因为没有提交数据或者切换渲染状态的话,其实多来几个draw call没什么所谓),但是Set Pass Call和Batch两个数字都要想办法降低。由于二者存在强相关性,那么通常降低一个,就一并可以降低第二个。

    Unity提供了三种批次合并的方法,分别是Static Batching,GPU Instancing和Dynamic Batching。它们的原理分别如下:
    Static Batching,将静态物体集合成一个大号vbo提交,但是只对要渲染的物体提交其IBO。这么做不是没有代价。比如说,四个物体要静态批次合并前三个物体每个顶点只需要位置,第一套uv坐标信息,法线信息,而第四个物体除了以上信息,还多出来切线信息,则这个VBO会在每个顶点都包括所有的四套信息,毫无疑问组合这个VBO是要对CPU和显存有额外开销的。要求每一次Static Batching使用同样的material,但是对mesh不要求相同。

    Dynamic Batching将物体动态组装成一个个稍大的vbo+ibo提交。这个过程不要求使用同样的mesh,但是也一样要求同样的材质。但是,由于每一帧CPU都要将每个物体的顶点从模型坐标空间变换到组装后的模型的坐标空间,这样做会带来一定的计算压力。所以对于Unity引擎,一个批次的动态物体顶点数是有限制的。

    GPU Instancing是只提交一个物体的mesh,但是将多个使用同种mesh和material的物体的差异化信息(包括位置,缩放,旋转,shader上面的参数等。shader参数不包括纹理)组合成一个PIA提交。在GPU侧,通过读取每个物体的PIA数据,对同一个mesh进行各种变换后绘制。这种方式相比static和dynamic节约显存,又相比dynamic节约CPU开销。但是相比这两种批次合并方案,会略微给GPU带来一定的计算压力。但这种压力通常可以忽略不计。限制是必须相同材质相同物体,但是不同物体的材质上的参数可以不同。

    所以Unity默认策略是优先static,其次gpu instancing,最后dynamic。当然如果顶点数过于巨大(比如渲染它几千颗使用同种mesh的树),那么gpu instancing或许比static batching是一个更加合适的方案。


    接触过项目优化的人相信对这三个词组不陌生,优化很大部分时间可能最终就是在优化这三个东西,让它们保持在一个相对稳定合理的数值,太高肯定是不好。这三个数值也能直接在Unity上显示出来,在Unity界面Game窗口右上角有一个“Status”按钮,点击打开Statistics窗口,这个渲染统计窗口(或渲染数据统计窗口)展示了图像渲染、网络状况等多种统计信息。在Unity4.x版本,DrawCalls数据就可以在这里看到,但并没有显示SetPassCalls的数据;在Unity5.x版本之后,DrawCalls数据不再显示在这里,取而代之的是SetPassCalls和Batches的数据,SetPassCalls是Unity5.x才出现的,不过DrawCalls的数据依旧可以在Profiler窗口(Window->Profiler)的Rendering或者Frame Debugger(Window->Frame Debugger)中看到,而且在Frame Debugger中还能看到每一帧的渲染数据。

    都知道DrawCalls太高会影响游戏性能,但是究竟DrawCalls是什么,DrawCalls、Batches和SetPassCalls这三者有什么关系,很多人估计很难说清楚,至今Unity官方也没有给出详细的文档。有些地方为了容易理解,简单的把SetPassCalls等同于DrawCalls了,但是这两者却不是一个东西,虽然它们之间确实是有相互影响。

    DrawCall:CPU每次调用图形API的渲染函数,如 glDrawElements(OpenGl中的图元渲染函数)或者 DrawIndexedPrimitive(DirectX中的顶点绘制方法)命令GPU渲染的操作称为一次Draw Call。Draw Call就是一次渲染命令的调用,它指向一个需要被渲染的图元(primitive)列表,不包含任何材质信息,glDrawElements 或者 DrawIndexedPrimitive 函数的作用是将CPU准备好的顶点数据渲染出来。对于Unity而言,它可以多个Draw Call合并成一个Batch去渲染。

    Batch:把数据加载到显存,设置渲染状态,CPU调用GPU渲染的过程称之为一个Batch。这其实就是渲染流程的运用阶段,最终输出一个渲染图元(点、线、面等),再传递给GPU进行几何阶段和光栅化阶段的渲染显示。一个Batch必然会触发一次或多次DrawCall,且包含了该对象的所有的网格和顶点数据以及材质信息。把数据加载到显存是指把渲染所需的数据从硬盘加载到内存(RAM),再将网格和纹理等加载到显卡(VRAM),这一步比较耗时。设置渲染状态就是设置场景中的网格的顶点(Vertex)/片元(Fragment)着色器,光源属性,材质等。Unity提供的动态合批(Dynamic Batching )合并的就是这一过程,将渲染状态相同的对象合并成一个Batch,减少DrawCall。

    SetPassCall:Shader脚本中一个Pass语义块就是一个完整的渲染流程,一个着色器可以包含多个Pass语义块,每当GPU运行一个Pass之前,就会产生一个SetPassCall,所以可以理解为一次完整的渲染流程次数。

    由此可见,一个Batch包含一个或多个DrawCall,都是产生是在CPU阶段,而目前普遍渲染的瓶颈恰恰就是CPU,GPU的处理速度比CPU快多了,Draw Call太高,CPU会把大量时间花费在处理Draw Call调用上。如果Batch太大,CPU需要频繁的从硬盘加载数据,切换渲染状态,这个消耗要比DrawCall大,所以后面Unity才逐渐弱化了DrawCall的显示。


    没有Batch
    Batch

    再提一下,优化的时候还要关注下Statistics窗口上的三角形数(Tris)和顶点数(Verts),这两个数据也是会影响到性能,比如单个物体的顶点数最好不要超过900,不然会影响到Unity的动态合批。Unity的Statistics窗口上的三角形数(Tris)和顶点数(Verts)并不仅仅是视锥中的梯形内的三角形数和顶点数,而是Camera中 field of view所有取值下的三角形数和顶点数。也就是说,即使当前Game视图中看不到这个 cube,只有 field of view在1-179 范围内都看不到这个cube,stats面板才不会统计,GPU才不会渲染,否则都会渲染,而且Unity不会把模型拆分,这个模型哪怕只有1个顶点需要渲染,Unity也会把整个模型都渲出来。

    相关文章

      网友评论

          本文标题:Batch, Draw Call, Setpass Call 汇

          本文链接:https://www.haomeiwen.com/subject/kdjgkktx.html