【Unity优化】DrawCall

【Unity优化】DrawCall 【总结,来自Unity Documentation】 什么是DrawCall 简单翻译即为绘制调用。指的是CPU对图形库接口的调用。 在现代绘制流水线过程中,CPU与GPU协同并行工作来提升效率。CPU将各种所需要的信息和命令发送到缓冲区,GPU接收并执行命令。在GPU执行命令的同时,CPU可以继续在缓冲区中添加命令。GPU完成一个命令之后,会在缓冲区中取出一个命令继续执行。 [caption id=”attachment_1135” align=”aligncenter” width=”680”] = =写字母避免暴露字丑(并没有…… = =写字母避免暴露字丑(并没有……[/caption] DrawCall就是一种命令。 DrawCall对效率的影响 CPU在每次调用DrawCall命令之前,需要进行很多准备工作,比如检查渲染状态,提交渲染所需要的数据。完成了这些准备工作之后GPU就会开始本次的渲染。由于GPU渲染速度较快,往往快于CPU准备工作的速度。当DrawCall的次数过多时,CPU需要准备大量工作,而此时GPU则可能没有且无法执行命令,即GPU存在大量空闲。 提交大量较小的DrawCall会造成CPU性能瓶颈。 优化方向和思路 综上所述,减少DrawCall的调用次数,将较小的,使CPU和GPU保持并行计算,让GPU在单位时间内充分利用,极大提高整体效率。 注意 如果合并大量DrawCall导致CPU要传输的资源过多甚至大于总线带宽时,GPU依然只能等待。而且执行合并操作也会占用CPU内存。所以DrawCall并非越小越好,要保持与其他相关属性的平衡。 Unity使用的两种方法 动态批处理 静态批处理 批处理材质设置 只有游戏对象使用相同的材质才可以合并。 如果两个材质只有纹理不一样,可以合并两个纹理生成一个纹理图集(Texture Atlas),当纹理在用一个纹理图集中,两个材质就可以改为只用一个材质。 在脚本中访问这种共享材质时,Renderer.material会生成一个副本,使用Renderer.sharedMaterial会保持只有一个共享材质。 而即使是不同材质,只要阴影通道所需的材质值相同,阴影投射也可以使用动态批处理。 动态批处理(网格) Unity会自动的将使用相同材质并满足合并标准的游戏对象移动到同一个DrawCall中。比如添加1个默认cube和添加若干个默认cube,在Profiler中可以看到DrawCall的次数并没有增加。 批处理对GameObject的每一个顶点都有一定的开销,超过900个顶点属性和超过300个顶点的网格不适用动态批处理。如果着色器使用了顶点位置、法线、UV0、UV1和切线就只能支持到180个顶点。 在transform中有镜像,如scale中一个为+1一个为-1,也不会使用动态批处理。 多通道的着色器会终止批处理。预计算光照渲染路径需要绘制两次游戏物体,会禁用动态批处理。 动态批处理的工作原理是在CPU上将所有GameObject顶点转换为世界空间,因此只有当工作量小于DrawCall时才有优势。DrawCall的资源需求取决于许多因素,主要原因是图形API的使用。例如,在控制台或一些现代API上,DrawCall开销通常要低得多,这种情况下动态批处理通常没有什么优势。 动态批处理(粒子系统、Line Renderers、Trail Renderers) 对于Unity动态生成的几何组件,动态批处理的工作方式与对网格的工作方式不相同。 对于每个兼容的渲染器类型,Unity将所有可批处理的内容构建到一个大的顶点缓冲区中。 渲染器会为批处理设置材质状态。 Unity将顶点缓冲区绑定到图形设备。 对于批处理中的每个渲染器,Unity将把offset更新到顶点缓冲区(VB),然后提交一个新的DrawCall。 图形设备调用的成本中,设置材质状态是最慢的,将不同offset的DrawCall提交到共享顶点缓冲区中非常快。 这种方法非常类似于Unity在使用静态批处理时提交DrawCall的方式。 静态批处理 静态批处理允许引擎减少对任何尺寸的几何图形的DrawCall,前提是共享相同的材质,并且不移动。它不在CPU上进行顶点转换,它通常比动态批处理更有效,但是使用更多的内存。为了利用静态批处理,需要显式地指定GameObjects是静态的,并且在游戏中不移动、旋转或缩放。使用检查器中的静态复选框将GameObjects标记为静态的(static)。 使用静态批处理需要额外的内存来存储组合的几何图形。如果多个GameObject在静态批处理之前共享相同的几何体,那么将在编辑器或运行时为每个GameObject创建一个几何体副本。但是有时为了保持更小的内存占用,不得不牺牲渲染性能,需要避免对某些GameObject进行静态批处理。例如,”森林”类的游戏对象中有很多”树”,将”树”标记为静态会对内存产生严重影响。在内部,静态批处理的工作原理是将静态游戏对象转换到世界空间,并为它们建立一个共享的顶点和索引缓冲区。如果启用了优化的Mesh__ Data__(在玩家设置中),那么在创建顶点缓冲区时,Unity会删除没有被任何着色器变量使用的顶点元素。有一些特殊的关键字检查来执行。例如,如果Unity没有检测到LIGHTMAP_ON关键字,它将从批处理中删除lightmap UVs。然后,对于同一批处理中的可见GameObjects, Unity执行一系列简单的DrawCall,每个调用之间几乎没有状态变化。从技术上讲,Unity并不保存API 绘制调用,而是保存它们之间的状态更改(对于资源密集型部分)。大多数平台上的批处理限制是64k个顶点和64k个索引(OpenGL ES上是48k个索引,macOS上是32k个索引)。 Tips 目前,蒙皮网格、布和其他类型的渲染组件没有批处理。 渲染器只与其他相同类型的渲染器进行批处理。 半透明着色器通常要求GameObject按back-to-front顺序渲染。Unity首先按照这个顺序对GameObjects进行排序,然后尝试对它们进行批处理,但由于必须严格满足顺序,这通常意味着与不透明的GameObjects相比,半透明渲染器可以实现的批处理更少。

Author: 木尘痕
Link: https://muchenhen.com/2019/03/16/%E3%80%90unity%E4%BC%98%E5%8C%96%E3%80%91drawcall/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.