本文 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 與安全性設定

  • 必須在 HTTPSlocalhost 環境才能使用 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.xrundefined,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) 可取得第一支控制器,配合 selectstartselectend 事件實作抓取或點擊。
  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.jsWebXR 的結合讓 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 開發者投入學習與實作。祝你開發愉快,讓更多人透過瀏覽器體驗虛擬世界的奇妙!