使用移动 WebGL 让中土世界栩栩如生
过去,将基于 Web 的互动式多媒体体验引入移动设备和平板电脑一直是一项挑战。主要限制因素包括性能、API 可用性、设备上的 HTML5 音频限制以及缺少流畅的内嵌视频播放。
今年早些时候,我们与 Google 和华纳兄弟的团队合作启动了一项项目,旨在为《霍比特人》系列新电影《霍比特人:史矛格荒漠》打造移动优先的网络体验。构建以多媒体内容为主的移动版 Chrome 实验是一项非常富有启发性且充满挑战的任务。
我们针对新款 Nexus 设备上的 Chrome(Android 版)进行了优化,现在您可以在这些设备上使用 WebGL 和 Web Audio。不过,得益于硬件加速合成和 CSS 动画,非 WebGL 设备和浏览器也可以体验到大部分功能。
整个体验均基于《霍比特人》电影中的中土世界地图、地点和角色。借助 WebGL,我们能够对《霍比特人》三部曲的丰富世界进行戏剧化处理并加以探索,同时让用户控制体验。
移动设备上 WebGL 的挑战
首先,“移动设备”一词的范围非常广泛。设备的规格差异很大。因此,作为开发者,您需要决定是希望支持更多设备并提供较简单的体验,还是像本例中所做的那样,将支持的设备限制为能够显示更逼真的 3D 世界。在“穿越中土世界”测试中,我们重点测试了 Nexus 设备和五款热门 Android 智能手机。
在实验中,我们使用了 three.js,就像我们之前在一些 WebGL 项目中所做的那样。我们首先构建了 Trollshaw 游戏的初始版本,以便在 Nexus 10 平板电脑上正常运行。在设备上进行一些初始测试后,我们列出了一系列优化措施,这些措施与我们通常针对低配置笔记本电脑采用的措施非常相似:
- 使用低多边形模型
- 使用低分辨率纹理
- 通过合并几何图形尽可能减少绘制调用的数量
- 简化材质和光线
- 移除后期效果并关闭抗锯齿
- 优化 JavaScript 性能
- 以半尺寸渲染 WebGL 画布,并使用 CSS 放大
在对游戏的第一个粗略版本应用这些优化后,我们获得了令人满意的 30FPS 稳定帧速率。当时,我们的目标是在不影响帧速率的情况下改善视觉效果。我们尝试了很多技巧:其中一些确实对效果有影响;有些效果没有我们预期那么大。
使用低多边形模型
我们先从模型开始。使用低多边形模型无疑有助于缩短下载时间和场景初始化时间。我们发现,我们可以大幅增加复杂性,而不会对性能造成太大影响。我们在这款游戏中使用的巨魔模型大约有 5,000 张面孔,场景大约有 4 万张面孔,效果很好。

对于体验中的另一个(尚未发布)位置,我们发现减少多边形对性能的影响更大。在这种情况下,我们为移动设备加载的多边形对象数量低于为桌面设备加载的多边形对象数量。创建不同组的 3D 模型需要额外的工作,但并不总是必须的。这实际上取决于模型的复杂程度。
在处理包含大量对象的大型场景时,我们会尝试有策略地划分几何图形。这样一来,我们就可以快速开启和关闭不太重要的网格,以便找到适用于所有移动设备的设置。然后,我们可以选择在运行时在 JavaScript 中合并几何图形以进行动态优化,也可以在预生产环境中合并几何图形以节省请求。
使用低分辨率纹理
为了缩短移动设备上的加载时间,我们选择加载大小为桌面设备上纹理的一半的其他纹理。事实证明,所有设备都可以处理最高 2048x2048 像素的纹理大小,大多数设备都可以处理 4096x4096 像素的纹理大小。将单个纹理上传到 GPU 后,对这些纹理的纹理查找似乎没有问题。纹理的总大小必须适合 GPU 内存,以避免纹理不断上传和下载,但对于大多数 Web 体验来说,这可能不是大问题。不过,为了减少绘制调用次数,请务必将纹理合并到尽可能少的 Spritesheet 中,因为这对移动设备上的性能有很大影响。

(原始尺寸 512x512 像素)
简化材质和光线
材料的选择也会极大地影响性能,因此必须在移动设备上妥善管理。我们在优化性能时,就采用了在 three.js 中使用 MeshLambertMaterial
(每个顶点光照计算)而非 MeshPhongMaterial
(每个像素光照计算)的方法。基本上,我们尝试使用尽可能简单的着色器,并尽可能减少光照计算。
如需了解您使用的材质对场景的性能有何影响,您可以使用 MeshBasicMaterial
替换场景的材质。这样您就可以进行比较了。
scene.overrideMaterial = new THREE.MeshBasicMaterial({color:0x333333, wireframe:true});
优化 JavaScript 性能
在构建移动游戏时,GPU 并不总是最大的障碍。在 CPU 上花费的时间很多,尤其是在物理和骨骼动画方面。有时,一个有用的技巧是(具体取决于模拟),只在每隔一帧时运行这些耗时的计算。在对象池、垃圾回收和对象创建方面,您还可以使用可用的 JavaScript 优化技术。
在循环中更新预分配的对象(而不是创建新对象)是一项重要的步骤,可避免游戏期间出现垃圾回收“故障”。
例如,请考虑以下代码:
var currentPos = new THREE.Vector3();
function gameLoop() {
currentPos = new THREE.Vector3(0+offsetX,100,0);
}
此循环的改进版本可避免创建必须进行垃圾回收的新对象:
var originPos = new THREE.Vector3(0,100,0);
var currentPos = new THREE.Vector3();
function gameLoop() {
currentPos.copy(originPos).x += offsetX;
//or
currentPos.set(originPos.x+offsetX,originPos.y,originPos.z);
}
事件处理脚本应尽可能仅更新属性,并让 requestAnimationFrame
渲染循环处理更新阶段。
另一个提示是优化和/或预计算光线投射操作。例如,如果您需要在静态路径移动期间将对象附加到网格,则可以“记录”一个循环中的各个位置,然后从这些数据中读取,而不是对网格进行光线投射。或者,就像我们在 Rivendell 体验中所做的那样,使用光线投射来查找鼠标与更简单的低多边形不可见网格的互动。在高多边形网格上搜索碰撞非常缓慢,通常应避免在游戏循环中执行此操作。
以半尺寸渲染 WebGL 画布,并使用 CSS 放大
WebGL 画布的大小可能是您可以调整以优化性能的最有效参数。用于绘制 3D 场景的画布越大,则每个帧上必须绘制的像素就越多。这当然会影响性能。Nexus 10 采用高密度 2560x1600 像素显示屏,因此必须推送的像素数量是低密度平板电脑的 4 倍。为了针对移动设备进行优化,我们使用了一个技巧:将画布设置为原来的一半大小(50%),然后使用硬件加速 CSS 3D 转换将其放大到预期大小(100%)。缺点是图片会出现像素化,细线可能会成为问题,但在高分辨率屏幕上,效果不会太糟糕。额外的性能绝对值得。

将对象用作构建块
为了能够制作 Dol Guldur 城堡的大迷宫和无尽的瑞文戴尔山谷,我们制作了一组可重复使用的构建块 3D 模型。通过重复使用对象,我们可以确保在体验开始时(而不是过程中)实例化和上传对象。

在 Rivendell 中,我们有许多地面部分,随着用户体验历程的推进,我们会不断调整这些部分在 Z 深度中的重新定位。当用户经过这些部分时,这些部分会重新定位到远处。
对于 Dol Guldur 城堡,我们希望在每次游戏时重新生成迷宫。为此,我们创建了一个用于重新生成迷宫的脚本。
如果从一开始就将整个结构合并到一个大型网格中,会导致场景非常大且性能较差。为解决此问题,我们决定根据这些构建块是否在视野范围内来隐藏和显示它们。从一开始,我们就考虑过使用 2D 光线投射脚本,但最终还是使用了内置的 three.js 视锥剔除。我们重复使用了光线投射脚本,以放大玩家面临的“危险”。
接下来要处理的重大问题是用户互动。在桌面设备上,您可以使用鼠标和键盘输入;在移动设备上,用户可以通过轻触、滑动、张合手指、设备屏幕方向等方式进行互动。
在移动网站体验中使用触控互动
添加触控支持并不难。您可以阅读相关的优质文章,了解该主题。不过,有些小事可能会使事情变得更复杂。
您可以同时使用触控和鼠标。Chromebook Pixel 和其他支持触控的笔记本电脑同时支持鼠标和触控。一个常见的错误是,检查设备是否支持触摸,然后仅添加触摸事件监听器,而不添加鼠标事件监听器。
请勿在事件监听器中更新渲染。请改为将触摸事件保存到变量,并在 requestAnimationFrame 渲染循环中对其做出响应。这有助于提高性能,还可以合并冲突的事件。请务必在事件监听器中重复使用对象,而不是创建新对象。
请注意,这是多点触控:event.touches 是所有触摸的数组。在某些情况下,您不妨改为查看 event.targetTouches 或 event.changedTouches,并仅对您感兴趣的轻触做出响应,这样会更有趣。为了将点按与滑动区分开,我们会先延迟一段时间,然后再检查触摸是否已移动(滑动)或是否保持不动(点按)。为了检测双指张合,我们会测量两个初始触摸点之间的距离以及该距离随时间的变化情况。
在 3D 世界中,您必须决定相机对鼠标操作和滑动操作的响应方式。添加摄像头移动的一种常用方法是跟随鼠标移动。您可以使用鼠标位置进行直接控制,也可以使用增量移动(位置变化)进行控制。您不一定希望移动设备上的行为与桌面浏览器上的行为相同。我们进行了大量测试,以确定每种版本的适用程度。
在处理较小的屏幕和触摸屏时,您会发现用户的手指和界面互动图形经常会遮挡您要显示的内容。在设计原生应用时,我们习惯于考虑这一点,但在设计 Web 体验时,以前从未考虑过。这对设计师和用户体验设计师来说是一项真正的挑战。
摘要
从该项目中获得的总体经验是,移动设备上的 WebGL 非常出色,尤其是在新型高端设备上。就性能而言,多边形数和纹理大小似乎主要影响下载和初始化时间,而材质、着色器和 WebGL 画布的大小是针对移动设备性能进行优化最重要的部分。不过,影响效果的因素是多方面的,因此您可以采取的所有优化措施都很重要。
以移动设备为目标平台还意味着,您必须习惯考虑触摸互动,而且不仅要考虑像素尺寸,还要考虑屏幕的实际尺寸。在某些情况下,我们不得不将 3D 相机移近一点,才能真正了解发生了什么。
实验已启动,这是一个非常棒的历程。希望您喜欢!
想试用一下吗?踏上自己的中土之旅。