麥子 发表于 2020-5-24 11:22:36

【转载】UE4项目《和平精英》渲染技术浅析

几天前针对基于Unreal Engine 4开发的《和平精英》做了一次渲染技术分析,整理了一下,分享出来,希望对于使用Unreal Engine 4开发游戏或者开发类似《和平精英》这种大世界FPS游戏的开发者提供一些有价值的参考。同时也希望能让更多的开发者了解到Unreal Engine 4的强大之处。
我通过GPU调试的方式,结合自己的引擎开发经验和对UE4的了解,分析了一下《和平精英》的图形渲染过程,整理了几个模块的内容,并结合UE4的各项功能特性给大家就游戏渲染技术方面来做一个浅析。关于一款游戏的性能指标,可能大多数开发者最先想到的就是Drawcall数量(其实应该是Batch指标,Drawcall数不等价于Batch数,这个问题这里先不做讨论),所以我们先来看一下Drawcall的数据。
下面表格左侧数据为摄像机在右侧图示视角位置时的Drawcall数量。除了个别场景,《和平精英》整体Drawcall控制在300以下,可谓是非常稳定。


选了两个视角,分别统计了一下渲染相关数据。
场景0:密集建筑群纹理显存占用:5346KBVBO更新:42次平均Overdraw:2.83x顶点数:881442面片数:293814每Drawcall平均顶点数:2671.04每Drawcall平均面片数:890.35帧内总API调用次数:4740
场景1:密集植被群纹理显存占用:5346KBVBO更新:32次平均Overdraw:2.49x顶点数:681834面片数:227278每Drawcall平均顶点数:2426.46每Drawcall平均面片数:808.82帧内总API调用次数:3948
Drawcall数量控制在300以下,Overdraw控制在3x以下,各项数据都非常稳定。数据这么稳定,而且从各个视角位置都很难看到穿帮的情况,由此可以看出游戏研发团队非常有经验,实力非常强。
当然,UE4提供的Scalability系统也是功不可没的,例如,我们可以通过调节Scalability参数来控制屏幕上显示的Foliage实例的数量,以此来控制Drawcall数量。
在场景中截取了一些Mesh数据,统计了一下顶点数和面数数据,供美术同学参考:

上图所示为在场景内某个视角位置的场景渲染过程,切换其他视角位置场景渲染顺序可能略有小范围变化,但大体上一致。目前通过渲染过程和已经拿到的部分Shader来看,渲染管线用的是Forward Rendering,这种光源单一的场景Forward Rendering比较合适。上图为渲染过程的中间结果,在调试过程中发现植被好像执行了Early Z-pass。找到植被绘制的Drawcall,然后全局搜索提交的数据,发现这几个Drawcall在前面已经被执行了一次。下图中的Drawcall序号说明了植被在当帧渲染的不同阶段被绘制了两次:《和平精英》将可能产生大范围重叠覆盖并且Overdraw很高的植被(Masked Mesh)元素执行了Early Z-pass。这样做在后面再次渲染自身和其他物体的时候可以有效地由硬件来做Early Z剔除,缓解FS渲染压力。至少在Masked Mesh渲染的时候可以有效避免AlphaTest导致的硬件管线优化能力的打破。UE4内建提供了这方面的优化能力,我们在Project Settings -> Engine -> Rendering -> Optimezations下可以找到功能开关。《和平精英》开启的就是如下设置:Opaque物体没有做Early Z-pass,在Forward管线下Drawcall数量和FS压力间需要找到一个平衡点,不同的项目需要根据自身情况做一些性能测试,再做决定。Early Z-pass剔除大量的FS计算:

UE4的植被系统是我深入研究过的引擎中做的最好的。排在其次的应该是在许多年前使用的CryEngine3,当然在当时CryEngine3应该排在第一位,只是很可惜后来Crytex落寞了。Unity的地形植被系统在功能上较前两者差了一点,在我过往使用中也发现了一些性能问题。如果是小范围使用没有问题,在较大范围使用的时候最好做一下性能测试,对比一下将植被直接摆放到场景中和在Terrain中刷上去的性能差异,尤其是Culling的性能。
众所周知,植被系统一般都是采用硬件Instancing来渲染。UE4会将以Static Mesh(不支持Actor)种植的植被进行分组,每一个分组称为一个Cluster。后续的LOD、Culling、Render过程都是基于这个Clusters分组进行了,这种方式对于大世界、大量植被的游戏来说尤为重要。
我曾经做过测试,Unity在这方面的表现略差一些,当场景内植被量过大(超过2万个实例)的时候,Culling的代价就会升高。这种情况要自己进行优化。我采用的方案是写插件,将美术刷在地形上的植被重新自动摆放到场景中,然后根据分割参数自动建立Clusters,每一个Cluster由Unity的一个CullingGroup来管理,以此来降低场景管理的代价。具体技术细节有机会再跟大家分享,此处不做深入讨论。
UE4支持植被Culling的平滑过渡,《和平精英》在地表Layer中刷的颜色和其上面刷的植被颜色相近,所以植被实例由远及近的过程中过渡非常平滑,这一点非常值得借鉴:《和平精英》地表植被使用的面数极少,加上LOD处理,部分植被基本上就是1~2个面片,Billboard级别。但是实例的量很大,都采用硬件Instancing渲染:当然屏占比较高的植被LOD使用的层级较高,面片数也是比较高的,下图中所示植被面片数可达2478 / 3:另外场景设计上植被的体积搭配非常科学,植被的多样性表现很好,视觉上给人的自然的感觉也很到位。当然这里从技术角度评估也是很科学的,比较高大的植被屏占比也高,LOD层级高,一般面数也高,所以在摄像机内出现的的数量尽量保持很少,以至于仅有一个而无法进行Instancing。
通俗点讲各种类型的植被 实例数量 x 单株植被面数 的结果几乎是相等的(2337 ~= 600 * 4)当然在不同角度这个误差都极大,总会有某些观察角度让这一理论不能完全站住脚,但是这确实是一个非常科学的设计方向:UE4提供了Foliage Scalability特性,游戏可以在运行时动态决定植被的密度,所以这点对于适配不同档次的机型非常有帮助。下图所示为一处植被非常密集的地方,同屏植被的实例数为1193,此时Drawcall数为281:植被的贴图制作上有些地方还是值得介绍一下的:1. 植被的贴图制作方面树叶与树干采用同一张贴图。在传统的做法中,由于树叶要开启AlphaTest渲染,所以跟树干一般是做成两张贴图,这就导致了两次Drawcall,而且是两个批次。《和平精英》的这种做法最大程度上合并了Drawcall和批次;2. 一些大范围覆盖地表的杂草类植被由于贴图细节要求不高,另外非常有可能在同一视角内混合出现,同时渲染。由于这类植被的模型都是几个面片组成,结构相同,所以通过将他们的贴图合并到一张Atlas中来最大程度上合并Drawcall;
UE4内建支持Lightmass + CSM阴影方案,并且支持动态与静态Shadow的完美融合,可以以较低成本实现高质量的阴影效果。从目前拿到的数据看,《和平精英》实时阴影和烘培阴影都有使用。角色及其附近一定范围内物体渲染采用实时阴影,几乎所有视觉上能够产生明显阴影贡献的室外场景元素都参与实时阴影渲染(都投射阴影),但是像马路及山体岩石以及一些低矮灌木和杂草这些元素没有参与实时阴影投射,因为生成的阴影或者没意义或者视觉贡献不明显。下图为角色在上图位置,方向光光源处投射的Shadowmap结果,通过分析Drawcall内容和Depth buffer内容可知,采用的是控制阴影范围的Cascaded Shadow Map(级联阴影)技术。左边Depth区域内容为右侧Light生成的Depth buffer内容中远离视锥体处的内容,目前看实时阴影采用的是2级CSM。从Depth buffer中的内容可以看到,房屋建筑,植被中高大的树木都参与了实时阴影渲染投射,但是生成Shadowmap的场景物体范围控制的比较小,这样可以在保障效果的前提下大幅降低阴影渲染压力:分析下面这张图我们可以得出结论,马路未参与Shadowmap生成:建筑室内阴影采用的是Lightmass烘培阴影方案,我在建筑的绘制过程中拿到了Lightmap信息:另外顺便说一下,上面第一张图左上角为一张Normal map,《和平精英》为室外建筑绘制提供了基本的光照细节:


游戏引擎的Culling算法大部分都是在CPU实现,或者说是CPU相关性比较大。所以我们用抓取GPU数据的方式很难得出《和平精英》到底如何应用UE4的Culling算法。我们先来看一下UE4都提供了哪些Culling相关的功能,然后结合我们测试的结论可以大体上猜测一下。UE4在Culling方面提供了多种算法:1. Distance Culling2. View Frustum Culling3. Precomputed Visibility Volumes Culling4. Dynamic Occlusion Culling上面四类算法从1至4执行代价越来越高,这也是引擎默认的算法应用顺序。1和2因为运行代价很低,对于移动端来说一般都会默认使用。3因为是预计算的,所以会有一些内存消耗,但是运行期代价很小,对于移动平台非常友好。4包含多种算法,一般常用的就是Software Occlusion Queries和Hardware Occlusion Queries(还有个更保守的版本HZB),前者主要是CPU消耗,后者主要是GPU消耗。由于代价较高,而且后者对于硬件特性有一定要求,所以移动平台使用优先级较低。
《和平精英》1和2类算法肯定是有应用,2必定是引擎正常执行Culling的一个环节。我们主要看一下Occlusion Culling相关的算法应用。我做了一个测试,摄像机朝向一致的情况下,在有建筑遮挡的位置和无建筑遮挡的位置,观察一下Drawcall数量是否一致:
上图中,无建筑遮挡的视角位置Drawcall数量为216,有建筑遮挡的视角位置Drawcall数量为181,结论是有遮挡的位置Drawcall数量较低,游戏使用了3或4类Occlusion Culling算法。在房间内移动摄像机,在某些角落会出现遮挡剔除穿帮的情况:
如果使用了Hardware Occlusion Queries会有相应的Render Buffer存在,并且会有查询遮挡的Drawcall,但是我在整帧绘制过程中并没有找到。所以4的可能性不大。遮挡剔除可能使用的算法剩下的只有Precomputed Visibility Volumes Culling了。


UE4提供了Landscape系统,这是一套非常高效的地形渲染系统。原理是将地形数据编码到Texture,然后在VS中获取数据,在GPU中动态生成最终网格的方式渲染地形。这种方式在存储上能够有效减少空间占用,比同样复杂度的Static Meshes地形节省6~7倍内存占用。这一点对于《和平精英》这种大世界游戏来说非常有意义。同时这种地形实现方式也能够充分利用Texture的Mipmap技术来方便高效地实现地形的LOD,另外在光照等各种细节上面也能提供比传统Static Mesh地形更好的效果。所以还在用Static Mesh来做地形的朋友可以考虑改为使用UE4的Landspace系统。
Unity从2018.3开始也采用了这种地形渲染技术,但是目前还不太完善,在过去的项目研发中我们发现了非常严重的Bug,并且至今也没有修好。
Landscape系统会将地形拆分为多个Component,每一个Component的Heightmap是单独存储的,每一个Component也是一个独立的渲染单位(一个Component可内含多个Section,一个Section是一个Drawcall的单位)。《和平精英》的地形系统采用的正是UE4自带的Landscape,下图所示为一个地形Component渲染过程中渲染API绑定的贴图信息。红色线框内的Texture包括Layer混合信息和每一层Layer的Texture及Component对应的Vertex数据和Normal Texture:上图中我用蓝色线框标记出了两张贴图,这两张贴图不属于Landscape的Component渲染依赖的信息。在反复调试过程中我发现了《和平精英》地形系统的另外一部分:
在Landscape地形的相同位置有一个由52560个顶点和17520个三角面组成的呈现基础地貌的Mesh。这个Mesh覆盖了整个游戏场景的范围,渲染依赖三张Texture:一张Color Texture,一张Normal Texture和一张Shadowmap。
通过对比Color Texture和游戏中的画面可以发现我们在游戏过程中,在高空见到的地表信息主要由这个Mesh提供的,而Landscape主要是用于角色落地之后的精细的、复杂的地貌呈现。由于《和平精英》是超大地形的游戏,视野范围可以覆盖很广,为了节省性能,在视野很远的地方不需要非常精细的视觉效果,所以基于Landscape的地形块可以在超出了Culling距离的时候不渲染,而是使用这个一个Drawcall就可以绘制完成的覆盖整个场景的Mesh来提供视觉贡献。非常巧妙的场景设计方案,值得借鉴。下图所示为Landscape的Section渲染过程,红色区域标记出的为一个Section:从上图中我们可以看到游戏内公路、山体、建筑等一些静态物体的渲染是在地形系统渲染之前执行。像素容易被覆盖的物体后渲染,充分利用硬件优化。由于Landscape系统在VS阶段生成最终Mesh信息,所以在Component的渲染过程中,相同LOD级别的地形块的VS输入可以是一致的,这样多个Section(LOD计算的单位)就可以利用Instancing进行合批渲染:上图的视觉效果由两部分地形系统重叠组成的:图中带有雾效的部分为地形Static Mesh的渲染结果,主要贡献远距离地形视觉效果;正在绘制的较为清晰的地形块为Landspace的渲染结果,主要贡献近距离地形的视觉效果。另外值得一提的是《和平精英》场景内有很多如下图所示公路:
对于这种公路设计UE4提供了Spline Tools工具,通过Spline Tools先建立Spline,然后能够一键修改Landscape的Heightmap数据来适配Spline的形状,以此来实现非常贴合地形的公路效果。

《和平精英》场景内静态物体渲染大体上可分为两种模式:一种是摄像机近处的静态物体,采用正常的独立Mesh渲染,如果场景中包含多个相同的Static Mesh,则采用Instancing渲染;对于远处的静态物体则采用HLOD技术,而且使用了LODLevel层数>=2的HLOD。在飞机上及跳伞过程中观察地表建筑时,场景内渲染的信息为HLOD模型及贴图信息,所以在高空俯瞰场景的时候渲染效率更高。当然在场景地表游戏过程中,远处的静态物体也会退化到HLOD渲染。
UE4内建支持HLOD技术,引擎会根据用户自定义参数或默认配置将整场分割成一个个Cluster,然后合并每个Cluster内的Mesh。HLOD可以存在多层LODLevel,每一级LODLevel都可以比上一级采用的Mesh更加简化,Cluster可以使用其包含的Mesh的更高LOD等级来合并生成。游戏过程中根据场景内物体距离摄像机的距离或者物体的屏占比自动选择相应的LODLevel来显示,以达到降低Drawcall和GPU处理消耗的目的。
下图为我用UE4做的一个实验场景,图中从绿色到黄色区域呈现的是场景内不同的LODLevel的渲染:下面拿《和平精英》游戏内跳伞过程中的一帧画面来举个例子。HLOD模型信息:HLOD烘培贴图信息:渲染结果:从最终渲染结果和资源数据对比来看《和平精英》HLOD烘培过程中使用了合并贴图选项,并且Mesh的组织方式是将独立Mesh作为子Mesh合并,这样在最终渲染的时候能够合批。
《和平精英》场景中所有的静态物体都有HLOD烘培,下图内容是由场景内的多个HLOD Cluster渲染的结果:摄像机近处的静态物体渲染没有什么值得过多介绍的。建筑模型设计方面有一点是值得借鉴的,建筑在设计上最大可能地重用一套贴图,然后将一个场景内所有建筑的贴图控制在从一张Atlas上索取,最大可能地节约Texture数量和大小,同时也最大程度上起到合批的作用(此处说的不是合并Drawcall,Drawcall是否合并取决于是否采用动态合批技术):之前有人问我植被系统适不适合用HLOD来做,我们先来看看《和平精英》是怎么处理植被的。植被系统与场景内的其他静态物体采用了不同的渲染方式,植被仍然采用LOD + Instancing技术渲染。原因有两个:第一,Unreal的HLOD合并贴图默认行为不会考虑Alpha通道,对于植被来说必须采用AlphaTest渲染,所以在Unreal内部植被系统不太适合HLOD技术渲染;第二,植被系统有大量重复Mesh,使用Instancing可以实现高效的合批,如果使用HLOD会让大量重复Mesh存在在场景中,导致内存暴增。
GPU调试过程中发现大量Shader是编译后的二进制格式,无法看到代码实现,所以游戏内采用的具体光照算法无法准确推测,在此就无法给大家做分析了。

如果开发一款与《绝地求生》类似的FPS手游,那么Unreal Engine 4在目前来说绝对是最佳选择。本文开始写于4月末,不知不觉写了这么多。上周Epic发布了关于UE5即将到来的消息,让我也跟着小小振奋了一下。希望能为Unreal Engine社区做一些贡献,希望能为有需要的人提供一些帮助。
by    WeiBoPub



99淡陌生 发表于 2020-9-21 10:34:50

6666666666666666666666666666666

奈之若何丶 发表于 2020-5-25 14:08:11

虽然没看懂,但很感谢分享

akbinlin 发表于 2020-5-25 14:00:51

虽然没看懂,但很感谢分享

别瘠薄闹 发表于 2020-5-25 12:32:48

你永远不知道你的队友在游戏里干啥

piemon 发表于 2020-5-25 11:31:47

虽然看懂,但很感谢分享

入学新生 发表于 2020-5-25 10:40:28

666

jj95198 发表于 2020-5-25 09:32:11

牛B。。
页: [1]
查看完整版本: 【转载】UE4项目《和平精英》渲染技术浅析