可以简单的理解为:批量渲染是通过减少CPU向GPU发送渲染命令(DrawCall)的次数,以及减少GPU切换渲染状态的次数,尽量让GPU一次多做一些事情,来提升逻辑线和渲染线的整体效率。但这是建立在GPU相对空闲,而CPU把更多的时间都耗费在渲染命令的提交上时,才有意义。
合批最重要的前提:材质必须相同!!! 合批是节省了CPU的相关准备工作的工作量。
合批后,经过VS,PS,尝试测试,模板测试后,此时已没有了纹理,顶点,索引的概念,只剩下一个个孤立的像素,各像素间没有任何关系了。像素送到GPU后进行批量处理,呈现到屏幕硬件上。因此合批与GPU没有任何关系,也几乎没有影响。不管是一批还是多批,最终在此帧送到GPU的像素数量是相等的,数据是相同的。分成多批,是一帧内将像素数据分多次提交给GPU。合批与否,对GPU的影响仅是像素到达的慢了还是快了,几乎不影响GPU的性能。
一、离线合批(Offline Batch)
离线合批就是在游戏运行前,先用工具把相关资源做合批处理,以减轻引擎实时合批的负担。 适合离线合批的是静态模型和场景物件。如场景地表装饰面:石头/砖块等等。 离线合批方式有:
- 美术利用专业建模工具合批。如3D Max/Maya等。
- 利用引擎插件或工具。如Unity的插件MeshBaker和DrawCallMinimizer,可以将静态物体进行合批。
- 自制离线合批工具。如果第三方插件无法满足项目需求,就要程序专门实现离线合批工具
在meshoptimizer开源库中就有非常好的mesh合并,先合并同一种Materials,删除冗余数据然后同一种Materials的mesh进行合并!
二、实时合批(Runtime Batch)
Unity引擎内建了两种合批渲染技术:Static batching(静态合批)和Dynamic batching(动态合批)。
第一 静态合批; 相同材质而且不能发生变换(旋转平移等等) 第二 动态合批; 第三 实例化渲染; Unity有两种合批,动态和静态,静态本质就是对标记为static的Mesh自动合并,没有任何区别。而动态合批则是将数份Mesh的数据复制粘贴到一起,也就是实时的,每一帧都合并。 所以一句话总结动态合批:用复制数据的性能消耗换取提交Drawcall的性能消耗。而现代计算机拥有更加优秀的硬件和API,所以动态合批在新设备上已经呈现负优化的趋势了。官方也注意到了这一点所以开发了新的SRP Batcher系统,SRP Batcher的思想在于放弃对模型的合并,转而利用现代API“提交渲染请求不昂贵,提交渲染所需数据和改变渲染状态更昂贵”这样的特性,提供了SRP Batcher: 最后总结一下:静态合批就是Mesh合并,动态合批是每一帧都进行一遍的Mesh合并。
静态合批的利弊:
静态合批采用了以空间换时间的策略来提升渲染效率。
其优势在于:网格通常在预处理阶段(打包)时合并,运行时顶点、索引信息也不会发生变化,所以无需CPU消耗算力维护;若采用相同的材质,则以一次渲染命令,便可以同时渲染出多个本来相对独立的物体,减少了DrawCall的次数。在渲染前,可以先进行视锥体剔除,减少了顶点着色器对不可见顶点的处理次数,提高了GPU的效率。
其弊端在于:合批后的网格会常驻内存,在有些场景下可能并不适用。比如森林中的每一棵树的网格都相同,如果对它采用静态合批策略,合批后的网格基本等同于:单颗树网格 x 树的数量,这对内存的消耗可能就十分巨大了。
总而言之,静态合批在解决场景中材质基本相同、网格不同、且自始至终都保持静止的物体上时,很适用。
动态合批与静态合批的区别:
1、动态合批不会创建常驻内存的“合并后网格”,也就是说它不会在运行时造成内存的显著增长,也不会影响打包时的包体大小; 2、动态合批在绘制前会先将顶点转换到世界坐标系下,然后再填充进顶点、索引缓冲区;静态合批后子网格不接受任何变换操作,仅手动合批后的Root节点可被操作,因此静态合批的顶点、索引缓冲区中的信息不会被修改(Root的变换信息则会通过Constant Buffer传入); 3、因为2的原因,动态合批的主要开销在于遍历顶点进行空间变换时的对CPU性能的开销;静态合批没有这个操作,所以也没有这个开销; 4、动态合批使用根据渲染器类型分配的公共缓冲区,而静态合批使用自己专用的缓冲区。
GPU Instancing
GPU Instancing 就是常说的实例化没有动态合批那样对网格数量的限制,也没有静态网格那样需要这么大的内存,它很好的弥补了这两者的缺陷,但也有存在着一些限制,我们下面来逐一阐述。与动态和静态合批不同的是,GPU Instancing 并不通过对网格的合并操作来减少Drawcall,GPU Instancing 的处理过程是只提交一个模型网格让GPU绘制很多个地方,这些不同地方绘制的网格可以对缩放大小,旋转角度和坐标有不一样的操作,材质球虽然相同但材质球属性可以各自有各自的区别。如下图两个图如果使用mesh合并用处不是很明显!如果像这样绘制模型的大量实例(Instance),你很快就会因为绘制调用过多而达到性能瓶颈。与绘制顶点本身相比,使用glDrawArrays或glDrawElements函数告诉GPU去绘制你的顶点数据会消耗更多的性能,因为OpenGL在绘制顶点数据之前需要做很多准备工作(比如告诉GPU该从哪个缓冲读取数据,从哪寻找顶点属性,而且这些都是在相对缓慢的CPU到GPU总线(CPU to GPU Bus)上进行的)。所以,即便渲染顶点非常快,命令GPU去渲染却未必,而且大量的顶点顶点着色器MVP变换等压力也挺大的!如果我们能够将变换处理好的数据一次性发送给GPU,然后使用一个绘制函数让OpenGL利用这些数据绘制多个物体。这就是实例化(Instancing)。 实例化这项技术能够让我们使用一个渲染调用来绘制多个物体,来节省每次绘制物体时CPU -> GPU的通信,它只需要一次即可。如果想使用实例化渲染,我们只需要将glDrawArrays和gl DrawElements的渲染调用分别改为glDrawArraysInstanced和glDrawElementsInstanced就可以了。这些渲染函数的实例化版本需要一个额外的参数,叫做实例数量(Instance Count),它能够设置我们需要渲染的实例个数。这样我们只需要将必须的数据发送到GPU一次,然后使用一次函数调用告诉GPU它应该如何绘制这些实例。GPU将会直接渲染这些实例,而不用不断地与CPU进行通信。
这里有500+的cube其实都是在同一个cube复制出来只需要记录一个Transform就可以啦,给他一个变换矩阵、可以是缩放、平移、旋转等等;包括gltf里面也可以直接实现实例化! 水就是用了实例化技术translation里做了平移。更好的学习例子openglleran 实例化小行星带
参考资料: 游戏图形批量渲染及优化:Unity静态合批技术 动态合批和静态合批的区别 实例化
|