SRP Batcher:加速渲染
Unity 2018引入了可编程渲染管线SRP,其中包含新的底层渲染循环SRP Batcher批处理器,它可以大幅提高CPU在渲染时的处理速度,根据场景内容的不同,提升效果为原来的1.2~4倍不等。本文将介绍如何充分使用SRP Batcher。
下面的视频展示了Unity最难处理的情况:每个对象都是动态的,并且使用了不同颜色,纹理的材质。场景有很多外观相似的网格,但场景会以每个对象有不同网格的方式来运行,因此无法使用GPU Instancing功能。
在使用SRP Batcher后,在PlayStation 4平台上约有4倍的提速效果。
小提示:上面提到4倍的提速效果时,我们指的是CPU渲染代码,即“RenderLoop.Draw”和“ShadowLoop.Draw”分析器标记,并非全局帧率FPS。
Unity与材质
Unity编辑器拥有非常灵活的渲染引擎。我们可以在一帧中随时修改任意材质属性。此外Unity一直面向非常量缓冲区开发,支持DirectX9等图形API,但这些不错的功能有一些缺点,例如:当Draw Calls使用新材质时,需要进行很多处理。场景内的材质越多,设置GPU数据所需的CPU资源就越多。
标准Unity渲染工作流程
在内部渲染循环期间,当检测到新材质时,CPU会收集所有属性,并在GPU内存中设置不同的常量缓冲区,GPU缓冲区的数量取决于着色器如何声明其CBUFFER。
SRP Batcher的工作流程
在开发SRP技术时,我们必须重写部分底层引擎。我们发现了在本地集成新范例的机会,例如:GPU数据持久化。
我们的目标是提高常见用例的速度,在常见用例中,场景会使用大量不同材质和少量着色器变体。
现在,底层渲染循环可以使材质数据在GPU内存中具有持久性。如果材质内容没有改变,则不需要设置并上传缓冲区到GPU。此外我们还使用了专用代码路径,从而快速更新大型GPU缓冲区的内置引擎属性。
新的渲染工作流程如下图所示。
SRP Batcher渲染工作流程
在这个工作流程中,CPU只处理内置引擎属性,它们被标记为对象矩阵变换。所有材质都有位于GPU内存的持久性CBUFFER,可以随时使用。
提速效果源于二个方面:
每个材质内容现在都会一直保留在GPU内存中
专用代码会管理大型“per object” GPU CBUFFER
启用SRP Batcher
你的项目必须使用轻量级渲染管线LWRP,高清晰渲染管线HDRP或自定义SRP。为了在HDRP或LWRP中启用SRP Batcher,请在SRP资源的检视窗口勾选SRP Batcher复选框。
如果想在运行时启用或禁用SRP Batcher,以测量基准性能增益效果,我们也可以使用C#代码启用该全局变量。
GraphicsSettings.useScriptableRenderPipelineBatching = true;
SRP Batcher兼容性
为了使对象通过SRP Batcher代码路径渲染,要注意二个要求:
对象必须处于网格中。对象不可以是粒子或蒙皮网格。
必须使用兼容SRP Batcher的着色器。HDRP和LWRP中的所有Lit Shader受光着色器和Unlit Shader无光照着色器都符合此要求。
为了让着色器兼容SRP,需要进行以下处理:
所有内置引擎属性必须在名为“UnityPerDraw”的CBUFFER中声明。例如:unity_ObjectToWorld或unity_SHAr。
所有材质属性必须在名为“UnityPerMaterial”的单个CBUFFER中声明。
我们可以在检视窗口查看着色器的SRP Batcher兼容状态,该兼容状态只会在使用SRP的项目中显示。
在特定场景中,某些对象兼容SRP Batcher,有些则不兼容。但场景仍会正常渲染。兼容对象会使用SRP Batcher代码路径,而其它对象仍会使用标准SRP代码路径。
使用快速SRP Batcher代码路径
不使用快速SRP Batcher代码路径
使用兼容SRP Batcher着色器的网格
不属于网格的内容,包括蒙皮网格
使用不兼容SRP Batcher着色器的网格(可通过着色器检视窗口查看原因)
使用材质属性块(Material Property Block)的渲染器
性能分析
SRPBatcherProfiler.cs
如果想要测量SRP Batcher对特定场景的速度提升效果,可以使用SRPBatcherProfiler.cs脚本,只要将该脚本添加到场景即可。
获取SRPBatcherProfiler.cs,请访问SRP Batcher项目模板页:
https://github.com/Unity-Technologies/SRPBatcherBenchmark.git
当脚本运行时,我们可以使用F8键切换覆盖显示画面。我们也可以使用F9键开启或关闭SRP Batcher。如果在运行模式启用覆盖画面,我们会看到许多有用信息。
下图中所有的时间都以毫秒为单位,这些时间测量显示CPU在Unity SRP渲染循环中花费的时间。
小提示:这些时间值表示在一帧中调用的所有“RenderLoop.Draw”和“Shadows.Draw”标记的累计时间,无论线程所有者是谁。当看到“1.31ms SRP Batcher code path”时,可能其中0.31毫秒用于主线程,1毫秒用于所有图形作业。
覆盖信息
下面的表格中,可以看到运行模式下覆盖画面每项设置的说明。
SRP Batcher ON
表示目前的SRP Batcher状态。可以在运行时按下F9键来切换开关状态。
2.11ms CPU Rendering time
这一行表示在不同Unity SRP循环中累计使用的总CPU时间为2.11毫秒,无论是否使用多线程模式,例如:单线程,客户端线程/工作线程,或是图形作业。
(incl 0.36ms RT idle)
此处的0.36毫秒是总时间2.11毫秒的一部分,表示渲染线程的闲置时间。它或许表示场景处于没有图形作业的客户端/工作模式,渲染线程经常会等待主线程的图形指令。
1.31ms SRP
Batcher code path (89 flush(s))
此处的1.31毫秒是总时间2.11毫秒的一部分,表示在SRP Batcher代码路径所用的时间。
请记住,只有兼容SRP Batcher的对象会使用快速代码路径。在帧内需要进行89次批处理刷新。这里的次数越低越好。每次遇到新的着色器变体时,都会刷新SRP Batcher。
1.18ms All objects
这部分时间是SRP Batcher兼容对象所用1.31毫秒的一部分,表示1.18毫秒用于渲染除阴影通道外的所有通道。
0.13ms Shadows
这部分时间是SRP Batcher兼容对象所用1.31毫秒的一部分,表示0.13毫秒用于渲染阴影通道。
0.80ms Standard code path (81 flushs)
此处的0.80毫秒是总时间2.11毫秒的一部分,表示用于渲染非SRP Batcher兼容对象的时间。
对于SRP Batcher代码路径,0.80毫秒中的0.09毫秒用于阴影通道,0.71毫秒用于其它通道。
Global Main Loop: 3.29ms ( 304 FPS)
这部分表示平均到每秒的全局主循环时间和帧每秒FPS。
我们不愿意在覆盖画面底部添加FPS信息,因为优化时要对FPS指标非常小心,出于以下二方面考虑:
FPS不是线性的,因此FPS提高20%并不表示场景有对应的优化效果。
FPS表示的是全局帧数。FPS或全局帧时间值取决于渲染外的很多其它因素,例如:C#游戏性,物理和遮蔽等。
多场景基准
下面一些Unity场景截图,它们分别展示开启和关闭SRP Batcher的画面,我们可以查看不同情况下的速度提升效果。
死者之书
《死者之书》项目,使用HDRP,平台为PlayStation 4,具有1.47倍的提速效果。
FPS并未改变,因为该场景与GPU绑定。我们得到剩余的12毫秒来在CPU进行其它处理,提速效果和PC几乎一致。
FPS示例项目
FPS示例项目,使用HDRP,平台为PC DirectX 11,具有1.23倍的提速效果。
因为场景有部分内容不兼容SRP Batcher,仍有1.67毫秒用于标准代码路径。本示例中,不兼容的内容为蒙皮网格和使用材质属性块渲染的部分粒子。
Boat Attack
Boat Attack项目,使用LWRP,平台为PlayStation 4,具有2.13倍的提速效果。
Boat Attack项目下载地址:
https://github.com/Verasl/BoatAttack
支持平台
SRP Batcher几乎适用于所有平台。下面表格展示了支持平台和所需最低Unity版本的信息。Unity 2019.2目前处于开放式Alpha测试阶段。
关于VR
SRP Batcher快速代码路径支持VR,但仅支持“SinglePassInstanced”模式。启用VR不会添加任何CPU时间,因为使用了SinglePassInstanced模式。
常见问题
如何知道是否已充分使用SRP Batcher?
请使用SRPBatcherProfiler.cs,首先检查SRP Batcher是否已经启用。然后查看“Standard code path”的时间值,该值应该接近于0,而且所有时间都应该用在“SRP Batcher code path”部分。
如果场景内使用了部分蒙皮网格或粒子,部分时间用于标准代码路径是正常现象。请访问SRP Batcher Benchmark项目了解详情:
https://github.com/Unity-Technologies/SRPBatcherBenchmark.git
为什么无论SRP Batcher是否开启,
SRPBatcherProfiler都显示相似的时间使用情况?
首先,检查是否几乎所有渲染时间都通过新的代码路径。如果是,而且数值仍旧相似,那么请检查“flush”数值。
“flush”值应该在SRP Batcher开启时大幅减小。根据我们的经验,SRP Batcher开启后“flush”值变为原来的1/10是很理想的效果,变为原来1/2也很不错。
如果flush值没有减小太多,这意味着仍有很多着色器变体,请尝试减少着色器变体数量。如果使用了很多不同的着色器,请尝试制作带有更多参数的“特别”着色器。这样拥有大量不同材质参数不会有太大影响。
在启用SRP Batcher时,为什么全局FPS没有变化?
请先查看前面的二个问题。如果SRPBatcherProfiler显示“CPU Rendering time”(CPU渲染时间)是原来的2倍速度,而FPS没有变化,那么CPU渲染部分不是运行效果的瓶颈。
这并不意味着场景不和CPU绑定,相反,你也许使用太多C#游戏效果,或是太多物理元素。无论如何,如果“CPU Rendering time”是原来的2倍速度,它仍有积极的作用。
在前面的示例视频中,即使有3.5倍的提速效果,场景仍旧是60fps。那是因为我们开启了VSNYC。SRP Batcher节省了CPU部分的6.8毫秒时间,这些时间可用于其它任务,也可以节省移动设备的电池寿命。
检查SRP Batcher效率
理解什么是SRP Batcher中的“批处理”非常重要。过去,开发者往往倾向减少Draw Calls的数量来优化CPU渲染成本。
这样做的原因是,引擎必须在发起Draw Calls前进行大量设置,真正的CPU成本来自这些设置过程,而不是来自GPU Draw Calls本身。
SRP Batcher不会Draw Calls的数量,它只会降低Draw Calls之间的GPU设置成本。
下图详述这个工作流程。
左侧是标准SRP渲染循环,右侧是SRP Batcher循环。在SRP Batcher中,“批处理”其实是指“绑定”,“绘制”这样的GPU指令序列。
在标准SRP中,会为每个新材质调用缓慢的SetShaderPass。在SRP Batcher中,会为每个新着色器变体调用SetShaderPass。
为了获得最佳性能,我们需要保持尽可能大的批处理。因此我们需要避免着色器变体的改动,但如果材质都使用相同的着色器,我们使用任何数量的不同材质。
我们可以使用Unity Frame Debugger来查看SRP Batcher“批处理”的长度。在Unity Frame Debugger中,每次批处理是被称为“SRP Batcher”的事件,如下图所示。
请查看左侧的SRP Batcher事件,同样要注意批处理的大小,即绘图调用的次数,此处为109次,这是非常高效的批处理。
我们也会在此看到之前批处理受损的原因:“节点使用了不同的着色器关键字”。这意味着,该批处理使用的着色器关键字和之前批处理中的关键字不同,而且它也表示着色器变体没有改变,我们必须破坏批处理。
在某些场景中,部分批处理大小会很小,如下图所示。
批处理大小只有2,这可能表示我们有太多不同的着色器变体。如果创建了自定义SRP,请尝试使用最少的关键字编写通用的“特别”着色器。不必担心在“属性”部分加入的材质参数数量。
小提示:显示Unity Frame Debugger的SRP Batcher信息需要使用Unity 2018.3或更高版本。
使用兼容着色器编写自定义SRP
这部分内容面向编写自定义可编程渲染循环Scriptable Render Loop和着色器库的高级用户提供。LWRP或HDRP用户可以跳过这部分,因为我们提供的所有着色器都已经兼容SRP Batcher。
如果你要编写自定义渲染循环,着色器必须遵循一些规则,才能通过SRP Batcher代码路径。
“Per Material”变量
首先,所有“Per material”(材质相关)数据都应该在名为“UnityPerMaterial”的单个CBUFFER中声明。
什么是“Per material”数据?通常是在“着色器属性”部分声明的所有变量,这都是“Per material”数据。即艺术家可以使用材质GUI检视窗口调整的所有变量。例如:我们看看下面这个简单的着色器。
Properties
{
_Color1 ("Color 1", Color) = (1,1,1,1)
_Color2 ("Color 2", Color) = (1,1,1,1)
}
float4 _Color1;
float4 _Color2;
如果编译该着色器,着色器检视窗口会如下图所示。
为了修复该问题,按照下面代码声明所有“Per material”数据即可。
CBUFFER_START(UnityPerMaterial)
float4 _Color1;
float4 _Color2;
CBUFFER_END
“Per Object”变量
SRP Batcher还需要名为“UnityPerDraw”的特别CBUFFER,该CBUFFER应该包含所有Unity的内置引擎变量。
“UnityPerDraw” CBUFFER内的变量声明顺序也很重要,所有变量都应该遵循名为“Block Feature”的布局。例如:“Space Position block feature”应该包含所有变量,顺序如下。
float4x4 unity_ObjectToWorld;
float4x4 unity_WorldToObject;
float4 unity_LODFade;
float4 unity_WorldTransformParams;
如果不需要,则不必声明部分块功能。“UnityPerDraw”中的所有内置引擎变量都应该为float4或float4x4类型。
在移动平台上,开发者可能想使用real4类型,以节省部分GPU带宽。不是所有UnityPerDraw变量都可以使用real4类型。详情请查阅下方表格的“Could be half”列。
下方的表格描述“UnityPerDraw” CBUFFER中可使用的所有块功能。
小提示:如果功能块的一个变量被声明为real4,即half,那么该功能块的所有可用变量都应该被声明为real4。
始终要检查检视窗口中新着色器的兼容状态。我们会检查到多个潜在错误,并显示不兼容的原因。
在编写自定义SRP着色器时,你可以参考LWRP或HDRP资源包的UnityPerDraw CBUFFER声明,以得到一些启发。
未来展望
我们会不断改进SRP Batcher,提高部分渲染通道的批大小,特别是阴影通道和深度通道。
MegaCity演示使用的全新DOTS渲染器。该演示在Unity编辑器中的提速效果非常惊人,从10fps提升到了50 fps。
下面的视频为编辑器内使用了SRP Batcher和DOTS渲染器的MegaCity演示。性能上的区别非常大,即使是全局帧率也提高了5倍。
小提示:确切的说,由于编辑器目前没有图形作业,因此启用SRP Batcher后的提速效果仅限于编辑器内,在Standalone模式下,提速效果为原来的2倍。
下面视频为MegaCity在编辑器内的效果。如果可以在60hz下播放该视频,你会感受到启用SRP Batcher时的提速效果。
现在,具有DOTS渲染器的SRP Batcher仍处于实验开发阶段。
更多Unity最新功能,尽在Unity Connect平台(Connect.unity.com)。
推荐阅读
官方活动
3月13日晚8点,由Unity技术经理鲍健运老师为大家带来,如何在Unity的高清晰渲染管线HDRP实时光照环境中,制作高质量的灯具光照效果。[了解详情...]
直播时间:3月13日 20:00 - 21:30
直播地址:
https://connect.unity.com/events/using-light-cookie-to-make-light-fixtures-in-unity-hdrp
Unity GDC 2019 活动日程
3月18-22日GDC 2019将在旧金山举行,了解Unity活动日程信息,请点击此处。
Unity GDC 2019官网:
https://unity.com/gdc-2019
Unite Shanghai 2019
5月10日-12日上海,Unite大会强势回归。技术门票正在热销中,购票即获指定Asset Store资源商店精品21款资源的5折优惠券。
购票请访问:Unite2019.csdn.net
点击“阅读原文”访问Unity Connect