哈比人體驗

運用行動 WebGL 重現中土世界

Daniel Isaksson
Daniel Isaksson

以往,要將互動式、以網頁為基礎的多媒體體驗帶入行動裝置和平板電腦,一直是項挑戰。主要限制包括效能、API 可用性、裝置上的 HTML5 音訊限制,以及缺乏無縫內嵌影片播放功能。

今年稍早,我們與 Google 和華納兄弟的夥伴合作,為新版《魔戒世界:霍比特人》The Hobbit: The Desolation of Smaug 建立行動版內容優先的網路體驗。建立多媒體重重的行動版 Chrome 實驗,是一項非常有趣且具有挑戰性的任務。

我們現在已針對新 Nexus 裝置上的 Chrome 進行最佳化,讓使用者可以存取 WebGL 和 Web Audio。不過,由於硬體加速合成和 CSS 動畫,因此大部分體驗也能在非 WebGL 裝置和瀏覽器上使用。

整個體驗以中土世界地圖為基礎,並採用《霍比特人》電影中的地點和角色。我們使用 WebGL 技術,將《魔戒旅程》三部曲的豐富世界呈現在觀眾眼前,並讓使用者自行控制體驗。

行動裝置上的 WebGL 挑戰

首先,「行動裝置」一詞涵蓋的範圍很廣泛。裝置的規格各有不同。因此,開發人員必須決定是否要支援更多裝置,並提供較不複雜的體驗,或是像本例一樣,將支援的裝置限制為可顯示更逼真的 3D 世界。在「中土世界之旅」中,我們著重於 Nexus 裝置和五款熱門 Android 智慧型手機。

在實驗中,我們使用了 three.js,就像我們先前在某些 WebGL 專案中所做的那樣。我們首先建構 Trollshaw 遊戲的初始版本,以便在 Nexus 10 平板電腦上順利執行。在對裝置進行初步測試後,我們列出一些最佳化項目,這些項目與我們通常為低規筆記型電腦使用的最佳化項目類似:

  • 使用低多邊形模型
  • 使用低解析度的紋理
  • 合併幾何圖形,盡可能減少繪圖呼叫數量
  • 簡化材質和燈光
  • 移除後製效果並關閉反鋸齒
  • 最佳化 JavaScript 效能
  • 以一半大小算繪 WebGL 無框畫,並使用 CSS 放大

在將這些最佳化措施套用至第一個粗略版本的遊戲後,我們獲得了穩定的 30 張/秒影格速率,這讓我們非常滿意。當時我們的目標是改善視覺效果,但不影響影格速率。我們嘗試了許多方法:有些方法確實對效能產生影響,但有些方法的效果不如預期。

使用低多邊形模型

我們先從模型開始。使用低多邊形模型確實有助於縮短下載時間,以及初始化場景所需的時間。我們發現,在不影響效能太多的情況下,可以大幅提高複雜度。我們在這個遊戲中使用的惡搞模型約有 5,000 個面孔,而場景約有 40,000 個面孔,這兩者都運作良好。

Trollshaw 森林的其中一個巨魔
Trollshaw 森林的其中一個巨魔

在另一個 (尚未發布的) 體驗中,我們發現減少多邊形對效能影響更大。在這種情況下,我們為行動裝置載入的多邊形物件數量,會比為電腦載入的物件數量少。建立不同組的 3D 模型需要額外的工作,但不一定需要。這取決於模型的複雜程度。

在處理包含大量物件的大型場景時,我們會盡量以策略性的方式劃分幾何圖形。這讓我們可以快速開啟或關閉較不重要的網格,找出適用於所有行動裝置的設定。接著,我們可以選擇在 JavaScript 的執行階段合併幾何圖形,以便進行動態最佳化,或是在預先製作階段合併幾何圖形,以便節省請求。

使用低解析度的紋理

為了縮短行動裝置的載入時間,我們選擇載入不同紋理,大小為電腦版紋理的一半。結果發現,所有裝置都能處理紋理大小,最高可達 2048 x 2048 像素,大多數裝置則可處理 4096 x 4096 像素。個別紋理的紋理查詢上傳至 GPU 後,似乎不會發生問題。紋理的總大小必須符合 GPU 記憶體的容量,以免紋理不斷上傳及下載,但這對大多數網頁體驗來說可能不是大問題。不過,為了減少繪圖呼叫次數,請盡可能將紋理合併至少數精靈板中,這對行動裝置的效能影響很大。

Trollshaw 森林中其中一個巨魔的紋理
Trollshaw 森林的其中一個巨魔的紋理
(原始大小 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 採用高密度 2560 x 1600 像素螢幕,因此必須推送的像素數量是低密度平板電腦的 4 倍。為了針對行動裝置進行最佳化,我們使用了一個技巧,將畫布設為一半大小 (50%),然後透過硬體加速 CSS 3D 轉換,將其放大至預期大小 (100%)。缺點是,在細線可能會出現象素化問題的圖片中,這項效果就會變差,但在高解析度螢幕上,效果就不會那麼差。這絕對值得付出額外效能。

在 Nexus 10 上未調整畫布縮放比例的相同場景 (16FPS),以及縮放至 50% (33FPS)
在 Nexus 10 上,不縮放畫布 (16FPS) 和縮放至 50% (33FPS) 的相同場景。

物件做為建構模塊

為了建立 Dol Guldur 城堡的大型迷宮和 Rivendell 的無盡山谷,我們製作了一組可重複使用的 3D 模型。重複使用物件可確保物件在體驗開始時即能例項化及上傳,而非在體驗過程中。

在 Dol Guldur 迷宮中使用的 3D 物件建構模塊。
在 Dol Guldur 迷宮中使用的 3D 物件建構區塊。

在 Rivendell 中,我們有許多地面區段,會隨著使用者旅程的進展,持續在 Z 深度中重新調整位置。當使用者經過各個區段時,這些區段會重新定位至遠處。

我們希望 Dol Guldur 城堡的迷宮能為每場遊戲重新產生。為此,我們建立了可重建迷宮的指令碼。

從一開始就將整個結構合併為一個大型網格,會導致場景過大且效能不佳。為解決這個問題,我們決定根據建構模塊是否顯示在畫面上,決定是否隱藏或顯示這些建構模塊。一開始,我們就想使用 2D 光線投射器指令碼,但最後我們使用了 內建的 three.js 截斷視角裁剪功能。我們重複使用了光線投射器指令碼,放大玩家面臨的「危險」範圍。

接下來要處理的重大問題是使用者互動。在電腦上,您可以使用滑鼠和鍵盤輸入內容;在行動裝置上,使用者可以透過觸控、滑動、雙指撥動、裝置方向等方式進行互動。

在行動網頁體驗中使用觸控互動

新增觸控支援功能並不難。這個主題有很棒的文章可供參考。但有些小事會讓情況變得更複雜。

您可以同時使用觸控和滑鼠。Chromebook Pixel 和其他支援觸控功能的筆記型電腦都支援滑鼠和觸控功能。常見的錯誤是檢查裝置是否啟用觸控功能,然後只新增觸控事件監聽器,而沒有新增滑鼠事件監聽器。

請勿在事件監聽器中更新轉譯作業。請改為將觸控事件儲存至變數,並在 requestAnimationFrame 算繪迴圈中回應這些事件。這麼做不僅可提高效能,還能合併發生衝突的事件。請務必重複使用物件,而不是在事件監聽器中建立新物件。

請注意,這是多點觸控:event.touches 是所有觸控的陣列。在某些情況下,您可能會想改為查看 event.targetTouches 或 event.changedTouches,並只回應您感興趣的觸控動作。為了將輕觸動作與滑動動作區分開,我們會先延遲一段時間,再檢查觸控動作是否已移動 (滑動),或是仍在原處 (輕觸)。為了取得捏合動作,我們會測量兩次初始觸控之間的距離,以及該距離隨時間變化的情形。

在 3D 世界中,您必須決定相機如何回應滑鼠和滑動動作。加入相機移動效果的常見做法之一,就是追蹤滑鼠移動。您可以使用滑鼠位置或差異移動 (位置變更) 進行直接控制。行動裝置和電腦版瀏覽器的行為不一定相同。我們經過大量測試,決定每個版本的適當感覺。

在處理較小的螢幕和觸控螢幕時,您會發現使用者的手指和 UI 互動圖形經常會擋住您要顯示的內容。這項做法在設計原生應用程式時很常見,但在網頁體驗中則不常使用。這對設計人員和使用者體驗設計師來說,確實是一大挑戰。

摘要

根據這個專案的整體經驗,行動裝置上的 WebGL 運作良好,尤其是在較新的高階裝置上。就效能而言,多邊形數量和紋理大小似乎會對下載和初始化時間造成影響,而材質、著色器和 WebGL 畫布的大小,則是為了提升行動裝置效能而進行最佳化的重點。不過,這項指標是影響成效的部分總和,因此您採取的所有最佳化措施都會影響這項指標。

指定行動裝置也意味著您必須習慣思考觸控互動,而且這不僅是像素大小,也是螢幕的實際大小。在某些情況下,我們必須將 3D 相機移近,才能實際查看發生了什麼事。

實驗已啟動,這段旅程非常精彩。希望你會喜歡!

想試試看嗎?展開屬於自己的中土之旅