Graphics Performance:Draw Call Batching

绘制一个对象到 Screen 上,引擎会对图形 API (如 OpenGL 或 Direct3D)发出一次绘画调用(Draw calls),Draw calls 是昂贵的,每个DrawCall 与图形 API 一起做了大量的工作,导致在 CPU 上的性能开销,这主要是由 DrawCall 之间的状态更改引起的(如切换不同的材质),DrawCall 导致了在图形驱动中昂贵的验证和变换过程。

Unity 使用几点技巧来解决这个问题:

  1)Static Batching(静态组合),将静态的对象(如不移动的)合并到一个大的 Meshes,更快速渲染。

   2)Dynamic Batching(动态组合),对于足够小的 Meshes,在 CPU 上做一些顶点变换,将相似的网格体分组到一起,然后一起绘制。

内置的 Batching 相对于手动合并物体到一起有几点好处,最显著的是被合并的物体仍然能够单独的被剔除,但它也有一些弊端,静态合并(static batching)会导致一些内存和存储开销,而动态合并(dynamic batching)会导致一些 CPU 开销。

Batching 的材质设置:

只有共享相同材质的对象才能被合并到一起。因此,如果你想实现不错的 Batching,你必须在不同的物体间共享尽可能多的材质。

如果你有两个完全一样的 Materials,只是纹理贴图不一样,你可以合并那些纹理贴图到一个更大的贴图上(通常称为纹理图集,texture atlas)。一旦纹理在同一个图集,你就可以使用单个 Material 替代。

如果你需要从脚本访问共享 Material 的属性,重点注意的是 修改  Renderer.material 将会创建一个 Material 的复制。相反,你应该用 Renderer.sharedMaterial 保持 Material 共享。      

当渲染阴影投射(shadow caster)的时候,即使是 Materials 不同也会经常合并到一起。Unity 中的阴影投射甚至能用不同的 Material 进行动态合并,只要阴影通道中所需的材质属性是相同的。例如许多箱子可以使用不同的纹理材质,但对于渲染纹理阴影投射(shadow caster)是不相关的,在这种情况下可以使用批处理(Batching)。
      
Dynamic Batching(动态批处理):

Unity 能将那些共享相同 Material 和履行其他标准的物体自动合并动态到同一个DrawCall,动态合并(Dynamic Batching)是自动完成的并且不会要求一些附加的工作量。

  • 合并动态物体对每个顶点具有一定的开销,所以合并只仅仅用于包含 共少于 900 个顶点属性的 Mesh 。
        如果你的 Shader 用于顶点位置、法线和单个 UV ,你可以合并到最多 300 个网格所有顶点,反之,如果你的 Shader 用于顶点位置、法线、UV0、UV1和正切函数,你可以合并到最多 180 个网格所有顶点。         注意:属性数量的限制有可能在未来被改变。
  • 如果物体在变换时包含镜像,将不会变换。例如放大1倍的物体A 与缩小1倍的物体 B不能合并到一起。
  •  使用不同的 Materail 实例,即使它们本质上是相同的,也不能合并到一起,除非是阴影投射渲染(shadow caster rendering)。
  •  物体带有光照贴图(lightmaps)的附加渲染属性:光照贴图指数和光照贴图的偏移/大小(offset/scale),所以一般动态烘焙的对象应该指向完全相同的光照贴图的位置进行批处理(Batching)。
  • 多通道 Shader 将打破 Batching。

        几乎所有的 Unity Shaders 都支持几个灯光的正向渲染,有效地为他们做更多的 Pass 处理,附加像素光照(additional per-pixel lights)中的 DrawCalls 将 不会被 Batching。
        延迟渲染(Legacy Deferred)的光照模式(light pre-pass)渲染路径已被动态 Batching 禁止,因为它会对物体绘制两次。

从它在CPU 变换所有对象的顶点到世界坐标上,对于一个 DrawCall 绘制来说只是一个小的胜利,究竟一个 DrawCall 有多昂贵取决于很多因素,主要在图形 API 的使用上,例如,在控制台或者现代 API 如 Apple 的 Metal 上,绘制开销通常低的多,经常的 Dynamic Batching 并不能起多大作用。

Static Batching(静态批处理):
      
静态 Batching 允许引擎为任何尺寸的几何体(只要不是移动的且共享材质)减少 DrawCall ,它比动态 Batching 更高效,它不会在 CPU 上进行顶点变换,但会耗费更多的内存。
为了是静态 Batching 更加高效,你必须明确指定确定的游戏对象是 Static ,并且将不会在游戏中进行移动、旋转和缩放变换。因此你可以在Inspector面板勾选Static标记。

使用静态 Batching 需要附加的内存存储合并的几何体。如果几个游戏对象在 Static Batching 之前共享相同的几何体,那么在 Editor 或者 运行时,将会为每个对象创建一个几何体的复制。这将不总是一个很好的办法,有时你为了通过避免 Static Batching 来牺牲渲染性能保持较小的内存空间占用。例如,在茂密树林关卡中使用静态树木会对内存有严重的影响。

在内部,静态 Batching 的工作原理是把静态对象变换到世界空间,然后为它们建立一个大的顶点索引缓冲区( vertex + index buffer)。 对于在同一个Batch中的 可见物体,一系列廉价的 DrawCall 将被完成,它们之间几乎没有任何状态改变。所以严格说它不会节省“3D API 绘制调用(DrawCall)”,但它节省了它们之间状态变换所做的更改(这是极其昂贵的)。

      
其他 Batching 相关提示

        目前,只有网格渲染(Mesh Renderers)将会被 Batched。这意味着蒙皮网格(Skinned Meshes)、布料模拟(Cloth)、路径渲染(Trail Renders)和其他类型的渲染组件不会被 Batched。

        半透明材质(Semitransparent shaders)经常要求对象按照透明度从后向前的次序进行渲染,Unity 按此顺序获取第一个命令对象,然后尝试 Batch 它们。但因为该顺序是严格遵守的,这意味着更少的 Batching 比不透明的对象更容易实现。

        手动合并相临近的游戏对象对于 DrawCall Batching 来说是很好的选择,例如,将带有很多细节的静态抽屉合并到一个 Mesh 是有意义的,无论是在 3D 模型应用中还是使用 Mesh.CombineMeshes

猜你喜欢

转载自avi.iteye.com/blog/2345135