手机游戏引擎的优化

565次阅读
没有评论

我们的手机游戏引擎一直在跟随着游戏项目的进程不断优化。一开始是因为游戏引擎在手机上帧数达不到要求。得益于 EC++S 框架,我们把初期用 Lua 快速开发出来的几个核心 system 用 C 重写后,得到了质的飞跃。

其实这些核心代码总量并不算大。例如在 profile 中表现出来的非常消耗 CPU 的一个场景树更新系统,用 C 重写了也才 200 行代码 ,但在优化前 Lua 版本会消耗超过 1ms 的时间,而用 C 重写后,时间已可以忽略不计。

另外,我们采用了类似 skynet 的 ltask 做多线程框架,把业务尽量拆分到多线程中并行处理,这也极大的减少了每帧的耗时。除了主业务逻辑外,UI 、粒子系统、IO 被分为几个并行线程。且渲染底层的 bgfx 也是按多线程渲染设计的。这些并行流程间只通过少量的消息通讯,所以,并行的总工作量并没有比单线程模型更多。ltask 也可以很方便的调节工作线程的个数,用来更好的适配手机的 CPU 。

从xcode 的调试信息看,在游戏场景丰富时,大约会占用 280% 的 cpu 。换句话说,如果我们采用的是单线程架构,在不删减特性的前提下,是无论如何都无法做到流畅的。

我的开发用手机为 iPhone 12 mini 。目前,游戏锁定在 30fps ,而每帧实际时间开销为平均 10ms 左右 (7ms ~ 15 ms 之间)。所以理论上锁定 60fps 也是完全做得到的,但因为不是动作类游戏,所以无此必要。

目前遇到最大的问题是游戏的能耗。优化游戏引擎让它尽量减少能耗,是过去在 PC 上做开发所没有过的经历。

让玩家的续航时间更长是一方面的原因,更重要的原因是:一旦让手机长时间工作在高能耗状态下,超过了手机的散热设计功耗 (TDP),必然会逐步发烫。最终会导致手机 CPU 自己降频,帧率也达不到了。

以前以帧率为唯一衡量标准放到手机上就行不通了。如果只为帧率达标的话,一般是在达标的基础上尽可能的提升画面质量,或是采用更简单易维护的算法。例如,有些算法为了简单,就直接每帧重复计算。而 cache 计算结果尽量避免重复计算,通常会增加代码的复杂度,却能减少 CPU 的使用。

现在,优化变得没有上限。只要能减少 CPU 开销都值得做。减少的开销全部能兑换成更长的续航时间和更高的散热效率。从散热角度看,手机真不是个好的游戏设备,switch 这种带风扇的设备要好得多。GPU 的开销更是如此,虽然 iphone 的旗舰级的 GPU 性能开起来远超 switch (从字面上的数字看,超过了 ps4 ),但实际上峰值性能除了给人几秒的惊艳外,远远达不到玩家的需求。iphone 12 只有 6W 的 TDP ,而 switch 的 TDP 达到了 15 W ,是它的两倍半。我们在手机上设计游戏,需要适当裁剪效果,把能耗控制下来才行。

我们的引擎使用了基于物理的渲染 ( PBR )。材质、光照都比较复杂。技术虽然不算新,但现在看起来在手机上运行还是比较勉强的。我发现像素着色器(fragment shader)开销比较大。而这恰巧是最容易优化的:改一行设置,直接降分辨率即可。iphone 12 mini 的视网膜屏有 476 ppi ,如果按最高分辨率的像素绘制 18×6 的点阵字母会小到我的眼睛几乎分辨不清。按一半分辨率渲染 1170 x 560 给我的感受,画面质量只下降了一丁点。但能耗居然可以下降到全分辨率的一半左右。(而每帧的时间开销并无太大区别)

为了得到准确的能耗情况,我没有采纳 xcode 给我的数据,而使用了更苛刻的测试方法:

每次测试前把电量充到 97% ,然后静置到刚好变成 96% 的那一刻开始启动游戏,加载足够复杂的场景,连续玩 16 分钟。以这样相同的测试条件,分别对全分辨率和半分辨率各测试两次(共四次),我发现全分辨率下的耗电几乎是半分辨率的一倍。

而考虑到能耗开销由 CPU 和 GPU 共同承担,修改分辨率丝毫不影响 CPU 的使用,那么 GPU 开销实际不只翻倍。或者说,我们引擎的 CPU 部分优化已经足够了,接下来需要重点考虑的是 GPU 开销。

btw, 我们游戏引擎几乎是用 Lua 编写,只在最近一年把 profile 后发现的少量性能瓶颈系统平替为 C/C++ 版本。用占比不大的 C/C++ 代码解决了 Lua 框架的性能问题。而 Lua 部分易于开发维护,(多虚拟机)还可以更好的适应多线程框架。

另一个能耗大头是特效系统,它主要是 CPU 方面的能耗很大。这块使用的 effekseer ,美术一开始只想追求好的效果,使用的比较粗犷。一开始发现它吃 CPU 比较多后,就将其移到了独立服务(可并行的线程)中,每帧耗时(实际帧率)倒是立竿见影的降低下来了,但能耗并无改善。毕竟,做的工作并无减少。

我想到的折衷方案是在镜头之外的粒子发射器就停止工作。这样从画面上来说是不准确的。设想一下一个火焰或烟雾效果,粒子片是逐步弥散开的。如果粒子发射器一旦离开镜头就删掉,重新进入镜头再重新发射,画面上会有些奇怪。但这样的画面 “bug” 我认为是为了性能而必要牺牲的准确性,可以接受。

我觉得只优化引擎代码是不够的。接下来的时间,还需要仔细优化美术资源:尺寸更小的贴图,更低面的模型更简单的动画,应该可以带来更好的性能。

 

Read More 

正文完
可以使用微信扫码关注公众号(ID:xzluomor)
post-qrcode
 
评论(没有评论)
Generated by Feedzy