静态批处理/动态批处理/GPU Instancing /SRP Batcher
2021年7月29日 更新
开启更多功能,提升办公效能

基于Unity引擎

https://www.sohu.com/a/364110559_667928

https://zhuanlan.zhihu.com/p/98642798

  • 静态批处理(Static batching)--静态物件

不减少DC,由于编辑器的算法,会显示减少

优点:提前进行空间变化,减少运行时cpu的变换计算;且子模型之间共享材质,所以在多次DC调用之间并没有渲染状态的切换。

缺点:包体增大,运行时内存体积增大。

原理:在打包时对相同材质静态物体进行的Vertex buffer和Index buffe进行提取,然后变换到世界空间下。在后续的绘制过程中,一次性提交整个合并模型的顶点数据,根据引擎的场景管理系统判断各个子模型的可见性。然后设置一次渲染状态,调用多次Draw call分别绘制每一个子模型。


标记为Static的静态物件(判断条件),在使用相同材质球的条件下,在Build(打包)的时候Unity会自动提取这些共享材质的静态模型的Vertex buffer和Index buffer(索引缓冲,给顶点索引,渲染的时候不用重复调用)。根据最终位置将模型的顶点数据变换到世界空间下,存进更大的Vertex buffer和Index buffer中。并记录每个子模型的并且记录每一个子模型的Index buffer数据在构建的大Index buffer中的起始及结束位置。

一次性提交整个合并模型的顶点数据,根据引擎的场景管理系统判断各个子模型的可见性。然后设置一次渲染状态,调用多次Draw call分别绘制每一个子模型。

  • 失效条件
  • 无法参与批处理情况
  1. 改变Renderer.material将会造成一份材质的拷贝,因此会打断批处理,你应该使用Renderer.sharedMaterial来保证材质的共享状态。
  • 相同材质批处理断开情况
  1. 位置不相邻且中间夹杂着不同材质的其他物体,不会进行同批处理,这种情况比较特殊,涉及到批处理的顺序,我的另一篇文章有详解。
  1. 拥有lightmap的物体含有额外(隐藏)的材质属性,比如:lightmap的偏移和缩放系数等。所以,拥有lightmap的物体将不会进行同批处理(除非他们指向lightmap的同一部分)。


  • 动态批处理(Dynamic batching)--小物件

优点:会降低DC数量

缺点:额外CPU性能消耗。所以对比一下性耗再做

原理:相当于减少CPU往GPU传递的时候的绘制命令(DC),以及GPU从VRAM里拿顶点数据的次数(一起拿)

在使用相同材质球的情况下,Unity会在运行时对于正在视野中的符合条件的动态对象在一个Draw call内绘制,所以会降低Draw Calls的数量。在进行场景绘制之前将所有的共享同一材质的模型的顶点信息变换到世界空间中,然后通过一次Draw call绘制多个模型,达到合批的目的。模型顶点变换的操作是由CPU完成的,所以这会带来一些CPU的性能消耗。并且计算的模型顶点数量不宜太多,否则CPU串行计算耗费的时间太长会造成场景渲染卡顿,所以Dynamic batching只能处理一些小模型。

对比:Dynamic batching相对于Static batching不需要预先复制模型顶点,所以在内存占用和发布的程序体积方面要优于Static batching。但是Dynamic batching会带来一些运行时CPU性能消耗,Static batching在这一点要比Dynamic batching更加高效。

打破:详见链接

  • 失效条件
  • 无法参与批处理情况
  1. 物件Mesh大于等于900个面。
  1. 代码动态改变材质变量后不算同一个材质,会不参与合批。
  1. 如果你的着色器使用顶点位置,法线和UV值三种属性,那么你只能批处理300顶点以下的物体;如果你的着色器需要使用顶点位置,法线,UV0,UV1和切向量,那你只能批处理180顶点以下的物体,否则都无法参与合批。
  1. 改变Renderer.material将会造成一份材质的拷贝,因此会打断批处理,你应该使用Renderer.sharedMaterial来保证材质的共享状态。
  • 批处理中断情况
  1. 位置不相邻且中间夹杂着不同材质的其他物体,不会进行同批处理,这种情况比较特殊,涉及到批处理的顺序,我的另一篇文章有详解。
  1. 物体如果都符合条件会优先参与静态批处理,再是GPU Instancing,然后才到动态批处理,假如物体符合前两者,此次批处理都会被打断。
  1. GameObject之间如果有镜像变换不能进行合批,例如,"GameObject A with +1 scale and GameObject B with –1 scale cannot be batched together"。
  1. 拥有lightmap的物体含有额外(隐藏)的材质属性,比如:lightmap的偏移和缩放系数等。所以,拥有lightmap的物体将不会进行批处理(除非他们指向lightmap的同一部分)。
  1. 使用Multi-pass Shader的物体会禁用Dynamic batching,因为Multi-pass Shader通常会导致一个物体要连续绘制多次,并切换渲染状态。这会打破其跟其他物体进行Dynamic batching的机会。
  1. 我们知道能够进行合批的前提是多个GameObject共享同一材质,但是对于Shadow casters的渲染是个例外。仅管Shadow casters使用不同的材质,但是只要它们的材质中给Shadow Caster Pass使用的参数是相同的,他们也能够进行Dynamic batching。
  1. Unity的Forward Rendering Path中如果一个GameObject接受多个光照会为每一个per-pixel light产生多余的模型提交和绘制,从而附加了多个Pass导致无法合批,如下图:

可以接收多个光源的shader,在受到多个光源是无法合批


  • GPU Instancing-优化合批

优点:GPU Instancing可以规避合并Mesh导致的内存与性能上升的问题,但是由于场景中所有符合该合批条件的渲染物体的信息每帧都要被重新创建,放入“统一/常量缓冲器”中

限制:碍于缓存区的大小限制,每一个CBuffer的大小要严格限制。

原理:在使用相同材质球相同Mesh(预设体的实例会自动地使用相同的网格模型和材质)的情况下,Unity会在运行时对于正在视野中的符合要求的所有对象使用Constant Buffer将其位置、缩放、uv偏移、lightmapindex等相关信息保存在显存中的“统一/常量缓冲器”中,然后从中抽取一个对象作为实例送入渲染流程,当在执行DrawCall操作后,从显存中取出实例的部分共享信息与从GPU常量缓冲器中取出对应对象的相关信息一并传递到下一渲染阶段,与此同时,不同的着色器阶段可以从缓存区中直接获取到需要的常量,不用设置两次常量。

打破:详见链接


  • SRP Batcher

优点:与GPU Instancing相比,因为数据不再每帧被重新创建,而且需要保存进“统一/常量缓冲区”的数据排除了各自的位置、缩放、uv偏移、lightmapindex等相关信息,所以缓冲区内有更多的空间可以动态地存储场景中所有渲染物体的材质信息。由于数据不再每帧被重新创建,而是动态更新,所以SRP Batcher的本质并不会降低Draw Calls的数量,它只会降低Draw Calls之间的GPU设置成本。(相当于将多个dc一起提交到GPU)

(让每次batch(大的cbuffer)填满再上传)

(单一物体,例如只有草,insitance快,材质4+后batcher快)

原理:只要物体的Shader中变体一致,就可以启用SRP Batcher加速。它与上文GPU Instancing实现的原理相近,Unity会在运行时对于正在视野中的符合要求的所有对象使用“Per Object” GPU BUFFER(一个独立的Buffer) 将其位置、缩放、uv偏移、lightmapindex等相关信息保存在GPU内存中,同时也会将正在视野中的符合要求的所有对象使用CBuffer将材质信息保存在保存在显存中的“统一/常量缓冲器”中。




Cbuffer

DX10后引入的常量缓冲区,允许着色器常量组合提交,而不是每次调用都传一次,降低着色器的传递的带宽。


动态批处理

基本原理:每一帧把可以进行批处理的模型网格进行合并,再把合并后的模型数据传递给GPU,然后使用同一个材质对其渲染。动态批处理的一个好处是实现方便,另一个好处是,经过批处理的物体仍然可以移动,这是由于在处理每帧时Unity都会重新合并一次网格。

条件限制:

  • 能够进行动态批处理的网格的顶点属性规模要小于900。例如,如果shader中需要使用顶点位置、发现和纹理坐标这3个顶点属性,那么要想让模型能够被动态批处理,它的顶点数目不能超过300。
  • 多Pass的shader会中断批处理。在前向渲染中,我们有时需要使用额外的Pass来为模型添加更多的光照效果,但这样一来模型就不会被动态批处理了。

静态批处理

Unity提供了另一种批处理方式,即静态批处理。相比于动态批处理来说,静态批处理适用于任何大小的几何模式。它的实现原理是,只在运行开始阶段,把需要进行静态批处理的模型合并到一个新的网络结构中,这意味着这些模型不可以在运行时刻被移动。但由于它只需要进行一次合并操作,因此,比动态批处理更加高效。静态批处理的另一个缺点在与,它往往需要占用更多的内存来存储合并后的几何结构。这是因为,如果在静态批处理钱一些物体共享了相同的网格,那么在内存中每一个物体都会对应一个该网格的复制品,即一个网格会变成多个网格在发送给GPU。如果这类使用同一网格的对象很多,那么这就会成为一个性能瓶颈了。例如,如果在一个使用了1000个相同模型的森林中使用静态批处理,那么,就会多使用1000倍的内存,这会造成严重的内存影响。

共享材质

无论是静态批处理还是动态批处理,都要求模型之间需要共享同一个材质。但不同的模型之间总会需要有不同的渲染属性,例如,使用不同的纹理、颜色等。这是,我们需要一些策略来尽可能地合并材质。

如果两个材质之间只有使用的纹理不同,我们可以把这些纹理合并到一张更大的纹理中,这张更大的纹理被称为是一张图集(atlas)。一旦使用了同一张纹理,我们就可以使用同一个材质,再使用不同的采样坐标对纹理采样即可。

但有时,除了纹理不同外,不同的物体在材质上还有一些微小的参数变化,例如,颜色不同、某些浮点属性不同。但是,不管是动态批处理还是静态批处理,它们的前提都是要使用同一个材质。是同一个,而不是使用了同一种Shader的材质,也就是说它们指向的材质必须是同一个实体。这意味着,只要我们调整了参数,就会影响所有使用这个材质的对象,那么想要微小的调整怎么办?一种常用的方法就是使用网格的顶点(最常见的就是顶点颜色数据)来存储这些参数。经过批处理后的物体会被处理成更大的VBO发送给GPU,VBO中的数据可以作为输入传递给顶点着色器,因此,我们可以巧妙地对VBO中的数据进行控制,从而达到不通效果的目的。一个例子是,森林场景中所有的树使用了同一种材质,我们希望它们可以通过批处理来减少draw call,但不同树的颜色可能不同。这么,我们可以利用网格的顶点的颜色数据来调整。



其他

Gpu instancing 要求是同一个mesh。一口气收集所有的obj信息上传上去,材质上的微小差距要写进自带的buffer中