[原创]静态批处理、动态批处理、GPU Instancing

这几个东西的原理是什么? 问过几个经验丰富的大佬,不知是不是这些问题太低端,他们要么不屑于回答,要么直接给个链接。反正就是别问,问就是只可意会,不可言传

没办法,只能去茫茫互联网寻觅答案;当疑惑被解开的时候,那感觉就像星期五下午,明知这个礼拜没得休息,而且晚上要加班。老板突然在群里说一句:最近大家幸苦了,项目进度不错,这周双休,今晚不加班!我特么,我好想反手给老板一………张过去的CD,纪念那时我们的爱情

要回答这个问题,首先得了解何为渲染流水线?渲染流水线的工作任务就是由一个三维场景出发,生成(或者说渲染)一张二维图像,而这个工作通常是由CPU和GPU共同完成的,本文主要讨论的就是渲染流水线中关于CPU的部分。在渲染物体之前,CPU需要为渲染这个物体做准备工作,主要包括如下三个步骤:

Data Transfer (把数据加载到显存中)

在程序开始运行之前所有渲染所需的数据都是保存在磁盘(HDD)上的。程序开始运行,为了CPU能够快速访问数据,需要先把数据载入到内存(RAM)。GPU和CPU一样,也有自己的可以快速访问的类内存结构,叫做显存(VRAM)。当需要渲染该物体时,网格和纹理等数据又被加载到显存中

Set Render States (设置渲染状态)

渲染状态(Render State),通俗的解释就是:这些状态定义了场景中的网格是怎么被渲染的。渲染状态包括 使用哪个顶点着色器/片段着色器、光源,材质,纹理等。在我们调用图形渲染API函数进行绘制之前我们需要设置这些状态值,这些状态值指导 GPU 如何渲染我们传递到显存的模型和纹理数据

Draw Call(调用图形渲染API)

在模型和纹理数据传输到显存,并且设置了正确的渲染状态之后,CPU需要调用渲染API函数通知GPU去执行绘制操作。这个步骤就是传说中的” Draw Call “。因为渲染状态已经通过其他API函数设置好了,所以 Draw Call 仅仅是发送命令给GPU,告诉GPU绘制哪些模型数据

CPU和GPU是并行工作的,他们之间通过命令缓冲区(Command Buffer)这个队列沟通。大概如下:

明白了 Draw Call,我们得了解另外一个概念:批次(Batch), 调用一次渲染API的绘制接口来向GPU提交使用相同渲染状态来渲染物体的行为称为一个渲染批次。从渲染API调用的角度来看,Batch和Draw call是一样的,但是在游戏引擎中他们的实际意义是不一样的,Batch一般指经过打包之后的 Draw call 。如下2种缓冲区命令,你就能明白他们的区别了

渲染状态A > 渲染A >渲染状态B > 渲染B > 渲染状态C > 渲染C Draw Call:3 Batch:3

渲染状态A > 渲染A > 渲染B > 渲染C Draw Call : 3 Batch:1

实际证明,从CPU到GPU最消耗性能的地方是切换渲染状态,而不是 Draw Call 这个操作。也因此,Unity引擎越来越淡化Draw Call,转而用Batchs作为Graphics的性能指标,同时这也是Unity进行批处理的理论依据

Static Batching(静态批处理)

静态批处理的工作原理是将静态游戏对象转换为世界空间,并为它们建立一个共享的顶点和索引缓冲区。 在后续的绘制过程中,根据Unity引擎渲染排序函数,将符合静态合批的对象,一次性提交整个合并模型的顶点数据,然后设置一次渲染状态,调用多次Draw call分别绘制每一个子模型 , 而多次 Draw call 调用之间并没有渲染状态的切换

优点:针对相同材质的静态GameObject,Unity使用静态合批能将相同渲染状态的对象同一批次添加到命令缓冲区供GPU去渲染,从而加速了渲染效率

缺点:会加大运行时的内存以及增加包体大小

为什么会增加包体呢? 答: 因为在Build项目的时候,Unity会把符合静态合批的对象生成一个大的合并网格,这个多出来的网格会导致包体增加

为什么会增加运行时内存呢? 答: 渲染一个对象,CPU会从硬盘拿到渲染这个物体的数据(网格,材质,贴图等)加载到内存中,然后传送到显存中,渲染完成后,自动删除这部分内存。渲染合批的对象时,因为是直接加载Build的时候生成的合并后的大的Mesh,所以运行时的内存就增加了。合并后的网格越大,则运行时增加的内存就越大

Unity还提供了一种灵活度很高的运行时静态合批方法我们可以在运行时调用StaticBatchingUtility.Combine实现将一些模型合并成一个完整模型。使用这种方法我们可以避免最终打包的应用体积增大,但是由于在运行时通过CPU做模型的合并,会到来一次性的运行时内存和CPU开销

Dynamic Batching (动态批处理)

动态批处理 专门为优化场景中共享同一材质的动态GameObject的渲染设计的。为了合并渲染批次, 动态批处理 每一帧都会有一些CPU性能消耗,如果我们开启了 动态批处理 ,Unity会自动地将所有符合条件的共享同一材质的动态GameObject在一个Draw call内绘制。动态批处理 的原理也很简单,在进行场景绘制之前将所有的共享同一材质的模型的顶点信息变换到世界空间中,然后通过一次Draw call绘制多个模型,达到合批的目的

优点:弥补了静态批处理不能处理相同材质的动态对象的不足

缺点:1. 会带来CPU性能消耗。它与静态批处理带来性能优化不一样,动态批处理是将多个对象通过一个Draw Call来渲染。那么就需要在渲染之前,将这些小模型的顶点从本地坐标系转换成世界坐标系,模型顶点变换的操作是由CPU完成的,所以这会带来CPU的性能消耗。计算的模型顶点数量不宜太多,否则CPU串行计算耗费的时间太长会造成场景渲染卡顿。2. 动态批处理有很多严格的限制条件,具体可以参考官网,这里就不一一列举

GPU Instancing

静态批处理和动态批处理,都有自己的优势和劣势。但这些都是在CPU端做的优化,实际上GPU的运行速度是很高的,一次性渲染20个对象和一次性渲染200个对象,几乎是一样的。以上两种提升渲染能力的方法都是针对Command buffer,那我们能不能另辟蹊径,针对GPU来做个优化。比如:一个Draw Call,绘制一个对象;那能不能告诉GPU,一个Draw Call ,给我绘制一百个对象

静态批处理的优先级要比 GPU Instancing 的优先级高,如果一个GameObject被标记为 static 物体并且在Build阶段成功地执行了静态合批,那么如果这个物体还要使用 Instancing Shader 渲染的话,Instancing会失效

动态批处理的优先级要低于 GPU Instancing ,如果一个GameObject使用Instancing渲染的话,那么对于它的Dynamic batching会失效

好了,希望本文也能为你解惑,重拾爱情

Unity3D性能优化——渲染篇

图形渲染及优化—Unity合批技术实践

Unity Shader 入门精要

发表评论

电子邮件地址不会被公开。 必填项已用*标注