Batch优化策略-Draw Call Batching

Unity2018版译文 

转载请注明出处 何文西

英文:英文地址

 

在屏幕上绘制一个物体,引擎必须向绘图API(openGL或者D3D)发起一次DrawCall。DrawCall通常是资源密集型的,图形API在每次调用时都要做大量的工作,这会导致CPU方面的性能开销。这主要是由于在DrawCall之间进行的状态切换(例如切换不同的材质),这将导致图形驱动程序中资源密集型的验证和转换步骤。

Unity使用了两种技术来解决这个问题:

  • 动态批处理:对于小的网格,会在CPU上转换它们的顶点,将许多相似的顶点组合在一起,并一次性将它们全部渲染出来。
  • 静态批处理:将静态(不移动)的物体合并到一个大网格中,并以更快的方式渲染它们。

与手动合并GameObjects相比,内置的批处理有几个优点,例如GameObject仍然可以通过相机做裁剪和剔除。当然它也有一些缺点,静态批处理会导致内存存储开销,而且静态批处理的物体在其生命周期中不能移动。动态批处理会导致一些CPU开销。

Note: Dynamic batching is not compatible with graphics jobs (see Player Settings). If graphics jobs are enabled, dynamic batching are disabled in Standalone builds.

 

Material set-up for batching

只有共享相同材质的游戏对象才可以被批处理在一起。因此,如果想要获得理想的批处理,需要尽可能多的不同的游戏对象之间共享材质。

如果你有两个相同材质的物体,只是他们的纹理不同,你可以把这些纹理组合成一个大的纹理。这个过程通常被称为纹理图集。一旦纹理在同一个图集里,你就可以用一种材质了。

如果需要从脚本中访问共享材质的属性,注意修改Renderer.material属性会创建当前材质的一个副本。作为替换方案,应该使用Rednerer.sharedMaterial以保证材质被共享。

在渲染时,即使物体的材质不同,它们的阴影也可以在渲染时被批处理在一起。在Unity中,阴影可以使用动态批处理,即使是不同的材质,只要阴影使用相同的Shadow pass。例如,许多板条箱可以使用不同材质的纹理,但是对于阴影来说,纹理是不相关的,所以在这种情况下,它们的阴影可以被批处理在一起。

 

Dynamic Batching

Dynamic Batching启用时,Unity将尝试自动批量移动物体到一个Draw Call中。要使物体可以被动态批处理,它们应该共享相同的材质,而且还有一些其他限制:

  • 顶点数量:Dynamic Batching场景中物体的每个顶点都有一定的开销,因此批处理只适用于少于900个顶点属性的网格物体。举个例子,如果你的着色器使用顶点位置,法线和一个UV,那么你可以动态批处理多达300个顶点;而如果你的着色器使用顶点位置,法线,UV0,UV1和切线,那么只有180个顶点。值得注意的是,属性计数限制可能会在将来更改。
  • 镜像信息:如果物体包含的Transform具备镜像信息,例如A物体的大小是(1f, 1f, 1f),而B物体的大小则是(-1f, -1f, -1f),则无法做批处理。
  • 材质:如果物体使用不同的材质实例,即使它们本质上相同,也不会被批量处理。而Shadow Caster Rendering是个例外。
  • 渲染参数:拥有光照贴图的物体有其他渲染器参数,例如光照贴图索引或光照贴图的偏移与缩放。一般来说,动态光照贴图的游戏对象应该指向要批量处理的完全相同光照贴图的位置。
  • 不能使用Multi-pass着色器的情况:几乎所有的Unity着色器都支持多个灯光的正向渲染模式(Forward Rendering),这要求额外的渲染次数,所以绘制 “额外的逐像素灯光”时不会被批处理;传统的延迟渲染模式(Light Pre-Pass)不能被动态批处理,因为该技术必须绘制物体两次。

Dynamic Batching通过将所有物体的顶点转换为CPU上的世界空间来工作,所以它只能在渲染Draw Call的工作量小于CPU顶点转换工作量的时候,才会起到提高性能的作用。当用游戏机或如Metal这样的现代API,Draw Call的开销通常低得多,Dynamic Batching就无法提高性能了。了解到以上限制后,如果明智地使用批处理,可以显著提高您游戏的性能。

 

Static Batching

静态批处理适用于任何大小的几何体,只要它们是相同共享材质,且不会移动。大部分情况下他比动态批处理更高效 ,但是它会占用更多的内存。

使用静态批处理,你需要明确的指定游戏中某些物体是静态的而且不会移动,旋转或者缩放。

可以在Inspector界面中的“Static”选项标记物体为static:

使用静态批处理需要额外的内存来存储合并后的几何体。如果几个物体在静态批处理之前共享同一个几何图元,那么这个几何图元将会为每个物体复制一份,无论在编辑器中还是运行时都是如此。这并不是一个好的方法,有时候为了保持更小的内存占用量,必须牺牲渲染性能,避免对某些游戏对象进行静态批处理。例如,在一个茂密的森林中,标记树木为static会产生严重的内存消耗。

在内部,静态批处理的原理是变换这些物体到世界空间,然后建立一个很大顶点和索引缓存。对于同一批次中可见的物体,完成了一系列简单的绘制调用,期间几乎没有状态切换。从技术上来讲,并不能节省3D API的调用,但是节省了它们之间的状态切换(这部分是CPU性能最耗费的地方)。在大多数平台上,批处理被限制为64k个顶点和64k个索引(在OpenGLES上的48k索引,macOS上的32k索引)。

 

Tips

目前,只有Mesh RenderersTrail RenderersLine RenderersParticle Systems and Sprite Renderers可以被批处理。这意味着蒙皮网格、布料和其他类型的渲染组件都没有被批处理。只有相同类型的Renderers可以进行批处理。

半透明着色器通常要求游戏对象以从后到前的顺序进行渲染,以使透明性生效。Unity首先按照这个顺序对游戏对象排序,然后尝试对它们进行批处理,因为这个顺序是严格限制的,这就意味着相对于不透明物体来说,会有很少的batching产生。

手动合并相近的物体可能是一个非常不错的选择。例如,一个有很多抽屉的橱柜通常可以合并成一个单独的网格,可以通过3D建模软件合并或者通过Mesh.CombineMeshaes接口合并。

 

posted @ 2018-05-14 10:42  何文西  阅读(...)  评论(...编辑  收藏