[译]SRP Batcher:提升您的渲染性能

2020/09 12 19:09

原文链接地址:
https://blogs.unity3d.com/2019/02/28/srp-batcher-speed-up-your-rendering/

在2018年,Unity引入了一种高可定制的渲染技术,称之为Scriptable Render Pipeline(SRP)。

其中一部分是一个名为SRP Batcher的新底层渲染路径,它可以在渲染过程中提升渲染性能1.2~4倍。

取决于使用场景,官方提供了一个视频,让我们来看看:

https://youtu.be/pUM7ifjGKkM

视频请自行爬楼梯观看

以上视频展示了Unity的最坏情况:每个对象都是动态的,并使用不同的材质(颜色,纹理);场景显示了许多相似的Mesh,但每个对象使用一个不同的Mesh(因此不能使用GPU Instance技术); 结果:在PS4上性能提升4倍

Unity 和 Materials(材质)

Unity编辑器是非常灵活的渲染引擎,我们可以在运行期间随时修改材质属性。

Unity历史上是基于–非常量缓冲区(non-constant buffers)的,支持如DirectX9这种图形API。

所以, 当渲染使用了新材质时,这就需要大量的准备工作;即:在场景中拥有的材质越多,CPU提交给GPU的数据也越多。

标准Unity渲染流程

在Unity内部渲染路径中,当检测到新材质时,CPU会收集所有属性并在GPU内存中设置不同的常量缓冲区。 GPU缓冲区的数量取决于Shader如何声明其CBUFFER。

SRP Batcher 的工作原理

如果我们使用SRP技术,我们需要关心并写一些引擎底层代码。我们能原生地集成一些新的范式 – 比如GPU的数据管理(生命权);目标是由大量不同材质、但Shader变体较少的场景下,提升渲染性能。

如下,底层渲染路径可以让材质数据在GPU中持久存在。如果材质没有发生变化的话,则无需设置缓冲区并将其上传到GPU。此外,可以使用专用代码快速更新指定数据至大量的GPU-Buff上。新的流程图是这样的:

SRP Batcher 渲染流程

这里,CPU只处理内置的引擎属性,比如基本转换矩阵,所有的材质被托管至GPU的CBuffer上,随时可以被调用;

性能提升来自于:

1、所有材质数据都被托管至GPU内存中

2、每个对象的基础数据(例如Position/Scale/Rotation…) 被托管至GPU CBuffer中

SRP Batcher 兼容性

每个用SRP Batcher渲染的对象有2个要求:

1、只支持Mesh,不能是粒子或者 SkinnedMesh
2、用SRP Batcher兼容的Shader,例如HDRP和URP(LWRP)中的Lit,Unlit系列Shader

那如何SRP Batcher兼容呢?

1、引擎内建的属性列表,含有名为UnityPerDraw的CBUFFER声明;
例如: unity_ObjectToWorld, unity_SHAr …

注:在com.unity.render-pipelines.universal@7.3.1\ShaderLibrary\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

2、Shader的属性声明,含有名为 UnityPerMaterial的CBUFFER声明

注意1:选中Shader文件,在Inspector面板中,如果SRP Batcher为compatible,则为兼容。

注意2:Unity可同时处理并渲染 SRP Batcher兼容与不兼容的对象
兼容的使用SRP Batcher渲染,不兼容的用标准SRP代码渲染

使用快速SRP Batcher 渲染的为:
使用SRP Batcher兼容Shader渲染的Mesh对象

不使用快速SRP Batcher 渲染的为:
1、非Mesh对象(例如SkinnedMesh)
2、使用了非兼容Shader(用Inspector看一下就知道了)
3、使用了MaterialPropertyBlock技术的对象

剖析的艺术

可以使用SRPBatcherProfiler.cs脚本来测试,将此脚本放场景中,按F8切换显示,或者F9打开、关闭信息。

比如 1.31ms SRP Batcher时,可能在主线程上花了0.3ms,送到GPU上渲染花了1ms

剖析展示的信息

从这里下载这个脚本

https://github.com/Unity-Technologies/SRPBatcherBenchmark.git

测评

Book of the Dead

HDRP PS4, 1.47倍提升

FPS Sample, HDRP, PC DirectX 11. X1.23倍提升

Boat Attack, LWRP, PlayStation 4. 提升2.13倍

支持的平台

如何最佳的姿势接入SRP Batcher?

使用 SRPBatcherProfiler.cs,然后检查SRP Batcher是否打开,查看 “Standard codePath”最好为0,都应在“SRP Batcher”渲染路径中。

如何检查SRP Batcher的效率?

了解SRP Batcher上下文中的“Batcher”非常重要!

我们倾向于减少DrawCall的数量,真正的原因是引擎在绘制之前需要做许多准备工作。真正的CPU成本来自于准备工作(设置工作),而非DrawCall本身(这只是要放置GPU命令缓冲区的一些字节而已)

SRP Batcher并不会减少DrawCall数量,它降低的是DrawCall之间CPU的工作工作。

如上图所示,左侧为SRP渲染循环,右侧为SRP Batcher循环。在SRP Batcher上下文中, “Batcher”只是处理 绑定、绘制、绑定、绘制的CPU命令序列

在标准SRP中,每种新材质都需要调用很速的SetShaderPass
而在SRP Batcher上下文中,会为每个新Shader变体调用SetShaderPass。
( In standard SRP, the slow SetShaderPass is called for each new material. In SRP Batcher context, the SetShaderPass is called for each new shader variant. )

为了提高性能,最好让这些Batcher尽可能地大!
因此应该避免修改Shader变体,如果他们使用了相同的Shader,则可以大量不同的Material。
( To get maximum performance, you need to keep those batches as large as possible. So you need to avoid any shader variant change, but you can use any number of different Materials if they’re using the same shader. )

可以使用FrameDebugger查看SRP Batcher中“batcher”的长度

查看左侧SRP Batcher事件

也可以查看被上一个批次破坏的原因,例如使用不同Shader的Keywords
( See the SRP Batch event on the left. See also the size of the batch, which is the number of Draw Calls (109 here). That’s a pretty efficient batch. You also see the reason why the previous batch had been broken (“Node use different shader keywords”). It means the shader keywords used for that batch are different than the keywords in the previous batch. It means that the shader variant has changed, and we have to break the batch. )

上图所示,DrawCalls仅为2,可能意味着有太多不同的Shader变体,最好用最少的Keywords编写通用的Shader,不必担心在属性参数中输入了多少参数!

PreMaterial 变量

所有 PreMaterial 数据应在一个名为 UnityPerMaterial的CBUFFER中声明。

什么是PerMaterial数据?

比如在Shader中声明的变量(各种属性),美术在材质面板调用的各种变量。

Properties
{
_Color1 ("Color 1", Color) = (1,1,1,1)
_Color2 ("Color 2", Color) = (1,1,1,1)
}

float4 _Color1;
float4 _Color2;

如果编译此Shader,则为SRP Batcher不兼容,提示:

SRP Batcher – not compatible
Material property is found in another cbuffer than “UnityPerMaterial” (_Color1)

这样修正它:

CBUFFER_START(UnityPerMaterial)
  float4 _Color1; 
  float4 _Color2; 
CBUFFER_END

PerObject 变量

SRPBatcher需要一个特殊的CBUFFER:”UnityPerDraw”
该CBUFFER需要包含所有Unity引擎的内建变量

UnityPerDraw CBUFFER变量的变量声明次序很重要,所有变量要遵守称之为【块】的布局 ( block feature layout )

例如,常用的一些转换矩阵需要按以下次序包含:

float4x4 unity_ObjectToWorld; 
float4x4 unity_WorldToObject; 
float4 unity_LODFade; 
float4 unity_WorldTransformParams;

注:熟悉OpenGL ES中的 “统一变量缓冲区对象”概念,即可知道,此缓冲区是按块进行的,Uniform 块布局。点此查阅

需要不需要则不需要声明某些功能, UnityPerDraw中所有内建变量应为float4x4
也可以使用real4x4(16位编码)来省一些GPU带宽,但是并且所有的UnityPerDraw变量都可以用real4x4,可以查下面的表:

Tips:如果一个 feature block 被声明为 real4(half4),则所有其它块也应声明为real4x4

展望

我们仍然继续在渲染过程(尤其是Shadow和Depth)中增加Batch的大小来改进SRP Batcher

通过SRP Batcher添加自动GPU Instancing功能,从MegaCityDemo中使用新的DOTS渲染器开始,Unity编辑器从10FPS提升到50FPS(吹牛)

注意:带有DOTS的SRP Batcher还在开发中,不要急