本文出自该视频:https://www.youtube.com/watch?v=X_ir86-Cpvk,由Florin Pascu和Dmytro Ivanov分享。
一、我们会遇到哪些挑战
1.在生存游戏开发中,我们经常需要构建一个丰富且复杂的场景,移动端很难跑得动,所以为了优化我们通常会使用实例化网格体来渲染。但是实例化网格体没有很好的自动剔除和LOD功能,所以我们需要一些功能去优化它。
2.在构建场景时我们也会遇到一种情况,生存游戏可能会需要一个洞穴场景供玩家探索,洞穴里面定向光又照不到,所以只能用局部灯光。而在一个区域内局部光照太多会导致性能问题,接下来也会讲讲有什么功能能优化光照问题。
3.在渲染静态网格体时会重新缓存MDC,所以我们需要修正一些网格体绘制指令传给RHI,以达到更好的性能表现来适应移动端硬件。
4.实现运行时PSO编译。在生存游戏的场景中通常包含很多材质,如果我们一开始加载的时候收集材质的PSO,就需要很长的加载时间。
正如上文所提到的,我们在生存游戏中使用实例化网格体(后文会用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等。
为此我们的解决方案如下:通常某些移动平台会有一个深度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配置文件里调整默认参数。
在前向局部灯光中MDC有不同的排序,MDC包含有着色器资源的状态等参数,然后传递给RHI进行网格体绘制。在静态网格体中我们会缓存MDC,但是这些MDC发生变化时还是需要重新缓存这些MDC的。比如当玩家在拿着火把到处乱扔的时候,那个火把网格体一直在重新缓存MDC。为了避免频繁的MDC刷新,我们会一直采样局部灯光的缓存纹理,就算纹理是空的也会采样。虽然说这样可能会使用CPU性能换取GPU性能,但这也是值得的。
旧的PSO收集,一句话概括就是,开发的时候要收集PSO,游戏打包好之后玩家又要编译这些PSO。花费这么长的时间实际在游戏中用到的这些PSO只有5%到10%,这场时间交易非常不值得。
以下是iOS平台中预缓存PSO的耗时。
运行时PSO预缓存的缺点:因为是运行时,所以会导致游戏中CPU和内存的占用。如果用旧的PSO收集系统,我们可以找到哪个材质不能编译PSO,但是运行时就需要见到材质的时候才知道那个材质有没有问题。如果没能及时预缓存PSO,可能就会出现物体不渲染的情况。
为了解决这些问题,我们只会在需要的时候对缓冲区清零。并且添加对其他架构的特殊支持,比如ArrayFloatToPcm16、ArrayPcm16ToFloat、ArrayMixIn等等。所有这些在5.4中直接可用,不需要调整控制台指令什么的。
对于C++文件迭代,安卓系统有一个特性,就是开发中的软件支持将动态库剔除在APK文件以外加载。我们只需要修改那个java文件就能让软件读取对应位置的动态库,然后C++迭代后替换那个动态库即可。这样做之后大项目的C++迭代时间就缩短到了一分钟以内了。
把包体传到对应的平台硬件上,有人会选择WIFI,有人会选择USB。无线频段发展虽然迅速,但如今而言有线传输仍然是最快的。尽量使用USB3协议的接口去传输我们的包体,它的速度有200+MB每秒,而USB2协议只有50MB每秒。所以尽量使用更快速的USB接口,确保速度能有5Gbps以上。
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知道哪些行为占用内存比较多。
近期焦点
扫描下方二维码,关注后点击菜单栏按钮“更多内容”并选择“联系我们”获得更多虚幻引擎的授权合作方式和技术支持。
长按屏幕选择“识别二维码”关注虚幻引擎
“虚幻引擎”微信公众账号是Epic Games旗下Unreal Engine的中文官方微信频道,在这里我们与大家一起分享关于虚幻引擎的开发经验与最新活动。