Unity的静态批处理和动态批处理

什么是批处理

为了将物体绘制到屏幕上,引擎必须向图像API(例如OpenGL、Direct3D)发送一个draw call指令。每一次draw call就可以大致理解为一个渲染批次(batch)。Draw call属于资源密集型的指令,图形API要为每个Draw call做大量的工作。造成CPU性能消耗的主要是渲染状态的切换导致的,例如切换到不同的材质,这会导致在图形驱动中产生密集的资源验证和切换。为了减少draw call的调用,Unity使用了两种技巧来优化这个问题:

  • 动态批处理:对于足够小的mesh,动态批处理通过将他们的顶点整合到一个批次中进行绘制。
  • 静态批处理:通过将不会移动的静态物体合并到更大的mesh中,以提升渲染速度。

与手动合并相比,内置的批处理方式最大的有点在于,每一个物体仍然可以被单独剔除。但是他们也有缺点,静态批处理会消耗更多的内存空间,动态批处理会产生CPU开销。

材质方面的要求

只有使用了相同材质的物体才能够实现批处理。如果两个不同的物体,使用的两个材质,只是纹理上的差别,那就把他们的纹理合并到一起,这样就可以使用同一个材质。脚本中使用Render.material属性时,会重新生成一个原来材质的拷贝,所以用Render.sharedMaterial可以保持材质的一致性(但是使用同一个材质的物体,都会被改变)。关于阴影,只要材质中使用的是相同的Shadow Pass,就可以实现批处理,即便他们不是同一个材质。

静态批处理

静态批处理因为不需要再CPU中进行顶点转换,所以要比动态批处理快的多,但是会占用更多内存。如果多个物体共享同一个材质,使用静态批处理时,会为每个几何体生成一份拷贝,不管是在编辑器还是运行时。所以在内存紧张的环境下,就需要损失一定的渲染性能来避免静态批带来的内存开销。

静态批处理比较简单,只需要将场景种不会动的静态物品设置Batching Static,剩下的就交给引擎了。

静态批处理的原理是将物体转换到世界空间,并为他们建立一个共享的顶点和索引缓存。在Player Setting中勾选了Opeimized Mesh Ddata以后,Unity在构建缓存时,会移除那些在所有shader变体中都没有被用到的顶点的elements(官方文档里是这么说的,不知道该翻译成什么,顶点的属性?)。Unity通过一些特殊的关键字检查来实现这个操作,比如如果没有包含LIGHTMAP_ON,该批处理的顶点输入中就会删除lightmap UVs。

官方文档里有这样一句话:Then, for visible GameObjects in the same batch, Unity performs a series of simple draw calls, with almost no state changes in between each one. 

在同一批次的可见物体中,unity会执行一系列的简单的draw call,且调用之间几乎不会发生状态变化。

可能是我的验证方式有问题,对这句话我表示存疑。

静态批处理,并不是所有的模型都合并到同一个Mesh里,合并的时候仍然受限于mesh的顶点数要求,在大多数平台上,批处理限制是64k个顶点和64k个索引(OpenGLES上48k个索引,macOS上32k个索引)。如果几个模型的顶点总数超过了这个限制,就会被拆分成多个draw call,但是这样的拆分,并不会造成setpass call的变化,因为渲染状态没有变化,所以这些被分开的渲染批次仍然执行的很快。

 

动态批处理

需要重点说的是动态批处理,动态批处理不需要我们做设置,引擎会在条件满足时自动处理,但是动态批处理的条件非常苛刻,任何一点不满足就不能实现批处理。究竟有多么苛刻呢?

注意,接下来的一部分内容将会与你在其他贴子中看到的有相当大的出入。鉴于此,先贴上官方文档(Version:2019.2):

Unity can automatically batch moving GameObjects into the same draw call if they share the same Material and fulfill other criteria. Dynamic batching is done automatically and does not require any additional effort on your side.

  • Batching dynamic GameObjects has certain overhead per vertex, so batching is applied only to Meshes containing no more than 900 vertex attributes, and no more than 300 vertices.
    • If your Shader
       is using Vertex Position, Normal and single UV, then you can batch up to 300 verts, while if your Shader is using Vertex Position, Normal, UV0, UV1 and Tangent, then only 180 verts.
    • Note: attribute count limit might be changed in future.
  • GameObjects are not batched if they contain mirroring on the transform (for example GameObject A with +1 scale and GameObject B with –1 scale cannot be batched together).
  • Using different Material instances causes GameObjects not to batch together, even if they are essentially the same. The exception is shadow caster rendering.
  • GameObjects with lightmaps
     have additional renderer parameters: lightmap index and offset/scale into the lightmap. Generally, dynamic lightmapped GameObjects should point to exactly the same lightmap location to be batched.
  • Multi-pass Shaders break batching.
    • Almost all Unity Shaders support several Lights in forward rendering
      , effectively doing additional passes for them. The draw calls for “additional per-pixel lights” are not batched.
    • The Legacy Deferred (light pre-pass) rendering path
       has dynamic batching disabled, because it has to draw GameObjects twice.

Dynamic batching works by transforming all GameObject vertices into world space on the CPU, so it is only an advantage if that work is smaller than doing a draw call. The resource requirements of a draw call depends on many factors, primarily the graphics API used. For example, on consoles or modern APIs like Apple Metal, the draw call overhead is generally much lower, and often dynamic batching cannot be an advantage at all.

Unity会自动为你完成动态批处理,只要你的东西符合它的要求,要求如下:

  • 对于顶点的要求只有两个:第一单个mesh的顶点数必须小于300,第二:单个mesh全部顶点的属性总个数必须小于900至于300,和180,那只是为了解释这两个要求举的例子!换一种说法就是,如果你的顶点输入中有三个属性,那么就可以支持300个顶点,如果输入中有5个属性,那就只能支持180个顶点,因为180*5=900。至于提到的顶点位置,法线切线UV等等,都只是举个例子。
  • 如果物体做了镜像,也就是缩放中出现了负值,则不会进行批处理。至于都为正数的缩放,测试过程中同时观察了SetPass call和Draw call,都可以正常合并。如果使用较老的版本,建议自行测试。
  • 使用了光照贴图的对象,光照贴图的相关参数必须一致。
  • 多通道(Multi-pass)的材质,如果两个对象的渲染使用的通道(pass)不一致,也会中断批处理。

动态批处理会在每帧都会在CPU中将模型的顶点转换到世界座标系,这部分的消耗也要权衡。

对于在运行时能够生成动态模型的组件(Particle Systems,Line Renderers,Trail Renderers),动态批处理的方式相对于Mesh有所不同。

  • For each compatible renderer type, Unity builds all batchable content into 1 large Vertex Buffer.
  • The renderer sets up the Material state for the batch.
  • Unity binds the Vertex Buffer to the Graphics Device.
  • For each Renderer in the batch, Unity updates the offset into the Vertex Buffer, and then submits a new draw call.
  • 对于每个兼容的渲染器类型,Unity将所有可批处理的内容构建到一个大的顶点缓存中。
  • Renderer为批处理设置材质状态。
  • Unity将顶点缓存绑定到图形设备(显卡?)。
  • 对于批处理中的每个Renderer,Unity会将偏移更新到顶点缓存中,然后提交一个新的绘制调用。

在计算渲染成本时,渲染组件最慢的部分是材质状态的设置。相比之下,将不同偏移量的draw call提交到共享顶点缓存会非常快。这种方法更像是Unity在使用静态批处理时如何提交draw call。

 

还有几点需要注意的:

  • 能够批处理的包括:Mesh Renderers,Trail Renderers,Line Renderers,Particle Systems,Sprite Renderers。其他的如Skinned Meshes,Cloth等渲染组件并能批处理。
  • 半透明物体在渲染时,使用的是从远到近的顺序,所以批处理对它的支持不如Opaque。
  • 手动合并(通过建模软件,或者Mesh.CombineMeshes方法)临近的物体,有时候也是不错的优化方式。

 

 

关于这部分内容的官方文档链接:https://docs.unity3d.com/Manual/DrawCallBatching.html

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章