« skynet cluster 模块的设计与编码协议 | 返回首页 | 为什么美术和策划在使用 git 时会遇到更多麻烦 »

Unity3D 的大场景内存优化

我们公司的一个 MMORPG 项目最近在内存方面碰到了红线,昨天开会讨论了一下。我提出了一个改进方案,写篇 blog 记录一下。

问题是这样的。在当下的手机及平板硬件设备条件下,操作系统留给应用的可用内存并不多,大约只有 500M 左右。

和 PC 环境不同,手机上是交换分区的机制来对应一些临时突发性内存需求的。而手机必须保证一些系统服务(某些高优先级后台业务)的运行,所以在接电话、收取推送等等意外任务发生时,有可能多占用一些内存,导致操作系统杀掉前台任务让出资源。

根据实际测试,游戏想跑在当前主流高端手机上必须把自己的内存占用峰值控制在 400M 内存以下,350 M 会是一个合理的值,而这个值是远远低于 10 年前的 PC 游戏标准的。而我们的项目却是一个写实类型的 拥有大场景的 MMORPG 。

Unity3D 是在智能手机普及以前设计的,远没有料到会被广泛用于手机游戏的制作。它在设计之初并没有为低内存环境考虑。内存使用太粗犷,是在使用 Unity 开发 MMORPG 项目时最被吐槽的一点。

为手机游戏定制游戏引擎,最特别的,和 PC 游戏不同的两点就是内存必须严格控制、能耗必须严格控制。比如在我们开发的 ejoy2d 中,会为资源数据中的项目引用定制短指针,即资源内部的相互引用使用 32bit 偏移量来代替 64bit 指针,每个指针节省出 4 字节内存;变换矩阵使用 6 个 32bit 的定点数,资源中相同矩阵共享一份数据;资源数据尽量连续存放,避免小数据块太多造成内存碎片浪费内存等等。这些显然是 Unity 没花精力去做的。在 PC 上,省下几M 几十M 内存微不足道,但在手机上很可能就是生死之间。

ps. 能耗问题是另一个有趣点。在 PC 上你可以通过多线程并行,压榨出高 fps ,可以不管 CPU 多烫;但是到了手机上,即使 CPU 8 核心已经是标配,还是尽量不要这么做。因为在总任务相同的情况下,单线程能做完的工作只要拆分到多线程上完成,就一定意味着总工作量增加(至少增加了线程间协调的工作)。增加了总能耗。玩家是不想玩一个插着充电线也会玩关机,手机滚烫的游戏的。这个问题有机会我另写一篇 blog 展开,今天是想谈谈内存。


在我们最近的测试中,在较坏情况下,我们的游戏会占到 360M 内存左右,已经接近了内存红线,所以要考虑进一步的优化。其中,较大的一块是游戏场景,占了 120M 内存。

有趣的是,这 120M 内存中只有大约 50M 是用于场景上物件的贴图、模型等等资源数据(注:我们项目没有使用静态批次合并,那样更消耗内存。);也就是说,有 70M 内存用于构建场景本身的结构。所以,并非让美术人员尽量复用同样模型的花花草草就可以省下内存的。换句让美术人员更容易明白的说法,我们的场景中摆的东西太多了,不是减少贴图用量,把同一块石头到处摆可以解决的。这和过去制作 PC 游戏的常识不同。

所以,大部分现有的手机 MMORPG 的画风偏卡通幻想风格不是没有道理。因为那样,可以用有悖现实的物件比例,场景物件个头大,就可以用更少的数量去充斥场景。而写实风讲将就细节丰富,用诸多细节去填满视野。在过去 PC 上,这不是问题,只要少做点独特的模型,少用贴图就能把内存降下来,手机上不行了。


我们并非刚刚意思到这点。一开始,开发人员就针对大场景制定了技术解决方案。

我们在场景上,认为设定了若干包围盒,勾画出一块块小区域。一旦玩家离开包围盒太远,程序就会把包围盒里面的物件卸载出内存。然后在美术设计上,不让玩家有可以从远处观望的角度。我们的美术风格会尽量保证场景细而精致、不追求空旷宏大的场面。

但这还做的不够,我提出了一个改进方案。


  1. 保留包围盒方案。但是包围盒略微扩大,允许包围盒重叠,并可以用多个包围盒来定义一个区域。同一个场景物件只可以属于一个区域,即使它的位置在多个区域内。(区域可以重叠)

  2. 所有物件都标记分类出外观物件和细节物件。比如一个城市的城墙就是外观物件,而城内的所有东西都是细节物件;一片树林的大颗植物是外观物件,地面的花花草草是细节物件。一般情况下,大部分物件都默认是细节物件,只有少数需要远观的才标记成外观。这点,其实原本就做了视距分层,只不过是为了在渲染时做显示剔除用的,并没有用于控制内存。而这次,需要对外观物件和细节物件单独打包分类,便于分开卸载。

  3. 当玩家处于一个区域内部时,必须保证这个区域的外观物件和细节物件都加载到内存。如果之前并不在内存,也需要开启异步加载的流程。当一个玩家距离另一个区域比较近时,只需要确保该区域的外观物件在内存即可,可以卸载任何不在区域的细节物件。


在以上改进方案里,把包围盒扩大以及允许多个包围盒一起构成区域,是为了改进数据加载的时机。单一用距离判断会有瑕疵。比如在城墙外,即使隔的很近也不需要加载城内的细节。而我们完全可以在城门外加一个缓冲的小区域并入城市区域,只要玩家一踩到城门口,城内的细节加载流程就开始启动了。

而允许区域重叠,并让物件唯一归属于单一区域则可以解决城门外的细节物体提前加载时机。城外的这块缓冲区上的物件就不必归属到城市板块,它们早在玩家在城外活动时就加载完毕了。

Comments

没有自研引擎不行啊。我们没有这种困扰,没用过untiy3d,也不知道是untiy3d不行还是你们不行。

不用unity3d你开发的3D游戏内存能到什么程度?你其它那些帖子都是扯淡。只有产品的画面。祯数,内存占用这些指标才能说明问题。平常看你夸夸其谈。我想看看你的产品效率到底怎样。

看到云风这一篇,加载时机让我想起了环形区域来解决AOI问题的方法。

什么游戏啊?

@云风 这个方案18年前的小话西游就已经用过了吧

这 120M 内存中只有大约 50M 是用于场景上物件的贴图、模型等等资源数据(注:我们项目没有使用静态批次合并,那样更消耗内存。);也就是说,有 70M 内存用于构建场景本身的结构。

这句话怎么理解,这部分内存究竟是什么

请问你们用什么格式的贴图?能否通过渐进加载格式改善内存使用量同时减少内存和rom的反复读写?

打格子做区域动态加载是上面提到的我们原版方案的一个更简化处理。这篇 blog 是对这套方案的改进意见。

有看unity源代码么?自己做一些修改应该可以减少很多

不用staticmesh,渲染还是会受影响呐。
我们是把把场景分割了,用Additive加载。
打格子做区域管理。直接加载卸载整块场景。

Post a comment

非这个主题相关的留言请到:留言本