本文 AI 產出,尚未審核

Three.js 與 WebXR:VR 模式啟動完整教學


簡介

隨著 WebXR API 的成熟,瀏覽器即時即能提供沉浸式的 VR 體驗,開發者不需要再依賴原生 App 或額外的插件,只要在網頁上載入 Three.js 並啟動 WebXR,就能讓使用者戴上 VR 頭盔直接進入 3D 世界。

本單元聚焦於「VR 模式啟動」的核心流程:從檢測裝置支援、建立 XR Session、到把 Three.js 渲染循環切換到 VR。掌握這些步驟後,你可以快速原型化虛擬展覽、教育模擬或互動遊戲,讓使用者在 Web 上即時體驗沉浸感。


核心概念

1. WebXR 與 Three.js 的合作模型

  • WebXR:瀏覽器提供的底層 API,負責與 VR/AR 硬體溝通、取得頭盔姿態與控制器輸入。
  • Three.js:負責 3D 場景的建模、材質、光源與渲染。Three.js 內建 WebXRManager,將 WebXR Session 與渲染器綁定,讓開發者只需要呼叫幾個方法即可完成切換。

重點:Three.js 只是一個渲染層,所有 XR 相關的權限請求、Session 建立都必須由原生 WebXR 完成,再交給 Three.js 處理渲染。

2. 必備的 HTML 與安全性設定

  • 必須在 HTTPS 或 localhost 環境才能使用 WebXR。
  • 在 <canvas> 標籤上加入 xr 屬性,讓瀏覽器知道此畫布會被用於 XR。
<!DOCTYPE html>
<html lang="zh-TW">
<head>
  <meta charset="UTF-8">
  <title>Three.js VR Demo</title>
</head>
<body>
  <canvas id="xr-canvas" xr></canvas>
  <script src="https://cdn.jsdelivr.net/npm/three@0.164.0/build/three.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/three@0.164.0/examples/jsm/webxr/VRButton.js"></script>
  <script src="app.js"></script>
</body>
</html>

3. 建立 Renderer 並啟用 XR

// app.js
const canvas = document.getElementById('xr-canvas');

// 1. 建立 WebGLRenderer,指向剛才的 canvas
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);

// 2. 啟用 XR 功能
renderer.xr.enabled = true;

// 3. 加入 VRButton,讓使用者點擊進入 VR
document.body.appendChild(THREE.VRButton.createButton(renderer));

4. 建立基本的 Scene、Camera、Light

// 基本場景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x202020);

// 相機:在 XR 中會被 WebXR 自動取代為 XR 相機
const camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(0, 1.6, 3); // 人眼高度

// 簡單環境光 + 平行光
const ambient = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambient);
const directional = new THREE.DirectionalLight(0xffffff, 1);
directional.position.set(5, 10, 7);
scene.add(directional);

5. 加入可交互的 3D 物件(示範用)

// 立方體
const geometry = new THREE.BoxGeometry(0.5, 0.5, 0.5);
const material = new THREE.MeshStandardMaterial({ color: 0x156289, metalness: 0.6, roughness: 0.2 });
const cube = new THREE.Mesh(geometry, material);
cube.position.set(0, 1.6, -1);
scene.add(cube);

// 讓立方體持續旋轉
function rotateCube(delta) {
  cube.rotation.x += delta * 0.5;
  cube.rotation.y += delta * 0.3;
}

6. 設計渲染迴圈與 XR Session

// 使用 Three.js 提供的 setAnimationLoop,會自動在 XR Session 中以 90fps 執行
renderer.setAnimationLoop(function (timestamp, frame) {
  const delta = renderer.clock.getDelta();

  // 更新場景(旋轉方塊)
  rotateCube(delta);

  // 交給 XR 相機渲染
  renderer.render(scene, camera);
});

7. 完整範例(結合上述所有步驟)

// ---------- app.js ----------
import * as THREE from 'three';
import { VRButton } from 'three/examples/jsm/webxr/VRButton.js';

const canvas = document.getElementById('xr-canvas');
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.xr.enabled = true;
document.body.appendChild(VRButton.createButton(renderer));

// --- Scene & Camera ---
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x202020);
const camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(0, 1.6, 3);

// --- Light ---
scene.add(new THREE.AmbientLight(0xffffff, 0.5));
const dirLight = new THREE.DirectionalLight(0xffffff, 1);
dirLight.position.set(5, 10, 7);
scene.add(dirLight);

// --- Object ---
const box = new THREE.Mesh(
  new THREE.BoxGeometry(0.5, 0.5, 0.5),
  new THREE.MeshStandardMaterial({ color: 0x156289, metalness: 0.6, roughness: 0.2 })
);
box.position.set(0, 1.6, -1);
scene.add(box);

// --- Animation Loop ---
renderer.setAnimationLoop(() => {
  const delta = renderer.clock.getDelta();
  box.rotation.x += delta * 0.5;
  box.rotation.y += delta * 0.3;
  renderer.render(scene, camera);
});

// --- Resize handling ---
window.addEventListener('resize', () => {
  const width = window.innerWidth;
  const height = window.innerHeight;
  renderer.setSize(width, height);
  camera.aspect = width / height;
  camera.updateProjectionMatrix();
});

提示:上述程式碼使用 ES6 import,若你的專案仍採用舊版 <script>,只要把 import 相關語句改成全域 THREE 變數即可。


常見陷阱與最佳實踐

陷阱 可能的症狀 解決方式
未在 HTTPS 環境 navigator.xr 為 undefined,VR 按鈕無法顯示 確保本機使用 localhost,或部署於 HTTPS 網站
Canvas 未加 xr 屬性 VRButton 點擊後無法進入 XR Session 在 <canvas> 標籤上加上 xr,或在程式碼中使用 renderer.xr.setReferenceSpaceType('local')
渲染循環使用 requestAnimationFrame 在 VR 中只能以 60fps 渲染,畫面卡頓 改用 renderer.setAnimationLoop,Three.js 會自動切換到 90fps(或裝置支援的最高頻率)
相機位置未調整 使用者進入 VR 後感覺「漂浮」或「埋在地面」 以 1.6m(約 1.6 單位)作為預設眼高,視需求再微調
資源載入過慢 進入 XR 前卡住或直接失敗 使用 LoadingManager 預先載入模型、貼圖,或在進入 XR 前顯示 loading UI

最佳實踐

  1. 使用 local 參考空間:renderer.xr.setReferenceSpaceType('local') 能確保使用者在真實世界的起始點與虛擬世界對齊。
  2. 將 UI 交給 WebXR:如有「退出 VR」的需求,使用 renderer.xr.getSession().end(),而不是自行操作 DOM。
  3. 適度降低材質複雜度:VR 裝置的 GPU 較手機或桌面弱,盡量使用 單一貼圖、低多邊形模型,以保持 90fps。
  4. 加入控制器事件:renderer.xr.getController(0) 可取得第一支控制器,配合 selectstart、selectend 事件實作抓取或點擊。
  5. 使用 Clock 取得穩定的 delta 時間:renderer.clock.getDelta() 在 XR 中會自動考慮幀率差異,避免手動計算造成不一致。

實際應用場景

領域 典型案例 如何利用 VR 模式啟動
教育 虛擬實驗室、歷史遺跡導覽 使用 Three.js 建立教具模型,透過 WebXR 讓學生戴上 Oculus Quest 直接「走進」實驗室或古城
電商 3D 商品展示、虛擬試穿 在商品頁加入 VRButton,使用者可在 VR 中旋轉、放大商品,甚至模擬穿戴感受
建築設計 客戶預覽建築模型 把 BIM 匯出的 glTF 模型載入 Three.js,啟動 VR 後讓客戶「站在」設計空間中,快速感受比例與光感
娛樂 小型沉浸式遊戲、互動藝術展 以簡易的場景與控制器交互為核心,讓玩家在瀏覽器中即時玩 VR 遊戲,降低安裝門檻
醫療 手術模擬、復健練習 透過 Three.js 動態渲染人體解剖模型,結合控制器觸發手術工具,提供醫師或患者沉浸式練習環境

總結

Three.js 與 WebXR 的結合讓 Web 端的 VR 開發門檻大幅降低。只要完成以下四個步驟,就能在瀏覽器中啟動沉浸式 VR:

  1. 檢查環境(HTTPS + navigator.xr)
  2. 建立 WebGLRenderer 並開啟 xr.enabled
  3. 使用 VRButton 產生進入 VR 的 UI
  4. 以 renderer.setAnimationLoop 替代 requestAnimationFrame,讓渲染自動同步至 XR Session

掌握這套流程後,你可以把任何 Three.js 場景—從簡單的立方體到複雜的城市模型—快速搬到 VR 中。未來隨著硬體效能提升與瀏覽器支援度擴大,WebXR 將成為跨平台沉浸式體驗的標準入口,值得每位前端或 3D 開發者投入學習與實作。祝你開發愉快,讓更多人透過瀏覽器體驗虛擬世界的奇妙!