Mozilla 实例分享如何优化性能,开发优秀 WebVR 体验

映维网 2018年10月15日)在过去数周时间里,我一直在研发自己最喜欢的益智游戏《Nonogram》(亦名为“Picross”或“Griddlers”)的VR版本。在游戏中,你必须通过使用列和行的计数来确定网格中哪个单元格为绘图方块。我认为这可以成为一款精美,休闲的VR游戏。我将VR版的游戏称为《Lava Flow》

由于《Lave Flow》的定位是休闲游戏,我希望它能够快速加载完成。我的目标是令整个游戏尽可能地小,并能够在3G网络下以低于10秒的时间加载完成。我同时希望它能够以每秒60帧或以上的速度运行。在开发WebVR应用程序时,一致的帧速率是最重要的考虑因素。

1. 测量第一

在优化性能或大小时,我需要确定切入点。确定应用程序实际大小的最佳方法是使用Firefox Developer工具的Network选项卡。以下是具体用法:

打开Firefox开发者工具的“Network”选项卡。单击“禁用缓存”,然后重新加载页面。你在页面底部可以看到总页面的大小。当我开始这个项目时,它显示为1.8MB。

我是采用热门的开源3D库three.js来开发这款应用。显然,最重要的是three.js本身。我没有选择精简版,所以它超过1MB。通过加载three.min.js,现在的大小为536KB vs 1110KB。不到一半的大小。

游戏使用两个3D岩石模型。它们采用GLB格式,并对Web使用进行了压缩和优化。两块岩石的“重量”不到3KB,因此可以进行优化的地方并不多。我编写的JavaScript代码是一堆小文件。我以后可以使用压缩器来减少fetch次数,但暂时尚无需担心。

2. 图像压缩

下一个最大的资源是两个熔岩纹理。它们是PNG文件,总共加起来为561KB。

我将两个熔岩纹理重新压缩为中等质量的JPEG。另外,由于凹凸贴图图像不需要像颜色纹理那样的高分辨率,因此我将其从512×512调整为128×128,将大小从234KB减少到143KB。这令文件从339KB减少至13KB。视觉上没有太大区别,但页面现在已降至920KB。

接下来的两个点则是JSON字体和GLTFLoader JavaScript库。这两个都可以进行gzip压缩,所以我暂时无需担心它们。

3. 音频

现在我们来玩一下游戏,并确保一切正常运行。看起来没什么问题。等一下,那是什么?一个新的网络请求?当然,是音频文件呀。声音直到第一次用户交互时才进行触发。然后系统需要下载超过10MB的MP3文件。DefaultLoader不会将其计算在内,因为我是通过音频标签来进行加载,而不是说JavaScript。

我不太希望等待背景音乐的加载,但预先加载音效的想法不错。加上音频元素没有我所需要的音效控制,也不具备3D功能。所以我把它们作为Audio对象加载到three.js中的AudioLoader。现在,它们在应用程序启动时将会完全加载完成,并纳入下载时间的计算。

加上音频,整个文件的大小为2.03MB。有进步。

4. 减速

我发现了一个奇怪的故障,在重建游戏棋盘时,整个场景会出现暂停。我需要弄清楚发生了什么事情。为了帮助调试问题,我需要在VR Immersive模式下看到每秒帧数。对于大多数three.js应用程序使用的标准stats.js模块,其实际上是通过在WebGL画布上叠加DOM element来实现。这在大多数时候都没有什么问题,但当我们处于沉浸式模式时将不起作用。

为了解决这个问题,我创建了一个名为JStats的little class,它会将统计数据绘制到一个固定在VR视图上方的小方块上。通过这种方式,你在沉浸式模式中随时都可以进行查看。

我同时创建了一个简单的计时器类,从而支持我测量特定函数运行的时间。经过一些测试后,我证实系统需要250到450毫秒才能运行玩家进入新关卡时所出现的setupGame函数。

我研究了代码并发现了两件事情。首先,每个单元格都使用自己的圆角矩形几何和材质副本。由于每个单元格都相同,我可以创建一次单元格并重复使用即可;另一件事是,每次关卡更改时我都在创建文本叠加层。这不是必要的事项。我们只需每个文本对象的一个副本。它们可以在不同关卡之间重复使用。通过将其移动到单独的setupText函数,我能够节省几百毫秒时间。事实证明,三角测量文本的成本非常高。现在,平均棋盘的设置时间大约为100毫秒,这应该不会十分明显,即便是移动头显。

在最后的测试中,我通过网络监视器将网络降低到3G速度,并且禁用缓存。我的目标是首先在1秒钟内完成画面,并在10秒内准备好游戏。网络屏幕显示需要12.36秒。就快完成了。

5. 前进两步,后退一步

我在开发游戏时意识到某些东西的缺失:

  1. 应该有一个进度条以说明游戏正在加载中。
  2. 进入和退出鼠标/指针时需要声音效果。
  3. 每次完成关卡时都应该有音乐。
  4. 启动画面需要很酷的字体。

进度条很容易解决,因为DefaultLoadingManager提供了回调函数。我在HTML叠加层中创建了一个进度条,如下所示:

然后当loading manager告诉我加载了某些事情时进行更新:

结合一些CSS样式,它看起来像这样:

6. 与臃肿作战

接下来轮到音乐与效果。添加额外的音乐需要133KB+340KB,这会令应用程序变得臃肿,为其大小增加了将近0.5M。这可不是好事。

我可以从字体方面进行一定的补偿。目前我正在使用一个标准的three.js字体,而它是JSON格式。这不是一种非常有效的格式。文件可以达到100KB到600KB,具体取决于字体。事实证明,three.js现在可以直接加载TrueType字体,同时无需事先转换为JSON格式。我挑选的字体名为Hobby of Night,它只有80KB。但要加载TTF文件,我们需要TTFLoader.js(4KB)和opentype.min.js(124KB)。所以我加载的内容仍然比以前多,但至少opentype.min.js能够在字体中实现摊销。现在它没有任何用处,因为我只使用一种字体,但它在将来会有所帮助。所以这又是100KB左右。

我今天学习到的事情是,优化总是前进两步,后退一步。我必须调查所有事情并花时间来完善游戏和加载体验。

The game is currently about 2.5MB. Using the Good 3G setting, it takes 13.22 seconds to load.

游戏目前大约为2.5MB。利用Good 3G设置,加载大概需要13.22秒。

7. 重访音频

当我添加新的音效时,我想到了一些事情。所有这些都来自FreeSound.org,而它通常是以WAV格式进行输出。我使用iTunes将它们转换为MP3,但iTunes可能不会使用最优化的格式。我发现有一个文件是编码为192KBps,而这是iTunes的默认值。利用命令行工具,我认为我可以进一步压缩它们。

我安装了ffmpeg,并将其传唤为30秒的歌曲:

从348KB降低至185KB。这可节省了163KB.。总的音频文件从10MB降低至4.7MB,大大减少了应用程序的大小。在没有背景音乐的情况下开始游戏的总下载体积现在为2.01MB。

8. 有时候你会获得意外赠品

我将游戏加载至我的网络服务器,并通过VR头显进行了测试,从而确保一切正常。然后,我尝试在网络选项卡打开的情况下再次在火狐浏览器中加载公共版本。我注意到一些奇怪的东西。总下载量变得更小了。它在状态栏中显示:已传输2.01 MB/1.44 MB。在我开发的本地Web服务器上,它显示为:2.01 MB/2.01 MB。这是一个巨大的差异。是什么原因造成的呢?

我猜测是因为我的公共网络服务器会进行gzip压缩,而我的本地网络服务器却不会。对于MP3文件而言,这没有什么影响,但对JavaScript这样的高度压缩文件而言,影响可非常大。例如,为压缩的3分钟.js文件是536.08KB,压缩后则会达到惊人的135.06KB。压缩会产生巨大的差异。现在下载仅为1.44MB,Good 3G上的下载时间为8.3秒。

我通常会在本地网络服务器上完成所有的开发,并在项目准备好时才使用公共网络服务器。这是我学习到的一课:不能单单固定在一艘船上,而且要经常进行测量评估。

9. 写在最后

这是我在调整应用程序时所学习到的事情。开发第一版本的游戏很简单,但优化游戏并准备发行很难。你需要很长时间的埋头苦干,但最终的结果会非常值得。原型和游戏的区别在细节。我希望这条建议可以帮助你开发出优秀的WebVR体验。