【益达】《永劫无间手游》RenderGraph改造分享
本文来自益达游戏平台的资深引擎开发工程师,有着近八年的游戏从业经验,擅长渲染管线改造提升与优化。
一、RenderGraph简介
渲染管线开发过程中,我们一般将一组渲染管线操作封装成Pass,既可以是渲染某类物体,也可以是单纯设置全局状态。随着各类效果的加入以及跨平台支持,Pass的逻辑越来越臃肿,数量越来越多,管理起来越来越困难(永劫无间手游支持GLES、Vulkan、Metal、DX11、DX12,最低画质有50多个Pass,最高画质有100多个Pass)。此时需要寻找一种方法能够精简Pass逻辑,自动化管理大量的Pass。
2017年GDC上育碧率先分享了寒霜引擎的FrameGraph技术,有效拆解了传统Pass繁重的逻辑,显著提升了复杂渲染管线的开发效率。之后业界便逐渐采用类似架构,如Unreal的Rendering Dependency Graph以及Unity的RenderGraph。

RenderGraph的核心思路是接管传统Pass中的部分逻辑,重组成Setup->Compile->Execute三大流程:先经由Pass的Setup收集资源相关需求以及Attachments声明,然后通过Compile确定最优资源分配与状态设置,最后再执行Pass的Execute并在前后插入资源申请与释放、Attachments设置等自动化操作。在此架构下管线开发人员只需定制图中的红色部分,无需关注剩余逻辑。之所以称作Graph,是因为Compile结果中的Pass流向以及Resource依赖组成了一副有向无环图。

RenderGraph的核心目的并不是为了更优的性能,而是提升开发效率,理论上反而比传统管线CPU性能更差。我们调研时用Unity官方的RenderGraph与其默认管线做了个对比,结果发现CPU性能差了将近一倍。为了能在移动端用上RenderGraph,我们基于URP从新设计并实现了定制版的RenderGraph,最终平安落地全平台。
二、RenderGraph架构设计
2.1 总体设计
传统Pass包含了很多逻辑,如环境配置,玩家交互等,但RenderGraph底层只关注Pass的资源相关操作与数据。UE和Unity官方选择使用函数回调的方式在两者间实现通信,但这种模式对开发者不够友好,代码不够内聚,不适合我们这种改造既有管线的情况。于是我们选择使用组件模式,益达平台通过轻量代理PassBuilder来实现上层Pass与下层RenderGraph的通信:
上层收集RenderGraphPass并通过PassBuilder进行资源声明;
下层编译数据并通过PassBuilder执行RenderGraphPass的渲染需求。

2.2 资源系统
渲染管线的所有操作均是围绕资源展开,传统Pass在所有阶段都是直接操作资源对象,而RenderGraph为了能够集中分阶段管理资源,则是采用资源“引用”的方式。

当帧使用的资源直接由资源系统管理,包括ComputeBuffer和RenderTetxure,支持帧内以及相机间复用;跨帧RT由每个相机自行管理,在相机开始渲染前import进资源管理系统里面。
Setup声明资源,返回Ref;
Compile确定资源申请与回收时机;
PreExecute给Ref赋值真正资源;
Execute通过Ref拿到真正资源;
PostExecute回收资源。

2.3 Compile模块
Compile模块负责将Setup阶段收集到的PassData数据处理成CompiledInfos供Execute阶段使用,包括四个步骤:
资源引用统计与Pass剔除,记录资源的生产Pass与消费Pass,剔除生产的资源无Pass消费的Pass;
资源生命周期计算,找到资源最早的生产Pass作为创建时机,最晚的消费Pass作为销毁时机;
Pass合并以及subpass拆分,基于Pass的Attachments声明,将连续的具有相同声明(subpass的话可以是同序子集)的Pass合并成一个区块;
LoadStoreAction等状态计算,例如:若后续没有Pass区块消费当前区块的Attachments,则StoreAction可以标记成Dontcare

Compile运算量有点大,为了能在移动端使用,我们设计了Dirty Compile机制,只在Setup阶段收集到的数据发生变化时才Compile. 主要监测以下三类数据:
Pass开关状态,变化主要发生在一些特殊效果如扭曲的出现与消失的时候;
PassData数据,主要包含资源读写声明和Attachments声明,这里不涉及资源具体参数,故只需比对Ref即可;
Compile设置,一些会影响Pass合并或者资源状态的设置。

三、RenderGraph落地效果
3.1 代码开发示例
一类相机对应一个RenderGraph,与URP的Renderer类似。RenderGraph可以看作是Pass生产者,可以像搭积木一样构建RenderGraph。Pass支持最大程度的复用,包括RenderGraph内部以及跨RenderGraph使用。

在Pass内部,可以通过PassBuilder与资源系统进行交互,包括申请资源,声明读写资源等。

在Pass内部,可以通过PassBuilder进行Attachments声明。

3.2 真机跑测数据对比
此次改造主要针对渲染管线CPU侧,故对比仅限于主线程渲染管线耗时。PC端直接通过Unity的Profiler工具,Android端通过AndroidStudio的CPU Profiler工具,IOS端通过Xcode的Instruments。用的都是开发包,故只需关注表格里数据的相对值。DirtyCompile能够有效降低性能开销,实测标准画质60FPS局内4排10分钟对战,Dirty帧数占比仅有0.5% (193/38794) ,即使决赛圈最后1分钟,占比也只是升到0.58%
如果您对本站有任何建议,欢迎您提出来!本站部分信息来源于网络,如果侵犯了您权益,请联系我们删除!
微信客服