本文 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 |
最佳實踐
- 使用
local參考空間:renderer.xr.setReferenceSpaceType('local')能確保使用者在真實世界的起始點與虛擬世界對齊。 - 將 UI 交給 WebXR:如有「退出 VR」的需求,使用
renderer.xr.getSession().end(),而不是自行操作 DOM。 - 適度降低材質複雜度:VR 裝置的 GPU 較手機或桌面弱,盡量使用 單一貼圖、低多邊形模型,以保持 90fps。
- 加入控制器事件:
renderer.xr.getController(0)可取得第一支控制器,配合selectstart、selectend事件實作抓取或點擊。 - 使用
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:
- 檢查環境(HTTPS +
navigator.xr) - 建立
WebGLRenderer並開啟xr.enabled - 使用
VRButton產生進入 VR 的 UI - 以
renderer.setAnimationLoop替代requestAnimationFrame,讓渲染自動同步至 XR Session
掌握這套流程後,你可以把任何 Three.js 場景—從簡單的立方體到複雜的城市模型—快速搬到 VR 中。未來隨著硬體效能提升與瀏覽器支援度擴大,WebXR 將成為跨平台沉浸式體驗的標準入口,值得每位前端或 3D 開發者投入學習與實作。祝你開發愉快,讓更多人透過瀏覽器體驗虛擬世界的奇妙!