近年来,游戏行业开放世界赛道火热,但是跨平台体验一直是业界的难题。最近几款备受期待的开放世界大作移动端接连翻车,要么出现跳票延期的情况,令众多玩家感到失望。
但在前阵子的UniteShanghai2024上,来自中手游的仙剑IP首款开放世界RPG《仙剑世界》带来了开放世界跨平台优化的技术分享,在大会上介绍了许多项目曾经走过的弯路,以及对应解决的经验技巧。
以下是Untiy中国对《仙剑世界》主创团队的采访原文,内容干货十足,值得全文阅读。
《仙剑世界》作为仙剑 IP系列的最新?篇?作,从故事和剧情上延续了仙剑的精髓。在仙剑33年的世界观下,游戏打造出了?个浪漫唯美的江南全景、磅礴恢弘的蜀?、神秘苗疆等区域,构成?个384平?公?完整的?缝开放?世界。以东?题材为起点,以江南的浪漫为开端,以巴蜀的幻想?格作为延续,通过不断的迭代和内容更新,为籼?们奉上?款难以忘怀的仙侠?作。
Unity中国有幸采访到了主创团队,并邀请他们参加了Unite上海2024,与开发者们面对面交流。
可以为我们简哪个单介绍下团队和项?吗?
?家好,我们是中?游旗下的满天星?作室,核?成员为原巨??络巨??作室原班团队,参与 过《征途》瑞游研发,主导了《巨?》端游,《仙侠世界》系列端游和《?珠最强之战》?游的研 发。?前我们正在开发的《仙剑世界》,是中?游?研?发的仙剑 IP?款开放世界RPG,游戏致?于在多端平台打造?个万物有灵的东?浪漫幻想世界。
但在项?研发早期,由于场景庞?,对视野要求也很?,我们?度纠结如何能在有限的时间内整 合各类效果好的 feature,管理开销。所幸有UnityHDRP 管线,得益于它的特性,最终帮助我们实现了对游戏世界的畅想,充分展现出东?浪漫幻想世界的魅?。
HDRP相对于URP,给《仙剑世界》带来了什么好处?
URP确实是一个非常简洁灵活的渲染管线,但是当我们需要制作一款在PC体验的大世界游戏的时候,URP就会有一些稍显不足的地方。特别是我们当时使用的是Unity2021,URP在原生支持的feature上略微少一些。反之,HDRP管线则为我们内置了大量非常实用,且效果很好的feature,确实为我们在项目初期,提供了快速,稳定,优质的画面效果提升。
Unite大会上《仙剑世界》HDRPFeature展示
例如,HDRP下内置的SSGI,在我们自己的PRTGI方案还未落地时,为游戏提供了很好的GI画面支持。SSS也为角色皮肤提供了非常好的质感,让角色变得更加真实自然,比项目早期使用预积分Lut的皮肤SSS方案要更加润泽和透亮,总体来说更符合我们游戏中国风人物的皮肤质感。Tile/Cluster的点光解决方案,也让场景灯光师可以肆意摆放灯光,为副本、夜晚提供很棒的氛围和质感。另外对于实时天气和日月变化,当时HDRP下提供的Volume体系也可以实现非常炫酷的效果,甚至提供管线的管理和混合效果。又比如,我们剧情导演希望引擎这边能提供一个比较高质量基于物理的DOF(景深)效果,那HDRP下的DOF就是一个很好的选择,它基于物理相机的整套参数调整,可以让我们的剧情导演非常容易的上手和理解。
HDRPSSAO遮蔽效果展示
在研发早期,人力相对比较紧张,但又正是项目确立游戏风格效果、质量体系等标准的时候,HDRP相比于URP会更加适合我们,因此我们也非常坚定的选择了HDRP管线作为我们的渲染管线。从最终结果上看,它也为我们节约了不少预研时间,加快了开发进度。当我们想要尝试某些效果,几乎只需要在管线设置上打开相应开关,即可看到它带来的画面变化,非常的方便。
HDRPPCSS软阴影效果展示
从 PC端移植到移动端?概花了多久?移植过程中遇到的最?的困难是什么?
在最开始的时候,项?是基于 URP管线的,但是由于PC端?渐新增的画?提升需求,我们加?了HDRP来作为PC端的管线。也就是说这个时期,我们确实是URP和HDRP同时运?的。
之所以选择同时进?,是因为 HDRP原?不?持移动平台,放眼全球?业,也鲜有其他游戏对HDRP进?移植的案例。所以我们?开始对它报以谨慎的态度,对HDRP管线技术不断地进?学习和整合。但是随着对HDRP使?的深?,我们了解了它的各种特性和优点之后,出于对引擎维护和资产流程管理成本的考虑;也因为我们团队相对有?较成熟的PC和移动平台的开发移植经验,对??的技术实?也很有信?。所以经过评估之后,我们决定将HDRP向移动平台进?移植。?此,我们游戏就只有HDRP?套渲染管线了。
Unite大会上《仙剑世界》HDRP跨平台优化技术分享
在开始移植之前,我们预留了约一个月左右的时间作为移植的缓冲时间,以防止遇到难以解决的突发问题。另外,我们提前评估了所有PC上我们正在使用的,甚至还未使用,但是很可能会用到的HDRP下所有feature,它们在移动端上可能的性能表现,用于确定是否能在移动平台保留或简化这些feature。我们经过多次会议和讨论,确定了大体的移动端管线结构和流程,以及对应的RT的格式和尺寸。然后就是通过平台宏控制,将移动平台下预期不佳的功能,从管线和Shader上先进行了屏蔽,渲染管线也进行了重新设计,以适配移动平台的带宽压力。在完成这些整理之后,我们才开始正式移植到移动真机上的。整个移植过程非常非常顺利。由于绝大部分的问题在上真机之前就已经预料和屏蔽了,因此几乎没有遇到什么困难就可以在移动平台的高端设备上跑起来。
Opaque的ColorBuffer的load是不必要的
只是真正的难关,是在跑起来之后。
在HDRP移植到真机之后,我们发现其运行性能实在不是很好,大约可能只有10+、20+的FPS。这个离我们的预期太远了,所以我们开始Profiler真机看看问题都出在哪里。
主要的问题就是在Shader上面。HDRP的标准LitShander在移动平台下编译出来数千行还带有循环的指令,实在是让移动平台的GPU难以应对。因此我们开启了大规模的Shader的整理和简化工作。整个优化流程进行了很多轮,时至今日,我们仍然还在持续为Shader进行移动端的优化。但是在初期一两次核心整理之后,FPS就有了大幅提升。主要的优化方向有,去掉复杂的,很多次循环迭代的某些算法(如阴影,环境高光等),改用适合移动端的轻量级算法。另外光栅化数据的结构整理,也很有必要。去掉各种不必要的feature的分支。因为我们已经熟悉了解了自己项目的需求,因此不必保留这些Shader里面可能的分支流程。最后,由于HDRP原本并不是为移动平台设计的,因此会存在大量的动态索引设计。动态索引确实在设计的灵活性上非常方便,但是这会使得buffer的访问速度,甚至大量中间变量的计算速度变得很慢。因此去除主方向光、阴影等等渲染输入数据的List结构,使其变成可以通过静态偏移的方式去访问。使得Shader代码从行数,寄存器访问速度,甚至CPU数据填充方面都有很大的性能提升。
在移动端开发的过程中,我们也深刻地认识到——移动平台的优化是需要持续进行的,即使到了项目开发的中后期,也应该对移动端性能的保持关注和维护。过程虽然会有困难和卡点,但是优化过后的效果总能让移动端的表现更上一层楼。
PC端的资源?产?作流和移动端是怎么平衡和配合的?
我们是以 PC为基础?产模型资源的,在技术给定的规格框架内,保证基本效果的情况下向移动端过渡。这个过度包括模型的?数,贴图材质的处理,LOD的处理和衔接,以及技术Shader上的调整。两者之间有共?和继承关系,也有完全独?的部分。?如PC的最后?级LOD是移动端的第?级。当然,移动端因为兼顾性能和效果问题,我们也会对?些模型进?单独制作和调整。这个调整也是在PC资产的基础上去想办法,这样才能保证PC和移动端的效果统?。
游戏场景及规模都很?,资源和渲染管理??做了哪些特殊的设计吗?
仙剑世界的地图面积高达384平方公里,涵盖多样化的地形地貌、人文景观和精怪生态。其中所涉及的资源种类和数量非常庞大,我们在寻找开发效率、包体大小、更新效率、性能优化和游戏品质之间的平衡点时,遇到了极大的挑战。通过项目前期的不断打磨和调整,我们最终探索出了一整套的涵盖资源制作、场景编辑等上下游的规范、流程和工具链。
首先是为不同平台制定不同的美术资源标准,确定每个平台的资源制作规格、命名规范和品质标杆。
其次我们将场景物件按“花草树木山石湖河器物”等进行细分,为每个类别制定专门的场景摆放规则。通过资源代理机制,地编只需制作一份场景,我们可以针对不同平台进行细节控制,在不影响功能性和品质的前提下适配平台特性。
《仙剑世界》场景地编
在打磨和调整过程中,我们逐步完善了工具链,以标准、自动、可视、高效为原则,助力各个生产环节,提供效率,保障品质。
再次,我们引入了Unity新一代资源构建管线SBP,其灵活、可定制、高效的特性,使我们可以从容管理数十万且还在不断增长的资产,随心控制打包粒度,缩减CICD的时长。
最后,我们通过对Unity引擎的深度定制化,让游戏可以承载更丰富的细节,容纳更多的玩法,体现更高的画面品质。
在渲染方面,我们除了在近景使用了HIS系统进行大规模合批(在Unite大会详细分享过这个技术,它主要针对材质相同,mesh相同,但是数量非常庞大的组件式的model类型),还利用Unity的BatchRendererGroup来针对mesh、材质重叠度不高,数量不大,但是种类很多的静态非组件式物件进行合批操作。这样我们近景对象的DrawCall问题就得到了很好的解决。
通过HIS进行场景管理,同样的场景FPS可以达到230以上,性能增加了15倍
未来的上线计划能和我们透露吗?
在前段时间结束的多端付费删档测试中,?些 HDRP在各个?机型号上的适配效果也得到了初步验证。虽然?前还没达到最佳理想的状态,但是从团队优化效果的轨迹来看,对于HDRP移动端的移植还是保持着乐观的预期。
同时,?论是 PC端还是移动端平台,我们都希望游戏给玩家带来更好的感受。最近也是再度开启了?范围的共研服测试,针对画?、玩法、体验等不同层?进?优化和迭代。例如针对我们核?“万物有灵”的玩法,新增多御灵助战、注灵探索、御灵仙术、御灵派遣等丰富内容;针对“五灵?克”战?机制,优化战?效果与玩法策略;针对游戏基础体验,优化UI表现和战??感等等。后续将会有更好的效果表现呈现给?家,也可以期待?下。
采访还附上了 U3D引擎开发工程师何姝姝,在UniteShanghai 2024 上的分享《<仙剑世界>: HDRP 管线下的超大世界游戏与跨平台优化》演讲全文。
大家好,我叫何姝姝,来自上海凡影满天星工作室,我们最近开发了一款名叫《仙剑世界》的开放世界游戏,很高兴今天有机会跟大家分享我们在《仙剑世界》这款游戏中所遇到的技术方面的经验和数据。
立项的时候,我们计划制作一款大世界游戏,它可以上天入地、自由飞翔,它有什么特点呢?它非常的大,它是一个开放性地图,地图直径有 24公里,总规模达到384平方公里,全场景自由探索,并且完全没有loading条,我们支持PC、iOS和安卓,可能还会有其他平台后续的支持计划。在PC上,我们要求很高的质量,并且移动平台我们要求非常流畅。
《仙剑世界》御剑飞行效果展示
我们项目早期是 URP管线,优点是简洁、灵活、代码总量小、便于维护,控制它的成本比较小,但这也是它的缺点,它实在太简单了,缺少大项目需要很多feature。当时我们有两个选择,一个是自己造轮子,进度和计划不是很允许。另外是寻找各种feature的package,把它们整合到一起,但是整合成本也很高,可能会有很多bug。总的来说,我们当时的诉求是快速、好看、灵活、效果丰富和稳定。
我们当时比较重视曾经使用了/或者还在使用的效果feature,包括SSS、Tile/Cluster的精确光系统、volume级别的环境效果、管线管理,SSAO,基于物理的光照单位(lux、lumen等),物理的光照、大气,更加完善的PBR着色,基于探针的环境GI、SSGI等全局光照体系,PCF和PCSS的阴影体系,后处理中比较方便的如DOF、运动模糊等效果体系,以及TAA、DLSS等抗噪体系,更完善的ColorGrading和Tonemaping等画面风格化处理,最后是极度便捷好用的RenderingDebug系统。
我这里给大家直接在游戏内截的图,比较真实的反馈了 SSS带来的皮肤效果。与选人或者抽卡等这种影棚灯光效果不同,大世界游戏场景的灯光会相对比较简单,这样更能给大家带来直观的SSS效果体验。我们这里看到,即使是从月光照射下,甚至在阴影下,SSS 都给角色皮肤带来了较为清透的质感效果。
SSAO也是游戏中比较重要的效果,左下图是SSAO贴图,左上是关闭了SSAO的效果,右下是开启SSAO的效果。开启SSAO之后,屋檐下的遮蔽更加明显,建筑和画面也因此变得更加立体和扎实。
在后面这组画面中,建筑基本位于阴影之下,SSGI没有开启之前,屋檐内部光照缺乏层次细节,黑糊糊一团,使用了SSGI之后,不仅外部柱子因为遮蔽减少,亮度提升,夹缝中的屋檐也因为反弹光被照亮了,并且屋檐下每个横梁、每个面都受到了不同GI的效果,画面也丰富了很多。
得益于 HDRP便捷的Volume系统,我们可以方便快捷的实现日月变化和动态天气效果。
PCSS也是HDRP的一个特色feature,它可以为我们实现比较高质量的软阴影效果,大家可以看到树叶打到柱子、树叶、地面,墙壁上的阴影呈现不同程度的soft效果,比较柔和自然。
这里我想给大家重点推荐一下 DLSS,在关闭DLSS画面的竹叶噪点很多,也比较闪烁,开启DLSS的那一刹那,画面得到了非常大的提升,没有噪点,更加稳定,甚至树叶形态都更加清晰了。DLSS确实是非常重要的提升效果的feature。
我们后来接入 HDRP之后遇到了一些问题,首当其冲的就是Shader,HDRP的Shader是基于Uber方式进行管理的,它提供了非常多便捷的feature支持,HDRP开发者将众多大型游戏中或者其他工业渲染中可能会运用到的各种功能浓缩到一套UberShader 下,这样做的好处非常明显,它可以在早期开发时期以很低廉的学习成本,直接运用和控制这些feature。比如所有的Lit shader可以轻易切换成更适合自己的游戏算法。中后期,Uber为我们带来了很多问题,比如它的代码量实在太大了,所以阅读起来非常耗时。其次它的编译太慢了,初期可能改一个底层的Lightloop文件需要编译20分钟时间,简直难以让人忍受。最后,代码或者keyword和分支过多之后,整个shader所占用的总内存就会变得很庞大,达到数百M以上。
这三点每一点都很痛。好在后面我们都摸索出了一些解决方案。
首先,我们仍然是建议,在 HDRP下,尽可能的使用Uber的方式进行shader管理HDRP的feature过多,渲染代码量非常大,且分支过多,因此一定要注意维持shader以及keyword的数量,keyword的新增是申请制,去掉一切不会使用到的feature。采用静态keyword去替代掉动态keyword。
第二,减少 if分支的使用。去掉不用的if分支。HDRP因为分支过多,会导致编译器无法合并,位于不同分支内的相同运算过程,这将使代码复杂度、运算量大大增加。
第三,尽量严格统一底层,使用相同的 Lit和LightLoop等底层光照文件,便于后期统一优化,因为我们一定需要优化。效果没有确定下来之前,建议使用ShaderGraph 来探索渲染效果,建议花费一些时间修改ShaderGraph的package中源码,增加其ShaderGraph 转成CodeShader后的代码可读性和维护性,否则转出来的代码太长了,实在没有什么可读性,优化起来也不是很方便。
第二个问题是 Volume的更新,因为有实时日月天气的变化,依赖Volume以后,可以很轻松实现。从右图可以看到,一个环境配置可能包括有数十个不同的组件,包括Unity自带的组件和我们自己实现得功能等,但是每个组件又带有数十个变量,会造成刷新性能不好的结果。因此,我们的优化方向是需要严格控制刷新频率,比如没有变化的时候不刷新、不需要刷新的变量也不刷新,这部分的优化是必不可少的大家可以根据自己的项目情况进行极限压缩,主要是为了减少刷新的计算量。
控制好 Volume体系开销之后,我们发现RenderGraph 管线开销也很大。首先HDRP管线,我来给大家梳理一下它的大体工作流程:
他们大部分都需 newRendererListDesc() 函数,这就需要引擎层为这些所有的pass,执行SRPBatcher工作。Unity引擎底层会将这些,通过了Filter裁剪器之后的Renderer,pack成RenderNode,然后把他们分发到多个Job线程,去整合他们的材质,变量,区分每一个变量是否是Global的,如果是,还要把变量从Global区域copy出来,填充形成自己这个Drawcall的cbuffer。另外还有每个需要裁剪的Camera,还需要对所有RenderNode进行裁剪,还有各种Probe的裁剪,Bound计算等等。管线的工作成本是非常高的。而且这个高,是会随着相机数量成倍数增长。即使开了Job,并且实在多线程中完成这些计算,但是当场景中的渲染对象多到一定程度之后,我们发现RenderGraph在PC上的成本都是很高的,更何况并行能力更差的移动平台。有时候其开销能达到10ms以上,基本上很难接受。因此我们的优化策略是一定要控制住Camera的数量,绝不额外增加Camera。剩下的相机,一定增加多种相机类型,以便于精确控制除了主相机之外的相机的渲染流程。例如:UICameraAvatarCamera 等等特殊相机类型。
如果要动态改变环境,当发生变化的时候,每个模块都需要刷新,这个时候就需要对每个相机的每个管线,进行量身定制,以减少除了主相机之外的所有次要管线内非必要计算:
例如:UpdateEnvironment()(他们包括了SkyBox更新,Convolutionmipmap 的计算,GI的球谐刷新等等),我们只需要计算和渲染主相机那一次,其他情况尽可能复用。另外,昂贵的PostProcess体系最好只有主相机有,其他相机只保留必要的feature即可,能去掉的尽量去掉。最后,GlobalBuffer会在多条管线中填充多次,很多时候是可以复用的,尽量减轻管线的负担,尽量减少非必要填充。
跨过了 RenderGraph之后,我们遇到了一个新的问题,那就是Batch。首先,我们已经定制修改渲染管线了,CPU压力就解决了吗?但是别忘了,我们是一个大世界游戏。
这里我给大家补充一些信息。
第一个,HDRP和URP同属于SRP管线,其底层都是具有SRPBatcher 功能的。本来SRPBatcher 是一个优化游戏性能的东西,为什么到这里,就变成一个性能瓶颈了呢?因为大世界的Renderer数量太多了,能达到30W个以上,其CPU管理成本非常非常高,所以Batch成本也是很高。
所以我们开发了一套 HIS系统。场景的Renderer太多,动态计算即使使用Job也很难承担SRPBatch 和底层渲染的CPU压力。那么我们采取的方案是:脱离Renderer对象管理(Renderer仅用于烘焙场景)——这样做减少了绝大部分原本SRPBatch 的压力。
BVHTree场景静态烘焙加速场景管理,它可以预先烘焙记录材质、变量等信息,直接从烘焙结果中得到Batch结构,不再需要动态计算。适合大量的,相同材质Shader的场景组件,可以一次性使用DrawInstanced 绘制成百上千的对象。彻底解决SRPBatcher 合批之后,仍然会有多个DrawcallSet的问题。得益于这个系统,我们可以发现,大量相同的场景组件,其Drawcall以及其他渲染状态切换均只有一次。GPU压力也因此得到降低。
我们可以看到右边图片,HIS合并之后,场景中的大量物体都进行了Instancing合批绘制,非常高效,最高的有1200多次。
这是 BVHTree 的烘焙结果,这个动画展示了4层的BVHTree 的烘焙结果。当我们想要定位摄像机内有哪些渲染对象的时候,可以根据BVHTree 进行加速筛选定位。
我们来看看这个 HIS系统加入前后的性能表现差异,这是以一个Renderer对象超过8W的小型城市建筑群。大家可以通过右上角的统计面板看到,SPRBatch 之后只有10个左右的setpass。但是在我的电脑I7的Cpu上,它仅仅能跑15fps。
我们来看 Profiler,是什么消耗了CPU性能。通过Unity的Profiler,我们可以清楚的看到,CPU每帧总开销超过50ms,其中60%以上来自SRPBatcher 的开销。当开启了HIS之后,我们的FPS可以达到230以上,整体CPU性能增加了15倍。
在场景的管理解决之后,我们又发现大世界中的光照也很麻烦,左边的视频可以看到,制作人要求我们实现实时日月光照角度变化,同时又要支持不同的天气效果切换。右边的视频可以看到角色从地面飞行到三百米的高空中,如果使用 Lightmap,可能需要加载很大范围内的贴图数据,并且PC端还被要求实现一根柱子不同朝向的GI变化,因此传统的Lightmap很难从内存和效果上达到我们的要求。因此我们采用PRTGI的方式来实现实时GI。
我们基本上参考了《对马岛之魂》在 2021的SIGGRAPH上发表的这篇文章,来实现的预烘焙部分。烘焙主要采用了DX12的光线追踪算法,来记录了以0.5米为单位的LightProbe内烘焙信息。具体做法是,烘焙时通过构建一个单位亮度为1的白色天空球的反弹信息x实时天空盒的0阶球谐(天光的反弹)+ 模拟水平面和垂直面的反弹的实时主光的反弹球谐(主光反弹)+实时天空盒SH,计算游戏的最终GI。
我们通过这种预烘焙和实时结合的方式,获得了低成本的实时 GI效果。由于其优秀的性能和内存表现,这套方案可以完全运行到移动平台上,它的性能开销可以控制在2ms内和40m左右。当然便捷的操作也是必不可少了。不能让我们场景美术手动去布置这些探针球,毕竟我们场景很大。因此自适应生成系统必不可少。另外特殊的地方可以进行人为修正也是可以支持的。
但是我们和大部分的 PRTGI系统一样,都会遇到的类似的问题,那就是——漏光。我们可以看到,由于漏光,在墙体物件和外界交界的地方,存在大量不均匀的,不稳定的遮蔽程度,严重影响效果。
我们采用的方案是借助 Dilation(Post-Bake)和VirtualOffset(Pre-Bake),我们很好的控制住了漏光现象,获得了均匀可信的烘焙GI信息,大家可以看同一个场景,漏光解决后,是不是均匀很多?大家可以参考Unity在SIGGRAPH2022 年发布的Enemies论文,我在这里就不作过多的详述了。
PC平台的一些问题我们介绍完了,下面着重给大家介绍一下移动平台。在PC版本逐渐稳定了之后,我们开启了对其进行移动平台的移植。这将是一个更大的挑战,因为我们都知道,HDRP原生是不支持移动平台的,如果我们要做一致,要选择适合的项目时机,我们要预留比较长的容错时间,安排合适的项目进度。由于管线的复杂程度,可能早期8Gen3的手机上只有10FP,但一定要有坚定的信心。
移植前需要做一些什么准备工作呢?
第一,HDRP是同时支持延时和前向管线的。所以需要根据自己项目对渲染的需求,设计自己的移动平台的渲染管线,是forward/deferred。挑选移动平台需要的feature。
第二,由于 HDRP整个管线体系内的Shader都并非面向移动平台,因此有大量feature是使ComputeShader,即使可以优化,也需要做好自己项目的支持的设备性能基线机型选择。因为ComputeShader 可能对少部分低端机不友好。部分featureHDRP提供了Shader和CS两种版本。可以按需选择。如果要支持移动平台,建议尽量使用shader版本。因此要评估自己的项目对低端机下限的容忍程度。如果没有特别长的时间专门去做优化,选择HDRP可能不能做到面向特别低端的手机。
第三,由于是移植,多端通用的资源意味着,往往并不是为移动平台设计的资源。那么需要提前预备好,优化这些资源的人力和时间。根据 PC端的面数和Drawcall数量,估算出多端资源的各级指标,比如角色面数上限,场景面数上限等,这些需要在制作PC资源的时候就要准备好,可以加快移动平台的移植进程。
接下来看一下我们早期遇到的问题,我们早期的 Drawcall数量可能达到了一两千个,带宽开销可能是1G以上,巨量的三角面数有时候达到了两千万亿左右,管线和Shader代码对移动平台不太友好,很多机型上有崩溃现象。
其中一点就是 DrawCall,我们的解决方案就是,前面介绍过的HIS系统。游戏的场景物件,尽可能使用模块组件化设计。目的是为了极大的提高Mesh的利用率,从而提高我们自己写的Insancing合批程度。另外就是HLod系统,HIS系统合并掉了近处的物件,那么中远景的场景就交给了HLod系统。它可以将一大堆建筑合并成一个Drawcall,也极大的减轻了管线压力,是非常值得挖掘的体系。多轮的资源优化和面数控制,最重要的还是输入数据就能控制到接近的数量级上,其次是多级的LOD体系,非常有助于减面。
最后我想讲一下剔除算法,第一个是 HIZ剔除,它用GPUDriven的方式,利用PreDepthPass的深度图,生成HIZ的深度金字塔,将实际上被遮挡的水体、植被等进行GPU上的剔除处理。它的特点是是可以以非常廉价的GPU开销,一次性的剔除掉大量的Renderer对象。但是如果不采用GPU回读的方式,由于它的剔除行为发生在GPU阶段,因此所有渲染数据都需要向GPU提交。因此CPU压力并没有得到释放。如果进行回读,当视角与上一帧变化稍大一些,其回读数据就会丧失可信任性,仍然是会产生CPU的渲染提交,以及GPU上最终的渲染成本。另一方面,由于它是以相同材质和Mesh来作为单元管理,当材质和Mesh的多样性非常高,实例化数量却不大的时候,CPU的管理压力就相对会比较大。
最后,在 CPU方面,我们有采取了Intel的SOC软光栅化剔除来减少从CPU提交Drawcall的数量。CPU剔除就没有材质类型的限制,但是它依赖遮挡体的设计和制作。因此也更适合用于城市中,墙体周围的小道具和小建筑等等的剔除。
这两个剔除算法都很重要。他们能协助我们从每一个层面,去最大可能的减少 Drawcall的渲染数量。
Drawcall看完了,让我们来看看带宽。首先,重新设计管线流程,尽量去掉每个pass之间RT和状态切换的次数,最好不切换。另外,不需要读取RT之前信息的时候,如ColorBuffer在Opaque阶段第一个pass前,使用DontCare或者Clear做为RTLoad 的Flag,避免强迫Load之前的渲染结果,导致带宽增加。
第三,由于 feature的减少,HDRP下Shader的VertShader部分的输出会出现大量冗余操作,例如去掉了NormalBuffer的输出,则无需读取顶点法线,更无需进入光栅化过程等。因此优化过程中一定要去掉Vertex函数输出结构中,不需要frag过程中使用的寄存器类型,不要有浪费的情况,应该控制住每个寄存器。容易被忽略的是使用Unity提供的Mesh.SetVertexBufferParam函数,对Mesh进行Split处理,使其在非着色pass中VertexBuffer绑定大小减少。
第四,可以尝试压缩顶点,但是可能会带来额外的顶点计算量,要权衡一下是否适合,每个项目的情况也不一样。最后,评估优化每一张管线中的 RenderTarget贴图,选择适合移动平台的尺寸和格式。比如能用R16,千万不要用R36,也不要使用全分辨率等等。最后,如果带宽切换太过严重,可以考虑局部使用Subpass,以避免掉带宽Load/Store。
说完了管线部分,我们来看看 shader部分。首先Shader里面尽可能去掉各种,开关某些功能的条件分支和for循环。分支间的即使相同的计算,也无法获得优化。比如寄存器不能复用,可能导致多次相同计算和采样(可能还会产生大量带宽问题)建议将其降低次数后展开for循环,并根据项目feature依赖情况直接去掉不需要的分支(或者采用静态宏条件分支)。
其次 HDRP采用了大量的动态生成的Buffer来进行数据填充,比如GISh,比如主光,点光,阴影设置,等等。严格控制RWBuffer的数量,最大不可超过4个,否则会有移动平台很多设备上的兼容问题。
然后提高 Half计算占比,是我们一个Shader优化的常用手段。但是HDRP的光照强度很可能会超过10W以上,Half计算之后,几乎会一直处于溢出状态,需要进行压缩后再计算,否者我们常年可能会遇到各种溢出。
还有环境高光计算太过复杂,过多的循环和混合,导致性能非常差,由于对画面贡献较小,移动平台上替换成 URP类似的环境高光计算即可。
最后更换阴影 PCSS算法(循环次数过多),使用更合适的阴影算法,如PCF、VSM等。
让我们来看看还有哪些 Shader中被忽略的点。索引大家都知道是什么意思,那么动态索引呢,就是HDRP底层比较喜欢将渲染的数据,比如方向光主光,生成list结构,用for循环的方式在Lightloop中进行渲染,这样,当GPU在执行一次渲染的时候,将两个像素分别派发给了不同的wave,这时GPU就不能清楚的知道,这两个wave在访问方向光数据的时候,是否使用了相同索引数据,那么这样数据访问的结果就会被存储到向量寄存器VGPR中,而VGPR中的数据,是无法在多个wave之间进行共享的。反之,如果这些数据被存到了标量寄存器中,GPU多个wave之间将会只采样和计算一次这个光照数据,在所有wave间进行共享。因此这两种寄存器会给Shader带来非常大的性能差异结果。因此我们对Shader的优化方向,就需要严格控制Shader中VGRP寄存器的使用率。具体的做法是,将HDRP管线下Shader中动态索引数据,比如主光,精确光,阴影等数据,统统都抛弃了list的数据结构,直接定义到Buffer中,用固定的offset去访问这些Buffer数据。这样这些被访问的数据以及这些数据的中间计算结果,都将有可能被存储到标量寄存器中,从而实现wave间的共享,避免掉多次计算。
另外还有容易被忽略的 LRZ的被关闭的条件。除了我们众所周知的Frag中进行与深度相关的Clip操作。还有,比如在这些pass渲染输出了深度/stencil也不行。这里提供一种解决的思路,如果我们希望,Opaque内能LRZ的更高效一些,我们需要尽量将深度和stencil的write,在Opaquepass 内关闭掉。最后,不可以使用SecondCommandBuffer,否则在某些设备上,可能也会被关闭LRZ。
我们再来看看还有哪些注意点,使用 Vulkan,性能指标要好很多,对ComputeShader支持也更好,机型兼容性更强,使用多线程渲染和RenderingBatches Jobs我们来看看右边这个图片。另外,移动平台下,对于CPU和GPU都会访问的的ComputeBuffer,比如CPU内填充setdata数据,GPU也会访问读取这些数据。这种情况,newComputeBuffer的时候,尽量去指定使用ComputeBufferMode的Dynamic/SubUpdates类型,以避免因为申请成默认的Immutable类型。因为Imm类型的Buffer只允许GPU进行读写,但是我们又要setdata,因此渲染底层会帮我们在每帧的最开始,将这个我们会setdata的buffer,通过copyBuffer出一个新的dynamicbuffer,给CPU使用和填充。这样会产生每帧大量的CopyBuffer成本。具体写法可以看右边这个图片。
最后,有源码条件的同学,也可以尝试更改一下渲染 Job的同步点类型,不同的项目可能适合的同步点不一样,有助于更好的实现线程之间,或者CPU/GPU之间的并行。
现在我们来说说移动平台的光照解决方案。得益于 PRTGI烘焙,可以让实时计算的复杂度减少。这是shader优化后效果。我们可以看到房屋下面的AO遮蔽和GI效果也比较明显。
让我们来继续看一下移动平台的点光方案。因为我们是一个多端通用资源的项目。从维护的便捷性方面考虑,移动平台仍然使用 HDRP的Cluster 点光管理方案,但是,我们限制了最大同一个Cluster内支持4-8盏点光,同屏最大支持达到80个以上点光。
精确光循环的计算内容,代码完全被展开,以更好的进行寄存器共享,并利用 SIMD计算,一次性计算多盏点光的光强衰减和diffuse和GGX高光计算。可将点光所有的代码量控制到150行DXBC指令左右。
我们可以看到左边是移动平台的副本点光效果,右边是 PC平台的点光效果。除了资源有所不同,其光照效果基本一致。
最后的最后,我想聊一下 HDRP平台上的各种Debug工具。首先HDRP自带的RenderingDebuger工具非常好用,它囊括了几乎绝大部分PBR渲染流程能的核心参数显示,对于检查材质正确性,它具有完全的不可替代性。
第二个是经常被使用的老牌 Debug工具,它非常好用,与Unity也结合的非常好。Xcode的各种工具Profiler,用于检查IOS真机的内存、CPU、GPU、管线带宽、Shader/CS性能开销,CasheMissing程度等等敏感指标,仍然是最方便的。SnapdragonProfile用于检查安卓真机的性能开销,profiler性能数据也不错,遗憾对vulkan支持较弱。Arm_Mobile_Studio用于检查shader性能,分析寄存器情况,SimplePerf用于查看安卓真机CPU开销情况也很好用。
在 PC上,我们比较喜欢使用的是GPA和Nsight,特别是N卡用户,可以通过Nsight,可以获得更加准确的GPU开销信息。
这是刚才提到的两个算法论文选型,谢谢大家!