霍比特人体验

使用移动 WebGL 让中土世界焕发生机

Daniel Isaksson
Daniel Isaksson

过去,为移动设备和平板电脑引入基于 Web、包含大量多媒体的互动体验一直是一个挑战。主要限制因素包括性能、API 可用性、设备上的 HTML5 音频的限制以及缺乏流畅的内嵌视频播放功能。

今年早些时候,我们与 Google 和华纳兄弟的朋友们合作启动了一个项目,为新上映的《霍比特人》电影《霍比特人:史矛革荒野》打造移动优先的网络体验。构建包含大量多媒体内容的移动版 Chrome 实验是一项非常鼓舞人心且具有挑战性的任务。

我们已为新 Nexus 设备上的 Chrome(Android 版)优化了使用体验。我们现已支持在这类设备上访问 WebGL 和网络音频。不过,得益于硬件加速的合成和 CSS 动画,在非 WebGL 设备和浏览器上也能实现很大一部分体验。

整个体验都基于一张中土世界地图以及霍比特人电影中的地点和角色。通过使用 WebGL,我们得以将《霍比特人》三部曲的丰富世界呈现和探索,让用户掌控体验。

在移动设备上面临的 WebGL 挑战

首先,“移动设备”一词非常宽泛。不同设备的规格有很大的差异。因此,作为开发者,您需要决定是希望支持更多但体验不太复杂的设备,还是像我们在本例中所做的那样,将支持的设备限制为能够显示更真实的 3D 世界的设备。“中土世界之旅”的重点是 Nexus 设备和五款广受欢迎的 Android 智能手机。

在这项实验中,我们使用了 three.js,就像之前针对一些 WebGL 项目所做的那样。我们首先构建可以在 Nexus 10 平板电脑上正常运行的 Trollshaw 游戏的初始版本。在设备上进行一些初始测试后,我们考虑了一系列优化措施,这些优化措施与我们通常在低规格笔记本电脑上采用的优化方式非常相似:

  • 使用低多边形模型
  • 使用低分辨率纹理
  • 通过合并几何图形尽可能减少绘制调用的次数
  • 简化材质和照明
  • 移除后期特效并关闭抗锯齿功能
  • 优化 JavaScript 性能
  • 以一半尺寸渲染 WebGL 画布并使用 CSS 进行纵向放大

将这些优化应用到我们的第一个粗略版游戏后,我们获得了 30 FPS 的稳定帧速率,对此我们感到非常满意。那时,我们的目标是改善视觉效果,同时又不会对帧速率产生负面影响。我们尝试了很多技巧:有些技巧确实对广告效果产生了积极影响,有些技巧没有达到我们的预期。

使用低多边形模型

我们先从模型开始。使用低多边形模型无疑有助于缩短下载时间,并缩短初始化场景所需的时间。我们发现,我们可以大幅增加复杂性,而不会影响性能。我们在此游戏中使用的巨魔模型大约有 5,000 个面孔,场景大约有 4 万张面孔,效果很好。

巨魔树林中的巨魔之一
巨魔树林的巨魔之一

对于该体验中的另一个(尚未发布)位置,我们发现减少多边形对性能有更大影响。在此示例中,我们在移动设备上加载的对象比为桌面设备加载的对象要低。创建不同的 3D 模型集需要一些额外的工作,但不一定非必不可少。这实际上取决于模型的起始复杂程度。

在处理包含大量对象的大型场景时,我们试图巧妙地划分几何图形。这使我们能够快速开启和关闭不太重要的网格,找到适用于所有移动设备的设置。然后,我们可以选择在运行时使用 JavaScript 合并几何图形以进行动态优化,或者在测试环境中合并几何图形以保存请求。

使用低分辨率纹理

为了缩短移动设备上的加载时间,我们选择在桌面设备上加载大小为纹理大小一半的不同纹理。事实证明,所有设备都可以处理高达 2048x2048 像素的纹理尺寸,并且大多数设备都可以处理 4096x4096 像素。将单个纹理上传到 GPU 后,对其进行纹理查找似乎不成问题。纹理的总大小必须适合 GPU 内存,以避免纹理不断地下载和下载,但对于大多数网络体验来说,这并不是一个大问题。但是,将纹理组合到尽可能少的 Sprite 工作表中对于减少绘制调用次数非常重要,这对在移动设备上的性能有很大影响。

巨魔树林中巨魔的纹理
巨魔树林巨魔的纹理
(原始尺寸 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 场景的画布越大,每帧上需要绘制的像素就越多。这当然会影响性能。与低密度平板电脑相比,配备高密度 2560x1600 像素显示屏的 Nexus 10 的像素数必须提升 4 倍。为了针对移动设备对此进行优化,我们采用了一种技巧:将画布的大小设为一半 (50%),然后通过硬件加速的 CSS 3D 转换将画布放大至预期尺寸 (100%)。这种图片的缺点是像素化图片,细线可能会带来问题,但在高分辨率屏幕上,效果还不错。这绝对物有所值。

在 Nexus 10 上,相同场景没有画布缩放 (16FPS) 和 50% (33FPS)
在 Nexus 10 (16FPS) 和 50% (33FPS) 上,未采用画布缩放的相同场景。

使用对象作为组成要素

为了能够制作出多尔格尔德城堡和永无止境的瑞文戴尔山谷的大型迷宫,我们制作了一套可以重复使用的积木 3D 模型。通过重复使用对象,我们可以确保对象在体验开始时(而不是中间过程)进行实例化和上传。

多尔哥尔德迷宫中使用的 3D 对象构建块。
多尔格尔德迷宫中使用的 3D 对象构建块。

在 Rivendell 中,我们拥有许多地面航段,并且随着用户行程的推进,我们会不断调整这些地面的 Z 轴深度。当用户经过各个部分时,这些部分将被重新放置在远处。

对于多尔格尔德城堡,我们希望在每次游戏中重新生成迷宫。为此,我们创建了一个可重新生成迷宫的脚本。

将整个结构从一开始就合并到一个大网格中,会导致场景非常庞大,但性能不佳。为了解决这个问题,我们决定根据这些组成要素是否处于可见状态来隐藏和显示它们。我们一开始就想到使用 2D 光线投射脚本,但最后我们使用了内置的 three.js 视锥体剔除。我们重复使用了 raycaster 脚本来放大玩家面临的“危险”。

接下来要处理的事项是用户互动。在桌面设备上,您可以使用鼠标和键盘输入;在移动设备上,用户会通过触摸、滑动、双指张合、设备屏幕方向等进行互动。

在移动网络体验中使用触摸互动

增加触控支持并不难。您可以参阅有关该主题的精彩文章。但有些小细节可能会让事情变得更加复杂。

您可以同时使用触控和鼠标。Chromebook Pixel 和其他支持触控的笔记本电脑均支持鼠标和触控。一种常见的错误是检查设备是否已启用触摸,然后只添加触摸事件监听器,不为鼠标添加任何监听器。

不更新事件监听器中的呈现功能。请改为将触摸事件保存到变量中,并在 requestAnimationFrame 渲染循环中对这些事件做出响应。这有助于提高性能,还可合并有冲突的事件。请确保重复使用对象,而不是在事件监听器中创建新对象。

请记住,它是多点触控:event.touches 是所有触摸的数组。在某些情况下,更有趣的是查看 event.targetTouches 或 event.changedTouches,然后只响应您感兴趣的触摸。为了将点按与滑动区分开来,在检查触摸是移动(滑动)还是静止(点按)之前,我们会使用延迟。为了进一步改善触摸效果,我们测量了两次初次接触之间的距离,以及这一距离随时间的变化情况。

在 3D 环境中,您必须决定相机对鼠标和滑动操作的响应方式。添加镜头移动操作的一种常见方法是跟随鼠标的移动操作。这可以通过使用鼠标位置直接控制或增量移动(位置更改)来实现。在移动设备上的行为不一定与桌面浏览器相同。我们进行了广泛的测试,以便确定每个版本的合适程度。

在处理较小的屏幕和触摸屏时,您会发现用户的手指和界面互动图形通常会按照您希望的方式显示。这是我们在设计原生应用时习惯的习惯,但之前在设计 Web 体验时并没有真正去考虑这一点。这对设计师和用户体验设计师来说是一个很大的挑战。

摘要

从这个项目中,我们的总体体验是:移动设备上的 WebGL 运行非常好,尤其是在较新的高端设备上。在性能方面,多边形数量和纹理大小似乎主要影响下载和初始化时间,而且材质、着色器和 WebGL 画布的大小是针对移动设备性能进行优化的最重要的部分。不过,数据是影响效果的各个部分的总和,因此您可以采取一切措施来优化计数。

定位到移动设备还意味着,您必须习惯于考虑触摸互动,而且这不仅仅是像素尺寸,更在于屏幕的物理尺寸。在有些情况下,我们必须将 3D 摄像头移近一点,才能真正看到发生的情况。

实验已启动,这是一段精彩的旅程。希望您会喜欢!

想试试吗?踏上您自己的中土世界之旅