为手机优化生存游戏 | UnrealFest演讲精粹

科技   2024-10-24 19:01   上海  
前言
虚幻引擎因出色的画面而闻名,在主机平台与PC上表现良好,但我们也不能忘了移动端设备。而对于移动端设备,性能优化是一个巨大的难题,今天要跟大家分享的是关于移动端平台优化的引擎相关功能与知识。

本文出自该视频:https://www.youtube.com/watch?v=X_ir86-Cpvk,由Florin Pascu和Dmytro Ivanov分享。

正文

一、我们会遇到哪些挑战

1.在生存游戏开发中,我们经常需要构建一个丰富且复杂的场景,移动端很难跑得动,所以为了优化我们通常会使用实例化网格体来渲染。但是实例化网格体没有很好的自动剔除和LOD功能,所以我们需要一些功能去优化它。

2.在构建场景时我们也会遇到一种情况,生存游戏可能会需要一个洞穴场景供玩家探索,洞穴里面定向光又照不到,所以只能用局部灯光。而在一个区域内局部光照太多会导致性能问题,接下来也会讲讲有什么功能能优化光照问题。

3.在渲染静态网格体时会重新缓存MDC,所以我们需要修正一些网格体绘制指令传给RHI,以达到更好的性能表现来适应移动端硬件。

4.实现运行时PSO编译。在生存游戏的场景中通常包含很多材质,如果我们一开始加载的时候收集材质的PSO,就需要很长的加载时间。

5.音频性能问题迭代时间问题内存管理问题等等。
二、渲染
2.1 GPU场景优化

正如上文所提到的,我们在生存游戏中使用实例化网格体(后文会用ISM简称)会比平常的项目要多。ISM会通过一个个实例化网格体簇(A batched draw)去计算剔除。默认的ISM剔除计算对于移动端来说很消耗性能,我们需要解决这个问题。还有一点是默认的SSBO顶点着色器实现在某些移动端平台是不受支持的,我们也需要解决这个问题。

为了专门应对移动端,编译着色器会为所有可见的实例写入InstanceData缓存,每一个基元占用512字节,每一个实例占用256字节。我们将会使用UBO而不是SSBO,这样就能解决部分硬件不能采样顶点着色器了。这样带来的好处是直接访问,速度更快。但是需要注意的是,这个在主机平台上的限制是64KB,在安卓上是16KB。也就是说在主机平台上,一个实例化网格体簇绘制(A batched draw)最多有128个基元(或者256个实例),而安卓就是32个基元(或者64个实例)。所以在实际使用中,如果簇中网格体数量大于这个值,就会多次调用DrawCall,但是一般是不会有状态变化的,所以这些DrawCall还可以接受。

为了启用GPUScene,我们需要在DefaultEngine.ini这个配置文件中设置r.Mobile.SupportGPUScene 1。这个优化功能将会在所有支持UBO的平台或者使用对应RHI的平台自动启用,比如安卓的Vulkan或者OpenGL,PC平台的Vulkan,Windows里的D3Dxx等。

2.2 前向着色优化
局部光照在移动平台上的性能不太好,如果有6/7个局部灯光放在一起,渲染时间就会达到40ms,这个时间对应的帧率是完全玩不了的。这其中的原因是在像素着色器正在对网格进行采样并且历遍周围的局部光照,然后调用BRDF(这里聊的是前向着色,暂时不考虑延迟着色)。

为此我们的解决方案如下:通常某些移动平台会有一个深度PrePass,我们可以利用这个深度信息去对光栅进行采样,从深度去计算世界位置,然后用世界问题计算灯光衰减。我们用了两张渲染目标去储存灯光的重要信息,图1是一张RG11B10的灯光颜色与亮度纹理,图2是一张RGBA8纹理,三个通道代表的是三个轴,用来对应灯光的方向,Alpha通道用作SpecularScale。然后这些纹理传到BassPass,对这些纹理进行采样,然后再调用BRDF就行,不需要再历遍周围的灯光。

用上面这个方案也会有一个问题,因为我们总是在处理所有像素,即使我们在画面上只有局部的像素在受灯光影响。所以我们在PrePass和上述方案中的Pass之间引入了一个新的Pass,这个Pass是一个图块状剔除方案。如果灯光只影响了局部像素块,那就会剔除其他部分的像素块,降低RT的性能消耗。以上方案有可能在一些平台还是不管用,因为一些平台没有PrePass,像素着色器上是没有空间给我们放这些RT的。

我们可以在编辑器里启用它(本地光源缓冲已启用),也可以通过控制台指令r.Mobile.Forward.EnableLocalLights 2来启用它。我们也可以和上面提到的GPU Scene优化指令结合在一起使用,并且在对应的平台Engine.ini配置文件里调整默认参数。

2.3 网格体绘制指令优化

在前向局部灯光中MDC有不同的排序,MDC包含有着色器资源的状态等参数,然后传递给RHI进行网格体绘制。在静态网格体中我们会缓存MDC,但是这些MDC发生变化时还是需要重新缓存这些MDC的。比如当玩家在拿着火把到处乱扔的时候,那个火把网格体一直在重新缓存MDC。为了避免频繁的MDC刷新,我们会一直采样局部灯光的缓存纹理,就算纹理是空的也会采样。虽然说这样可能会使用CPU性能换取GPU性能,但这也是值得的。

所以根据自己项目的情况决定是否启用这个优化功能:r.Mobile.Forward.LocalLightsSinglePermutation 1。
2.4 运行时PSO预缓存优化

旧的PSO收集,一句话概括就是,开发的时候要收集PSO,游戏打包好之后玩家又要编译这些PSO。花费这么长的时间实际在游戏中用到的这些PSO只有5%到10%,这场时间交易非常不值得。

这会儿就要引出新的运行时PSO预缓存机制了,加载到场景里的材质会将它的PSO放在编译队列中等待编译。我们可以为想要的材质触发PSO预缓存,比如一些对Gameplay影响重大的材质或者是一定会出现在场景内消耗很长编译时间的材质。如果出现了一些运行时编译PSO流程的问题,PSO没能编译出来,材质显示不出来,就可以使用回旧的PSO收集系统。我们可以通过r.PSOPrecache.***的子代码调整更多关于PSO预缓存的参数,比如调整硬件给多少百分比的算力去编译PSO、PSO如果缺失会执行什么操作等等。关于PSO缺失,会有几种操作可以选择,默认将不生成Primitive,或者生成Primitive但是不渲染任何东西,亦或者使用默认材质渲染

以下是iOS平台中预缓存PSO的耗时。

运行时PSO预缓存的优点:先不编译不会遇到的材质以达到更快的加载速度。无需做一个收集所有内容的PSO收集系统。系统可以调整更多参数,我们可以选择我们想要的材质进行预缓存。

运行时PSO预缓存的缺点:因为是运行时,所以会导致游戏中CPU和内存的占用。如果用旧的PSO收集系统,我们可以找到哪个材质不能编译PSO,但是运行时就需要见到材质的时候才知道那个材质有没有问题。如果没能及时预缓存PSO,可能就会出现物体不渲染的情况。

我们可以通过设置r.PSOPrecaching 1来启用这个PSO预缓存功能,支持Vulkan/OpenGL,会在5.5支持Metal
三、性能与迭代
3.1 音频优化
音频在移动平台上占用了大量的CPU时间,单核心就占了20%到30%。音频混音器在虚幻中,为了避免使用的音频损坏,会预先将完整的缓存区清零,然后尝试尽可能填充。所以在进行性能数据分析的时候,就会发现一堆malloc、memzero、memcopy的内容。虚幻是针对x64架构进行优化的,对于arm或者neon架构会有一些性能问题。

为了解决这些问题,我们只会在需要的时候对缓冲区清零。并且添加对其他架构的特殊支持,比如ArrayFloatToPcm16、ArrayPcm16ToFloat、ArrayMixIn等等。所有这些在5.4中直接可用,不需要调整控制台指令什么的。

以下是两段针对对应架构写的函数,感兴趣的小伙伴可以研究一下:
3.2 C++迭代时间优化
众所周知,虚幻是由大量C++代码编写而成的,所以通常的编码语言仍然是C++。但在安卓上,C++迭代有点浪费时间。改一行代码,编译和运行,等待启动,对于一个大项目来说通常需要耗费4到5分钟,更不用说还要重新打包apk文件并在平台上安装。我们或许可以探讨一下这个流程,看看有什么需要优化的。

UBT通过Unity构建生成动态库,然后将生成出来的库和其他文件通过文件列表交给Gradle进行打包工作。但如果使用Gradle的默认增量打包方式,当我们改了某些内容重新打包时,包体会越来越大。所以我们采用了重新打包APK的方式,但是重新打包要消耗很多时间。

对于C++文件迭代,安卓系统有一个特性,就是开发中的软件支持将动态库剔除在APK文件以外加载。我们只需要修改那个java文件就能让软件读取对应位置的动态库,然后C++迭代后替换那个动态库即可。这样做之后大项目的C++迭代时间就缩短到了一分钟以内了。

从apk剔除动态库功能可以在项目设置中找到,并且在5.4版本中是实验性功能,我们可以尝试这个功能,看看对我们的迭代流程有没有优化。支持在Visual Studio AGDE中快速启动,如果您习惯Rider,也可以Rider和VS一起使用。
3.3 传输优化

把包体传到对应的平台硬件上,有人会选择WIFI,有人会选择USB。无线频段发展虽然迅速,但如今而言有线传输仍然是最快的。尽量使用USB3协议的接口去传输我们的包体,它的速度有200+MB每秒,而USB2协议只有50MB每秒。所以尽量使用更快速的USB接口,确保速度能有5Gbps以上

3.4 安卓的内存分析

UE5.4终于有了安卓的内存分析工具。这个工具借助新的快速调用堆栈遍历器,对游戏的性能影响很小。结合Unreal Insight一起使用可以实时监控另一块屏幕上跑着的游戏的性能情况。使用网络连接的方式进行监控,一段时间内运行还算正常,当缓存区占满以后,会对游戏性能造成影响。我们今天的建议是在对应移动端机器上写入监控记录,然后再拉到电脑上使用Insight分析

为了启用内存监控,我们需要在命令行中添加-trace=memory -tracefile=...,运行这行代码adb shell run-as %package_name% touch files/UEEnableMemoryTracing.txt。5.4的符号化不能开箱即用,所以我们需要手动设置好。

可以捕获通过libc.so传递的UE外部的内存分配,在构建apk包时将-ScudoMemoryTracing传递给UBT,这样我们就可以通过UBT知道哪些行为占用内存比较多。

结语
以上便是一些对于移动端设备的优化功能与知识,希望能够对您在移动端平台上的创作有帮助。祝创作愉快,工作顺利!
近期焦点
优化 Android 移动设备的打包大小
精选免费商城内容 - 2024年10月
Dimension Studio见证越来越多的导演转向虚拟制片
虚幻引擎C++调试技巧:数据断点
Motion Blur用UE5为《黑色国度》带来逼真视觉效果


扫描下方二维码,关注后点击菜单栏按钮“更多内容”并选择“联系我们”获得更多虚幻引擎的授权合作方式和技术支持

长按屏幕选择“识别二维码”关注虚幻引擎

“虚幻引擎”微信公众账号是Epic Games旗下Unreal Engine的中文官方微信频道,在这里我们与大家一起分享关于虚幻引擎的开发经验与最新活动。


虚幻引擎
Epic Games 旗下 Unreal Engine 虚幻引擎官方订阅号
 最新文章