目录
前言:
一、什么是drawcall
二、如何合批
1、什么是合批?
2、静态批处理
1、什么是静态批处理:
2、静态合批的规则
3、动态批处理
4、GPU Instancing
1、GPU instancing的定义
2、编写支持GPU instancing Shader步骤
5、Scriptable Render Pipeline Batch(SRP Batch)
1、SRP Batch的定义
2、SRP Batch工作原理
3、SRP Batch规则
4、支持SRP Batch的管线
总结:
前言:
有时候内存并不是我们的瓶颈,可能渲染drawcall过多是我们的压力,drawcall过多的最直接后果是程序可能卡,耗电量大等表现。
移动端的程序不比PC端,留给程序员的优化空间比较小,一般对于相应的渲染指标都比较苛刻。那么设定多少个Drawcall是一个比较合理的标准呢,一般情况下,我们应该尽可能将控制在Drawcall在200平均值一下,对于效果特别要求的可以在250左右,不建议超过这个数
在讲解drawcall之前,我们先讲解下什么是Drawcall
一、什么是drawcall
drawcall是cpu对图形绘制接口的调用,CPU通过调用图形库(directx/opengl)接口,命令GPU进行渲染操作。
CPU和GPU之间的数据是通过命令冲区commandBuffer进行传输的。命令缓冲区包含了一个队列,由CPU向其中添加命令,GPU去读取命令,添加和读取的命令都是相互独立的。当CPU需要渲染一个对象时,就可以向命令缓冲区中添加命令,而当GPU完成上一个渲染任务后,就会从命令缓冲区中再取出一条命令并执行它。在渲染绘制过程有很多命令,drawcall就是其中一种。
我们经常认为,Drawcall是通常认为GPU是Drawcall产生的瓶颈,其实不然,真正的元凶是CPU。
在每次调用DrawCall之前,CPU需要向GPU发送很多内容,包括数据、状态和命令等。在这一阶段,CPU需要完成很多工作,例如检查渲染状态等。而一旦CPU完成了这些准备工作,GPU就可以开始本次渲染。GPU的渲染能力很强,渲染速度往往快于CPU提交命令的速度,相对来说CPU与GPU命令交互的过程是非常耗时,CPU在等待GPU指令返回之前这期间什么都做不了。如果DrawCall的数量太多,CPU就会把大量时间花费在提交DrawCall上,造成CPU的过载。
比如:我们渲染10个模型,如果每次渲染时都调用一次Drawcall,那么总共需要调用10次Drawcall;如果10个模型能够合并调用,那么只需要调用一次drawcall,减少了CPU与GPU的指令交互的时间,性能不言而喻,自然就提升了许多。
既然drawcall是主要的性能瓶颈,那么如何减少Drawcall呢?合批(Draw Call Batching)就是最终的解决办法。
二、如何合批
1、什么是合批?
将多个渲染对象的CPU渲染指令统一一起来向GUP提交,将多个独立Drawcall合并成给
一个drawcall的指令方式。
绘制调用批处理是一种组合mesh的绘制调用优化方法,以便Unity可以在较少的绘制调用中render mesh 。Unity提供以下内置的绘制调用批处理方法:
静态批处理(Static batching)
动态批处理(Dynamic batching)
SRP Batcher (只在UPR或SRP项目中有效)
对于合批是有些限定的,基本规则如下:
1)带有如MeshRender、TrailRender、LineRender、ParticleSystem、SpriteRender组
件的mesh支持合批。
2)带有SkinMeshRender和 布料模拟的mesh是不能合批。
GPU Instancing
2、静态批处理
1、什么是静态批处理:
静态批处理是一种绘制调用批处理方法,它结合了不移动的mesh以减少绘制调用。
它将组合的mesh转换为世界空间,并为它们构建一个共享的顶点和索引缓冲区。然
后,对于可见模型,Unity执行一系列简单的绘制调用,每个调用之间几乎没有状态变
化。
静态批处理不会减少绘制调用的数量,而是减少它们之间的渲染状态更改的数量。静
态批处理比动态批处理更有效,因为静态批处理不会转换CPU上的顶点。
对于静态mesh,Unity将其组合并一起渲染。将场景的物件勾选static就是告诉编辑器,该Game Object对象不能被移动并且需要合批。
如图:
2、静态合批的规则
1)必须符合合批的基本规则
2)必须是static类型的mesh,不能移动的mesh
3)mesh是一样的并且材质相同,有meshRender组件
4)在大多数平台上,批处理限制为 64k 个顶点和 64k 个索引(OpenGLES 上为 48k 个索
引,在 macOS 上为 32k 个索引)如果超过会合批成另外个mesh
5)合批的mesh如果Scale不同无法合批,合批会被打断
6)相同顶线信息和UV的才能一起合批:
如:Unity可以对使用顶点位置、顶点法线和一个UV的mesh进行批处理,但不能与顶点
坐标、顶点法线、UV0、UV1和顶点切线的mesh一起批处理
7)mesh顶点数必须大于0
8)Mesh Renderer component组件不使用具有DisableBatching标记设置为true的着色器的
任何材质。
9)不同的贴图信息的无法合批。如:烘焙的光照贴图不相同的mesh无法合批在一起
10)模型的GameObject必须是active状态
11)位置不相邻的中间夹杂着不同材质的其他物体,不会批处理。
12)动态改变Render.material会造成一个新的material拷贝,应该使用render.shareMaterial
保证材质共享,否则不能合批。
3、动态批处理
1、动态合批的定义
对于足够小的mesh,这将在CPU上变换它们的顶点,将相似的顶点分组在一起,并在一
次绘制调用中渲染它们。
Unity build-in的调用批处理比手动合并mesh有几个优点;最值得注意的是,Unity仍然可
以单独剔除mesh。然而dynamic batch会导致一些CPU开销。
2、动态合批规则:
1)Unity 无法将动态批处理应用于包含超过 900 个顶点属性和 300 个顶点的网格。
这是因为网格的动态批处理具有每个顶点的开销。例如,如果您的着色器使用顶点
位置、顶点法线和单个 UV,则 Unity 最多可以批处理 300 个顶点。但是,如果您
的着色器使用顶点位置、顶点法线、UV0、UV1 和顶点切线,则 Unity 只能批处理 180
个顶点。
2)Unity 无法将动态批处理应用于在其变换组件中包含镜像的对象。例如,如果一个对
象的比例为 1,而另一个游戏对象的缩放比例为 –1,Unity 无法将它们批处理在一
起。
3)如果对象使用不同的material实例,Unity 无法将它们批处理在一起,即使它们本质
上是相同的。Shadow cast是个例外,仅管Shadow casters使用不同的材质,但是只
要它们的材质中给Shadow Caster Pass使用的参数是相同的,他们也能够进行
Dynamic batching。
4)具有lightmap的GameObject具有其他渲染器参数。这意味着,如果要批量光照贴图
游戏对象,它们必须指向相同的光照贴图位置。
5)Unity 无法将dynamic batch完全应用于使用多pass的Shader的对象。
6)几乎所有 Unity 着色器都支持Forward render中的多个光源。为了实现这一点,他们
为每个光源处理一个额外的render pass。Unity 仅对第一个pass进行批处理。它无法
dynamic batch附加的每个光源的所产生的pass 进行合批处理。
7)旧版延迟渲染路径,不支持动态批处理,因为它在两个渲染通道中绘制对象。第一遍
是轻量级预传递,第二遍渲染对象。
4、GPU Instancing
1、GPU instancing的定义
GPU Instancing是一种drawcall优化方法,它在一次绘图调用中使用相同材质render mesh 的
多个副本。多个mesh的副本都称为Instance。这对于绘制场景中多次出现的对象非常有用,
例如树或灌木丛。GPU实例化在同一drawcall中渲染相同的mesh。
每个实例可以具有不同的属性,例如“Color或“Scale”。要对材质使用GPU实例,请在
material 中Inspector中属性中选择"Enable GPU instacing”选项。、
是否是所有的都支持GPU Instancing?答案肯定不是。只有在支持GPU Instancing的shader才有可能
2、编写支持GPU instancing Shader步骤
shader编写一定需要经历如下几步:
Shader"MyGPUInstance"{
Properties{
...
}
SubShader{
...
Pass{
...
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_instancing //第一步
...
struct a2v{
...
UNITY_VERTEX_INPUT_INSTANCE_ID //第二步
};
struct v2f{
...
UNITY_VERTEX_INPUT_INSTANCE_ID //第二步
};
v2f vert(a2v v){
v2f o;
UNITY_SETUP_INSTANCE_ID(v); //这里第三步
UNITY_TRANSFER_INSTANCE_ID(v,o); //第三步
...
return o;
}
fixed4 frag(v2f i):SV_Target{
UNITY_SETUP_INSTANCE_ID(i); //最后一步
...
}
ENDCG
}
}
FallBack"Diffuse"
}
并且在材质面中勾选 Eanble GPU Instancing选项。如图:
5、Scriptable Render Pipeline Batch(SRP Batch)
1、SRP Batch的定义
SRP Batcher是一个渲染循环(loop),可以让相同的shader Variant的材质的GameObject能够进行合批,加速你的CPU渲染。
SRP Batcher 通过批处理(batching)一系列绑定(Bind)和绘制(Draw)GPU 命令,来减少DrawCalls之间的GPU设置(工作量)。也就是之前一堆绑定和绘制的GPU命令,能够集合的集合起来,不需要一步步设置,而来减少CPU与GPU交互次数,从而减少CPU执行的时间。
2、SRP Batch工作原理
传统的合批方法是减少绘制的对象。相反,SRP Batch是减少渲染的状态和执行过程。
我们看到在内置渲染管线中我们需要设置Meterial和Object CBUFFER,如果材质参数不同,会被打断;但是对于SRP渲染管线,只有变体不同时,才会被打断合批。
3、SRP Batch规则:
- GameObject必须包含mesh或者 skinned mesh。它不能是粒子。
- Game Object没有使用MaterialPropertyBlocks设置材质信息
- Shader必须兼容SRP
4、支持SRP Batch的管线
功能 | 内置渲染管线 | 通用渲染管线 (URP) | 高清渲染管线 (HDRP) | Custom Scriptable Render Pipeline (SRP) |
SRP Batcher | 否 | 是 | 是 | 是 |
总结:
对于合批,我们应该实际情况做出选择,它们的合批效率从高到低依次是static->GPU instancing-->Dynamic->SRP。我们知道合批的基本条件是同一个模型,并且是同一份材质信息(动态合批和SRP可以不同)。我们不仅要了解合批的规则,我们也要了解合批限制与在什么情况下被中断,同时要了解他们的优劣,才能选择合适的合批方式。
由于篇幅原因,该篇主要讲解内容是drawcall 在什么情况产生的以及 减少drawcal的常用方法。下篇我会以项目的具体内容角度讲解如何对drawcall细致优化。