当我们使用SRP(Scriptable Render Pipeline)之后,例如HDRP,URP或者LWRP,在SPR的asset文件中有一个选项叫做SRP Batcher(如图)。
这篇文章就让我们来了解了解这个好东西。
简单的说,如果当你的场景中有很多的物体分别使用到了不同的Material,但是这些Material使用的Shader却都是同一个时,SRP Batcher可以大大为我们降低DrawCall,从而加快渲染速度。
官方介绍:https://blogs.unity3d.com/2019/02/28/srp-batcher-speed-up-your-rendering/
https://docs.unity3d.com/Manual/SRPBatcher.html
中文文档:https://connect.unity.com/p/srp-batcher-jia-su-xuan-ran
接下来我们可以通过一个简单的示例来验证这个效果。
首先我们先通过Create->Shader->Unlit Shader创建一个简单的无光照Shader命名为SampleUnlit。接着我们创建三个Material,分别命名为SampleMaterial1,SampleMaterial2,SampleMaterial3。这三个Material都使用我们刚刚创建好的SampleShader,并分别引用不同的Texture。
接着我们在场景中创建15个Cube,分别引用上面的这些Material,效果如图
此时打开Game视图的Statistics面板,可以看见15个Cube一共产生了15个DrawCall(由于SkyBox会产生一个DrawCall所以显示的有16个),而Saved By Batching为0。
我们通过Window->Analysis->Frame Debugger打开Frame Debug窗口,点击Enable按钮,可以观察到当前帧的绘制信息
按往常我们可能会通过动态批处理或者静态批处理来进行优化。
动态批处理:我们可以勾选SRP asset文件的Dynamic Batching,勾选后可以发现,DrawCall数量变为12,Saved By Batching变为4。同意通过Frame Debug可以发现有些方块的绘制被合并到了一起,从而减少了DrawCall。
静态批处理:我们可以将这些小方块全部变为静态物体(勾选其Static属性),运行后发现DrawCall同样变少了(未运行状态下不变),以及对应的Frame Debug视图如下:
但是使用SRP后,这种情况我们就可以使用SRP Batcher来处理了,而且效果会更好。
首先我们勾选SRP asset的SRP Batcher选项,嗯哼,怎么没有任何变化,说好的会减少DrawCall呢?
通过官方的文档,我们可以找到问题的所在,那就是Shader了。我们点击前面创建的SampleUnlit,会发现其有个SRP Batcher的属性,显示着not compatible,哇擦,不兼容。同时下面还有个看不懂的提示。
官方文档有如下一句话:
For a Shader to be compatible with SRP:
翻译成白话来说,Shader中所有的内置属性例如unity_ObjectToWorld,unity_SHAr等,都要在一个名为UnityPerDraw的CBUFFER中声明,而所有的Material属性都要在一个名为UnityPerMaterial的CBUFFER中声明。
官方文档后面也给到了一个代码示例
Properties
{
_Color1 ("Color 1", Color) = (1,1,1,1)
_Color2 ("Color 2", Color) = (1,1,1,1)
}
//原本的写法
//float4 _Color1;
//float4 _Color2;
//兼容SRP Batcher的写法
CBUFFER_START(UnityPerMaterial)
float4 _Color1;
float4 _Color2;
CBUFFER_END
可以看出Material属性,也就是Properties中的参数在声明时,都被包含在了一个下面这样的语法快中
CBUFFER_START(UnityPerMaterial)
//Properties
CBUFFER_END
再回过头来看前面我们Shader的提示:Material property is found in another cbuffer than "UnityPerMaterial"(_MainText_ST)。
也就是说我们Shader中的_MainText_ST属性没有声明在名为UnityPerMaterial的CBUFFER中,我们将其改为如下代码
sampler2D _MainTex;
CBUFFER_START(UnityPerMaterial)
float4 _MainTex_ST;
CBUFFER_END
注:何为_MainText_ST?当Shader中使用到Texture属性(如例子中的_MainText),Unity会自动为我们添加一个类型为float4后缀为_ST的属性(float4 _MainText_ST),用来表示Texture的Tiling和Offset。
再次查看我们的Shader会发现已经兼容了SRP Batcher了,同时Statistics面板中的Save By batching变为了-15。
此时查看Frame Debug,发现由原来的RenderLoop.Draw变为了RenderLoopNewBatcher.Draw,同时底下只有一个SRP Batch。不过这不代表只使用了一个DrawCall来显示了这些内容,而是对它们进行了序列上的优化。
此时我们已经实现了SRP Batcher的功能了,不过前文还提到了一个用来处理内置属性名为UnityPerDraw的CBUFFER却没有用到,按照UnityPerMaterial的样式,我们可以在Shader中添加一下测试代码:
CBUFFER_START(UnityPerDraw)
float4x4 unity_ObjectToWorld;
CBUFFER_END
注:有关内置Shader属性的说明可以参照:https://docs.unity3d.com/Manual/SL-UnityShaderVariables.html
结果会报错:Shader error in 'Unlit/SampleUnlit': redefinition of 'unity_ObjectToWorld',重复定义了unity_ObjectToWorld。而我们的Shader代码中,怎么也找不到又在哪定义了这个unity_ObjectToWorld。那么只可能是在通过#include引入的其他文件当中了。
如果是CG语言(CGPROGRAM),我们会引用UnityCG.cginc,在Unity安装目录的Editor/Data/CGIncludes文件夹中我们可以找到它,打开查看内容可以发现其又引用了UnityShaderVariables.cginc,相同目录下找到并查看内容,我们就会发现有如下一些定义,替我们定义好了这些内置属性
......
CBUFFER_START(UnityPerDraw)
float4x4 unity_ObjectToWorld;
float4x4 unity_WorldToObject;
float4 unity_LODFade; // x is the fade value ranging within [0,1]. y is x quantized into 16 levels
float4 unity_WorldTransformParams; // w is usually 1.0, or -1.0 for odd-negative scale transforms
float4 unity_RenderingLayer;
CBUFFER_END
......
如果用的是HLSL语言(HLSLPROGRAM),在我们项目工程的Library/PackageCache/com.unity.render-pipelines.universal/ShaderLibrary目录下有个Core.hlsl文件,在里面又引用了Input.hlsl,在其中又引用了UnityInput.hlsl,打开UnityInput.hlsl同样可以看见这些定义
// Block Layout should be respected due to SRP Batcher
CBUFFER_START(UnityPerDraw)
// Space block Feature
float4x4 unity_ObjectToWorld;
float4x4 unity_WorldToObject;
float4 unity_LODFade; // x is the fade value ranging within [0,1]. y is x quantized into 16 levels
real4 unity_WorldTransformParams; // w is usually 1.0, or -1.0 for odd-negative scale transforms
// Light Indices block feature
// These are set internally by the engine upon request by RendererConfiguration.
real4 unity_LightData;
real4 unity_LightIndices[2];
float4 unity_ProbesOcclusion;
// Reflection Probe 0 block feature
// HDR environment map decode instructions
real4 unity_SpecCube0_HDR;
// Lightmap block feature
float4 unity_LightmapST;
float4 unity_DynamicLightmapST;
// SH block feature
real4 unity_SHAr;
real4 unity_SHAg;
real4 unity_SHAb;
real4 unity_SHBr;
real4 unity_SHBg;
real4 unity_SHBb;
real4 unity_SHC;
CBUFFER_END