Three.js 課程 – Camera 與 Controls
主題:TrackballControls
簡介
在 3D 網頁應用中,相機的操作方式直接影響使用者的沉浸感與操作便利性。除了最常見的 OrbitControls,TrackballControls 以其「類似桌面 3D 軟體」的自由旋轉、平移與縮放行為,成為許多交互式視覺化、模型檢視器的首選。
本單元將帶你從 概念、實作、常見陷阱 到 最佳實踐,一步步掌握 TrackballControls 的使用技巧。即使你是剛接觸 Three.js 的新手,也能在閱讀完本文後,快速在自己的專案中加入流暢且直覺的相機控制。
核心概念
1. TrackballControls 是什麼?
TrackballControls 是 Three.js 附屬的相機控制器之一,模仿真實「軌跡球」的操作方式:
| 操作方式 | 滑鼠或觸控手勢 | 效果 |
|---|---|---|
| 左鍵拖曳 | 旋轉相機(繞目標點) | 像在桌面上旋轉模型 |
| 中鍵/滾輪 | 縮放(遠近) | 放大/縮小視圖 |
| 右鍵拖曳 | 平移相機(平行移動) | 移動視角位置 |
重點:
TrackballControls會自動維持相機的 目標點(target),讓旋轉、平移、縮放都以同一個焦點為基礎。
2. 為何選擇 TrackballControls?
- 自由度高:使用者可以同時在三個維度自由旋轉,感受更自然的 3D 操作。
- 參數可調:旋轉速度、縮放速度、平移速度皆可自訂,適合不同設備(桌機、平板、手機)。
- 支援慣性:可啟用「慣性」效果,讓拖曳結束後相機仍有微弱的持續移動,提升手感。
3. 基本使用流程
- 匯入模組(ES6)或使用全域變數(舊版)。
- 建立相機(PerspectiveCamera 為最常見)。
- 初始化 TrackballControls,傳入相機與渲染器的 DOM 元素。
- 在動畫迴圈中呼叫
controls.update(),讓控制器根據使用者輸入更新相機。
下面的程式碼展示了最簡單的設定:
import * as THREE from 'three';
import { TrackballControls } from 'three/examples/jsm/controls/TrackballControls.js';
// 1. 建立場景、相機、渲染器
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
60, // fov
window.innerWidth / window.innerHeight,
0.1, 1000
);
camera.position.set(0, 0, 5);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 2. 初始化 TrackballControls
const controls = new TrackballControls(camera, renderer.domElement);
controls.rotateSpeed = 2.0; // 旋轉速度
controls.zoomSpeed = 1.2; // 縮放速度
controls.panSpeed = 0.8; // 平移速度
controls.dynamicDampingFactor = 0.15; // 慣性阻尼
// 3. 動畫迴圈
function animate() {
requestAnimationFrame(animate);
controls.update(); // 必須呼叫
renderer.render(scene, camera);
}
animate();
程式碼範例
以下提供 5 個實用範例,示範在不同情境下如何客製化 TrackballControls。
範例 1:限制縮放範圍
有時候模型過大或過小會影響觀感,透過 minDistance 與 maxDistance 可以限制相機與目標點的距離。
controls.minDistance = 2; // 最近只能到 2 單位
controls.maxDistance = 20; // 最遠只能到 20 單位
範例 2:限制旋轉角度(僅允許水平旋轉)
在某些 2.5D 應用中,只想讓使用者左右旋轉,而不允許上下俯仰。可以透過 noRotate 結合自訂事件來達成:
// 先關閉所有旋轉
controls.noRotate = true;
// 只在水平拖曳時手動更新 target
renderer.domElement.addEventListener('mousemove', (event) => {
if (event.buttons === 1) { // 左鍵拖曳
const deltaX = event.movementX * 0.005;
const offset = new THREE.Vector3();
offset.copy(camera.position).sub(controls.target);
const theta = Math.atan2(offset.x, offset.z);
const radius = Math.sqrt(offset.x**2 + offset.z**2);
const newTheta = theta - deltaX;
offset.x = radius * Math.sin(newTheta);
offset.z = radius * Math.cos(newTheta);
camera.position.copy(controls.target).add(offset);
camera.lookAt(controls.target);
}
});
範例 3:啟用慣性(Damping)並自訂阻尼係數
慣性讓相機在使用者釋放滑鼠後仍有「滑行」感,提升操作手感。
controls.enableDamping = true; // 開啟阻尼
controls.dampingFactor = 0.1; // 阻尼係數 (0~1,越小越平滑)
註:若
enableDamping為true,必須在動畫迴圈中持續呼叫controls.update(),否則不會產生效果。
範例 4:在手機上啟用觸控手勢
TrackballControls 原生支援觸控,但你可以自行調整手勢的靈敏度與方向。
// 手勢靈敏度
controls.touchZoomSpeed = 0.5; // 縮放手勢
controls.touchPanSpeed = 0.5; // 平移手勢
controls.touchRotateSpeed = 0.5; // 旋轉手勢
// 允許雙指縮放(預設已開啟)
controls.allowZoom = true;
範例 5:自訂「重置」按鈕與快捷鍵
使用者在操作過程中常需要回到預設視角,提供一個快捷鍵或 UI 按鈕會更友善。
// 設定預設相機位置與目標
const defaultPos = new THREE.Vector3(0, 0, 5);
const defaultTarget = new THREE.Vector3(0, 0, 0);
// 重置函式
function resetCamera() {
camera.position.copy(defaultPos);
controls.target.copy(defaultTarget);
controls.update(); // 立即套用變更
}
// 監聽鍵盤 (R 鍵)
window.addEventListener('keydown', (e) => {
if (e.key === 'r' || e.key === 'R') {
resetCamera();
}
});
// 也可以在 HTML 中加入按鈕
document.getElementById('resetBtn').addEventListener('click', resetCamera);
<button id="resetBtn">重置相機</button>
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
忘記在動畫迴圈呼叫 controls.update() |
造成滑鼠或觸控操作後相機不會變化。 | 確保 animate() 中每幀都執行 controls.update()。 |
相機與 Controls 的 target 不一致 |
若手動改變相機位置卻未同步 controls.target,旋轉會出現突兀跳動。 |
變更相機位置後,務必同時更新 controls.target 或使用 controls.reset()。 |
| 縮放速度過快或過慢 | 使用者在手機或高 DPI 螢幕上操作時,預設值可能不適合。 | 依裝置類型調整 zoomSpeed、touchZoomSpeed,或根據 devicePixelRatio 動態計算。 |
慣性阻尼與 noRotate/noZoom 同時啟用 |
會產生「卡住」的感覺,因為阻尼仍在嘗試更新相機。 | 若關閉某個功能,建議同時關閉對應的阻尼(enableDamping = false)。 |
| 未釋放事件監聽器 | 在單頁應用切換場景時,舊的控制器仍會接收事件,造成衝突。 | 在切換或銷毀場景時呼叫 controls.dispose(),並移除自訂的事件監聽器。 |
最佳實踐:
- 將控制器封裝成模組:便於在不同場景間重複使用與維護。
- 根據裝置自動調整參數:使用
navigator.maxTouchPoints判斷觸控設備,動態設定rotateSpeed、zoomSpeed。 - 限制相機的可視範圍:配合
minPolarAngle、maxPolarAngle(若使用OrbitControls)或自行實作限制,避免相機穿透模型。 - 使用
requestAnimationFrame的時間戳:在controls.update(delta)中傳入時間差,可讓阻尼效果更一致。
實際應用場景
| 場景 | 為何適合使用 TrackballControls |
|---|---|
| 產品 3D 展示(如家具、汽車) | 使用者需要自由旋轉檢視細節,且常需要快速縮放。 |
| 科學資料視覺化(分子結構、天文模型) | 高自由度的旋轉讓使用者能從任意角度觀察複雜結構。 |
| 教育平台的交互式教材 | 透過慣性與觸控支援,提升學生在平板或手機上的操作體驗。 |
| 建築模型檢視 | 平移、縮放與旋轉的結合,讓設計師能快速切換視角檢查細部。 |
| 遊戲內的自由相機(第一人稱/第三人稱鏡頭切換) | 雖然 TrackballControls 主要是 UI 控制,但在開發原型或工具時,可快速實作相機操作。 |
總結
TrackballControls 為 Three.js 中功能最完整、彈性最高的相機控制器之一。透過 旋轉、平移、縮放 的三大手勢,配合 慣性、阻尼 與 自訂參數,開發者能在短時間內為 3D 應用提供自然且直覺的相機操作體驗。
本文從概念說明、實作範例、常見陷阱到最佳實踐,都提供了 可直接套用 的程式碼與技巧。只要掌握以下要點:
- 初始化時正確傳入相機與渲染器的 DOM 元素。
- 在每一幀都呼叫
controls.update(),確保相機即時回應使用者輸入。 - 根據裝置與需求調整速度、阻尼與限制參數,避免操作過快或過慢。
- 適時釋放資源(
dispose())以防止記憶體洩漏。
把這些原則內化後,你就能在任何 Three.js 專案中,輕鬆打造出 流暢、專業 的 3D 互動體驗。祝你玩得開心,創作出更多令人驚豔的 Web 3D 作品!