虚幻5的移动端延迟渲染技术到底有多好用?

5

主题

10

帖子

19

积分

新手上路

Rank: 1

积分
19
发表于 2022-9-20 08:18:32 | 显示全部楼层
延迟渲染技术在端游领域的应用已经趋近成熟。凭借其将复杂的光照计算变得更轻量化的优点,为开发者后续优化提供极大便利。但在移动端游戏开发过程中,延迟渲染仍然需要面对诸如兼容性、优化、功能升级、渲染框架等问题。为此,在虚幻引擎从4代到5代的升级过程中,Epic Games的工程师们针对性地为原有的移动端延迟管线功能作了优化升级,如今这项技术已经几近成熟。

在由腾讯游戏学堂举办的TGDC2022腾讯游戏开发者大会上,来自Epic Games China的技术支持工程师刘炜,就围绕这次虚幻引擎5下移动端延迟管线技术的更新做了一次技术层面的讲解。针对其与前向渲染相比的优点、GBuffer格式的布局、可能遇到的兼容性问题,以及虚幻5对比虚幻4而言新增的功能及其使用方式,带来一场细致入微的经验分享。

以下是分享实录:

我叫刘炜,是Epic Games中国的技术支持工程师,同时我也会参与虚幻引擎移动端的功能开发。很高兴能参加腾讯游戏开发者大会,这次我主要想分享的是关于虚幻5移动端延迟渲染的相关内容。

主要是想介绍一下虚幻5移动端延迟渲染的框架、所支持的功能、遇到的兼容性问题,以及优化方面的内容。



其实早在2018年左右就有开发者提出需求,希望我们支持移动端的延迟渲染。当时我们做了一个粗略的评估,觉得是有可行性的,于是我们在虚幻4.26的版本里开始支持移动端的延迟渲染。不过情况比我们想象中的复杂,直到虚幻5.1的版本才算比较成熟,结果不算完美,但是过程是值得借鉴的。因为每次技术升级总会有失有得,希望这个过程所总结的经验可以对大家在开发过程中遇到的问题有所启发,也希望这个技术可以更直接地帮助开发团队提升游戏品质。

这是我今天要讲的主要内容——

第一部分是移动端延迟渲染,跟前向渲染做一个横向对比,看它们都有哪些不同。我们需要评估一下延迟渲染能给我们带来什么好处,以及有什么限制。

第二部分是分析一下GBuffer的布局,以及怎样选择GBuffer的格式才能满足我们对功能需求的支持。

第三部分是介绍延迟渲染在虚幻4中有哪些所缺失的功能,在虚幻5.1中的实现情况,以及介绍一下它们的实现方式。

第四部分是介绍一下我们遇到的哪些兼容性问题,以及对应的修复方式。

最后介绍一下我们对于延迟渲染做了哪些针对性的优化。

其实延迟渲染技术已经相对比较成熟,没有太大的技术难度。所以这次我讲的内容,对于已经有这方面开发经验的程序估计没有太大的帮助,最多就是设备兼容性方面的问题可以留意一下。不过对于那些对移动端延迟渲染感兴趣的团队,尤其是还在使用虚幻4.27的项目组,这次的内容可能更有意义。

首先我们主要从移动端比较关心的内存和性能方面,来比较一下延迟渲染跟前向渲染,有哪些优势 ,又有哪些限制。

延迟渲染一个比较明显的好处,是可以减少shader的变体。我们在Fortnite项目里,一直想办法去减少shader的数量,因为Fortnite项目比较大,shader的数量比较多,每次更新的patch比较大,对内存的占用也较高,我相信很多项目组也遇到过类似的问题。

如果选用延迟渲染,base pass就不用计算光照了,由光照计算的shader变体就可以跟模型材质解耦开,可以大量减少shader的数量。

图为延迟渲染和前向渲染各自所用到的shader变体数量的比较,在关掉静态光照的情况下,延迟渲染只有1种shader变体,而前向渲染有4种,即延迟渲染可以减少大概75%的shader数量,相当于只有原来的1/4大小。



在打开静态光照的情况下,延迟渲染有4种shader变体,而前向渲染有18种,相当于可以减少大约78%的shader数量,差不多是原来的1/5大小。



可以看到无论在哪种光照环境下,延迟渲染都能减少非常多的shader数量,也减少了很多相应硬盘和内存的占用,这是我们目前最大的收获。shader数量的减少不仅可以减小包体和内存的占用,还可以减少shader的编译时间,加快启动速度。另外还可以减少游戏运行过程中的卡顿,因为很多卡顿可能是因为实时编译shader造成的,这也是延迟渲染给我们带来的额外的好处。

延迟渲染对性能的影响方面,因为Base Pass没有了复杂的光照计算,变得更加轻量化,Base Pass阶段的开销应该会比以前减少很多,而且光照只在可见的像素上做计算,不会浪费GPU时间在不可见的像素上,理论上也会减少GPU的压力,对性能提升会有所帮助。不过现在的芯片大多都内置了减少OverDraw的技术,比如PowerVR GPU的Hidden Surface Removal和Arm GPU的Forward Pixel Kill,但是即便如此 ,在有些设备上还是能有明显的收益的。

另外,使用延迟渲染可以将更多原来在Base Pass里做光照计算的Sampler给美术使用,使Reflection大概可以减少1至2个,Shadow texture可以减少1个,Planar reflection可以减少1个,Light grid data可以减少3个,所以在复杂的情况下,最多能减少6至7个sampler。



延迟渲染也没有占用更多的带宽,虽然延迟渲染需要创建多张GBuffer,但是我们可以利用Tiled Based GPU的特性,把GBuffer分配在Tiled Memory上,这样就不用存回System Memory,从而减少带宽占用。不过这样也会带来一个问题,就是无法在后期中访问GBuffer里的数据,这也是目前移动端不支持Screen Space Reflection的一个原因。



延迟渲染还支持一些前向渲染没有的功能,第一个是带光照的延迟贴花,延迟渲染因为有了GBuffer,可以在绘制贴花的时候改变GBuffer再计算光照,使贴花就可以受光照的影响了。

此外延迟渲染还支持IES profile和Light Function,而前向渲染只支持Clustered Local Light所以不支持这两个功能。

延迟渲染的光照默认会比前向渲染略微好一点,因为延迟渲染使用的是更准确的Environment BRDF,这个BRDF是bake在贴图里的,而前向渲染因为要省一个sampler用了近似的版本,从而需要进行实时的计算,延迟渲染只需要一次贴图采样,不需要实时计算,所以性能上更好,而且效果上也更加贴近PC。



不过我们在一开始支持不同硬件平台的时候也遇到了一些问题。在对Mali设备做支持的时候,发现Mali的GPU不支持MRT的Frame Buffer Fetch,如果要支持OpenGLES,就只能使用Pixel Local Storage的技术,这其实是Arm提供的另一种MRT Frame Buffer Fetch的形式,但是Pixel Local Storage有一个很大的限制,就是它的带宽只有128bit。

除此之外我们还发现即便使用Vulkan RHI,在一些设备上查出来的最大支持的Input Attachment的数量只有4个,这种设备大概占所有Android设备的大概16.3%,为了支持这些设备,引擎只能控制Gbuffer的数量,最多只能申请3张32bit的Render Target,并不能像PC上那样,可以创建5张甚至更多的GBuffer来支持一些效果,这也是我们目前遇到的一个最大的问题,接下来我们会想办法解决这个问题。

关于抗锯齿,因为GBuffer已经把Tiled Memory都占用了,所以不能使用MSAA来抗锯齿了,引擎目前支持FXAA TAA,在虚幻5.1中我们也支持了FSR,虽然效率上肯定比不了硬件支持的MSAA,不过这种程序化的抗锯齿效果可能会更好,另外我们也在考虑,针对抗锯齿去做一些性能优化,比如给TAA增加一个PixelShader的版本,这样可以利用硬件的FrameBufferCompression来减少带宽占用。



现在我们已经知道了延迟渲染在移动端上的优势与限制,接下来我们看一下如何在这些限制条件下组织GBuffer的布局使其满足移动渲染的功能需求。

首先在所有硬件平台上我们都需要申请SceneColor和SceneDepth,SceneColor是始终要存下来的,而为了节省带宽SceneDepth可以只保存后期需要的部分。



然后是GBuffer的创建,在Mali设备上,如果是OpenGLES,GBuffer是在shader里用Pixel Local Storage的方式创建在Tiled Memory上的,再加上32bit的SceneColor,一共占用128bit,这也是Pixel Local Storage的一个限制。



如果是Vulkan RHI或者是在高通硬件平台上支持OpenGLES,GBuffer可以使用Memoryless的方创建在Tiled Memory上,在使用Vulkan RHI的时候GBuffer和SceneDepth都算在Input attachments上,且总共不能超过4个,这也是现在很多移动设备的一个限制。



对于IOS设备或者其它PowerVR GPU的Android设备,因为GPU不支持DepthBufferFetch,只能用MRT的FrameBufferFetch,只能新申请一张32bit的Render Target来存储Depth,这张Render Target是Memoryless的,不占用带宽,只是需要处理一下shader里FetchDetph的逻辑。



关于GBuffer要选择什么格式以及存了什么信息,可以参考目前虚幻4里的结构,在表中我们可以看到SceneColor里存的是间接光和自发光的信息,GBufferA是存了压缩过的法线和静态阴影的信息(由于法线需要比较高的精度,GBufferA使用了RGB10A2的格式),GBufferB中存的是Metallic Specular Rougheness、ShadingModelID以及SelectiveOutputMask,GBufferC存的是Base Color以及Indirect Irradiance或者是Material AO。

这里我们可以得到一些信息,第一个是不支持延迟贴花的法线融合,因为法线是把3个通道的值压缩到2个通道里,混合后得到错误数据,而其它通道如Metallic Specular Roughness和BaseColor都是支持混合的。第二个是虚幻4里不支持Shading Model,只支持DefaultLit,若要支持则需要额外再申请一张GBuffer来存储Shading Model用到的Custom Data,但是由于硬件的限制无法新申请GBuffer,只能在现有的通道里寻找是否有可利用的。

PerObject Data和SelectiveOutputMask在移动端上未被用到,但PerObject Data通道只有2bit无法存储数据,Precomputed Shadow和Indirect Irradiance用于静态光照下,Material AO用于动态光照下,ShadingModelID至少需要4bit才能表达所有Shading Model,所以我们可以考虑在这几个通道里寻找思路。



这张表是我们根据前面得到的信息调整后的GBuffer结构,GBufferA改用为RGBA8格式,法线占用3个通道不再压缩,可以支持延迟贴花的法线混合,但是8bit的法线精度可能依然不够,在一些反射比较好的效果下面可能看到一些瑕疵。

此外,如果要支持Shading Model,需要腾出至少4个通道来存储Custom Data:Precomputed Shadow和Indirect Irradiance通道只在静态光照下用到,所以在动态光照下用在Cusom Data;Material AO用的不多且没有多余的通道,所以有Shading Model的时候不使用;另外Metallic在很多Shading Model里也是不用的,也可以用来存储Custom Data。还有一个Custom Data需要跟ShadingsModelID压缩在一个通道里,但会使ShadingModelID的精度降低至只有3bit,不能表达所有ShadingModelID,而且5bit的Custom Data精度也不太够,所以这个方案也不是很完美。



这张表是现在虚幻5.1中GBuffer的结构,GBufferA还是使用了RGBA RGB10A2的格式,法线压缩在两个通道里依旧不支持延迟贴花的混合,但是法线可以有更高的精度,还可以多一个10bit的通道,将ShadingModelID和Custom Data压缩在这个通道里,ShadingModelID就可以用4bit,CustomData可以用6bit,精度上基本满足,再加上Indirect Irradiance、Precomputed Shadow和Metallic三个通道,就可以存下4个通道的CustomData,所以在关掉静态光照的情况下,就能支持Shading Model了。



接下来我们来看一下虚幻5.1中支持了哪些新的功能。

第一个就是Shading Model,我们在关掉静态光照的情况下已经支持了所有的Shading Model:SingleLayerWater和Thin Translucent两个Shading Model是半透明效果才有的,是用前向渲染绘制的,所以在延迟渲染中不需要考虑它们;Eye Shading Model比较特殊,将会在后面介绍。

还有两个Cvar与移动端Shading Model相关,第一个是控制移动端是否支持PerPixel的Shading Model,如果支持就可以在一个材质球里有多个Shading Model效果,默认是开启的,第二个是控制在移动端上哪些Shading Model是打开的,如果没有打开就会用DefaultLit以提高效率,默认全部打开;在开启静态光照的情况下依然只支持DefaultLit,不过我们考虑添加一个选项,可以保留GBuffer数据,比如switch平台使用移动端的渲染器但它不是Tiled Based GPU,所以GBuffer数据始终会存下来,我们可以利用它去实现一些效果,比如支持Screen Space Reflection来提高switch平台上的画面品质。



Eye Shading Model较为特殊,它用到了Subsurface Profile Shading Model,所以在移动端上用了5个Custom Data,只能把其中两个Custom Data压缩到一个通道里,有时看到有些参数感觉精度不足也许就是这个原因。图为Eye Shading Model的效果,由左至右依次是DefaultLit、移动端上以及PC上的效果。



以下依次为Two Side Foliage Shading Model、Cloth Shading Model以及Hair Shading Model的效果,这都是以前不支持现在支持的Shading Model,可以看出它比以前默认的效果有了很大提升。







我们再看一下延迟贴花的一些效果,左图为前向渲染的效果,右图为延迟渲染的效果,可以看到延迟渲染的贴花支持光照计算,具有更加丰富的效果,但需要额外的带宽对移动端代价太大,所以移动端不支持Dbuffer的贴花。



在光照和阴影支持的情况中延迟渲染大概分成以下几个pass:

第一个是方向光的pass,可以支持方向光的Light Function,支持Clustered Local Lights、Planar Reflection以及CSM的Shadow和Distance Field Shadow。如果只有一个方向光,反射也可以在这个pass里一起计算;但如果有多个方向光,反射就需要放在一个单独的pass里去计算,以免重复计算。

Local Light也有自己的pass,支持IES Profile、Light Function和SpotLight Shadow,Local Light是分两次绘制的,第一次在stencil标记灯光所覆盖的区域,第二次在该区域的像素里着色,以达到性能的优化。Simple Light是粒子里创建的简单光源,通常在Clustered Local Light阶段绘制,如果没有开启Clustered Local Light才会有自己的pass,此过程和Local Light的pass类似。



另外在虚幻5.1中,移动端也支持了延迟渲染的Lighting Channel。PC上的方式是把Lighting Channel写到Stencil Buffer里,然后在shader里面去读取,但由于OpenGLES不支持TexureView的扩展,对于移动设备来说较为困难,所以我们在硬件上,也就是直接在Stencil Test上去做。

我们尝试了两种方案,第一个方案是先遍历所有灯光,然后再遍历所有的Lighting Channel,通过stencil test对受影响的像素着色,如果有多个Lighting Channel,还需要在stencil里去标记哪些像素已经被着色过,以免重复照亮这些像素,最后再把这个stencil的值清除掉,这个方案的好处是一个灯光可以支持多个Lighting Channel,但过程复杂,也有一些额外的性能消耗。



所以我们选择了第二个方案,只遍历所有的灯光,只做一次Lighting Channel的stencil ,整个过程会简单很多,但是每一盏灯就只支持一个Lighting Channel,如果有多个Lighting Channel的话就会出错,这也是美术上需要留意的地方。



我们也遇到过一些兼容性问题。在一些Adreno 660设备和Mali G57的设备可以看到延迟渲染的效果不太对,但是在其它设备上是正常的,后来我们发现是GBufferC用了sRGB的格式导致的,如果不用sRGB格式就可以修复这个问题。



在支持Clustered Reflection的时候,我们发现FReflectionCaptureShaderData Uniform Buffer会申请一个长度是2048的静态数组,但是因为我们在OpenGLES上是打开了Emulated uniform buffer的方式,它就会造成编译失败,因为它相当于是用uniform component去存储,但是因为设备上只支持1024个Uniform Component,所以会造成设备上不支持。

所以我们支持了一种混合的申明的方式,可以单独标记这个结构,使用真的Uniform Buffer来创建就可以正常编译了。



另外,有一些pass我们之前没有考虑过,我们有MRT的形式,所以没有设置这些MRT的BlendState,只设置了SceneColor,其余的MRT默认的都是可写的状态,但是在Shader里面,我们其实并没有对这些RT输出过任何数值,但是在一些PowerVR的设备上,这些RT的内容被覆盖或清空了,导致我们的渲染出错,所以我们就把这些相关的pass全部设置了MRT的BlendState,改成了不可写的状态,解决了这个问题。



针对延迟渲染我们主要做了两方面的优化。一方面是带宽的优化,我们尽可能不去占用带宽,只在必要时将数据存下来,比个DepthAux可以直接申明成memory less,因为我们不支持MSAA,所以不需要考虑关于MSAA的问题。第二个是将GBuffers也申明成memoryless,不占用带宽;还有SceneDepth只在需要时才使用,如果PP阶段没有用到Depth的信息,我们就不需要存下来。

另一方面,我们对性能也做了一些优化,我们发现开启延迟渲染后,半透明阶段的渲染耗时比以前高,后来我们发现,是因为半透明阶段也默认开了Clustered Local Light和Clustered Reflection,对性能产生了比较明显的影响,由于我们本身对于半透明的光照和反射效果的需求并没有那么高,所以我们修改成在半透明阶段关掉这些计算,半透明的效率就和以前持平了。



鉴于不少开发者依旧在使用虚幻4开发游戏,我这边列举了以下虚幻5与虚幻4之间,针对延迟渲染技术的一些主要差异。请大家可以参照表格调整开发策略,如有需要,也欢迎大家从虚幻5里面改动过来。我的分享就到这里,期待下次还有机会与大家分享虚幻引擎的最新技术!

回复

举报 使用道具

1

主题

8

帖子

12

积分

新手上路

Rank: 1

积分
12
发表于 2022-9-20 08:19:22 | 显示全部楼层
和Unity比较起来怎么样
回复

举报 使用道具

您需要登录后才可以回帖 登录 | 立即注册
快速回复 返回顶部 返回列表