本文 AI 產出,尚未審核

Three.js 課程 – Camera 與 Controls

主題:TrackballControls


簡介

在 3D 網頁應用中,相機的操作方式直接影響使用者的沉浸感與操作便利性。除了最常見的 OrbitControlsTrackballControls 以其「類似桌面 3D 軟體」的自由旋轉、平移與縮放行為,成為許多交互式視覺化、模型檢視器的首選。

本單元將帶你從 概念實作常見陷阱最佳實踐,一步步掌握 TrackballControls 的使用技巧。即使你是剛接觸 Three.js 的新手,也能在閱讀完本文後,快速在自己的專案中加入流暢且直覺的相機控制。


核心概念

1. TrackballControls 是什麼?

TrackballControls 是 Three.js 附屬的相機控制器之一,模仿真實「軌跡球」的操作方式:

操作方式 滑鼠或觸控手勢 效果
左鍵拖曳 旋轉相機(繞目標點) 像在桌面上旋轉模型
中鍵/滾輪 縮放(遠近) 放大/縮小視圖
右鍵拖曳 平移相機(平行移動) 移動視角位置

重點TrackballControls 會自動維持相機的 目標點(target),讓旋轉、平移、縮放都以同一個焦點為基礎。

2. 為何選擇 TrackballControls?

  • 自由度高:使用者可以同時在三個維度自由旋轉,感受更自然的 3D 操作。
  • 參數可調:旋轉速度、縮放速度、平移速度皆可自訂,適合不同設備(桌機、平板、手機)。
  • 支援慣性:可啟用「慣性」效果,讓拖曳結束後相機仍有微弱的持續移動,提升手感。

3. 基本使用流程

  1. 匯入模組(ES6)或使用全域變數(舊版)。
  2. 建立相機(PerspectiveCamera 為最常見)。
  3. 初始化 TrackballControls,傳入相機與渲染器的 DOM 元素。
  4. 在動畫迴圈中呼叫 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:限制縮放範圍

有時候模型過大或過小會影響觀感,透過 minDistancemaxDistance 可以限制相機與目標點的距離。

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,越小越平滑)

:若 enableDampingtrue,必須在動畫迴圈中持續呼叫 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 螢幕上操作時,預設值可能不適合。 依裝置類型調整 zoomSpeedtouchZoomSpeed,或根據 devicePixelRatio 動態計算。
慣性阻尼與 noRotate/noZoom 同時啟用 會產生「卡住」的感覺,因為阻尼仍在嘗試更新相機。 若關閉某個功能,建議同時關閉對應的阻尼(enableDamping = false)。
未釋放事件監聽器 在單頁應用切換場景時,舊的控制器仍會接收事件,造成衝突。 在切換或銷毀場景時呼叫 controls.dispose(),並移除自訂的事件監聽器。

最佳實踐

  1. 將控制器封裝成模組:便於在不同場景間重複使用與維護。
  2. 根據裝置自動調整參數:使用 navigator.maxTouchPoints 判斷觸控設備,動態設定 rotateSpeedzoomSpeed
  3. 限制相機的可視範圍:配合 minPolarAnglemaxPolarAngle(若使用 OrbitControls)或自行實作限制,避免相機穿透模型。
  4. 使用 requestAnimationFrame 的時間戳:在 controls.update(delta) 中傳入時間差,可讓阻尼效果更一致。

實際應用場景

場景 為何適合使用 TrackballControls
產品 3D 展示(如家具、汽車) 使用者需要自由旋轉檢視細節,且常需要快速縮放。
科學資料視覺化(分子結構、天文模型) 高自由度的旋轉讓使用者能從任意角度觀察複雜結構。
教育平台的交互式教材 透過慣性與觸控支援,提升學生在平板或手機上的操作體驗。
建築模型檢視 平移、縮放與旋轉的結合,讓設計師能快速切換視角檢查細部。
遊戲內的自由相機(第一人稱/第三人稱鏡頭切換) 雖然 TrackballControls 主要是 UI 控制,但在開發原型或工具時,可快速實作相機操作。

總結

TrackballControls 為 Three.js 中功能最完整、彈性最高的相機控制器之一。透過 旋轉、平移、縮放 的三大手勢,配合 慣性、阻尼自訂參數,開發者能在短時間內為 3D 應用提供自然且直覺的相機操作體驗。

本文從概念說明、實作範例、常見陷阱到最佳實踐,都提供了 可直接套用 的程式碼與技巧。只要掌握以下要點:

  1. 初始化時正確傳入相機與渲染器的 DOM 元素
  2. 在每一幀都呼叫 controls.update(),確保相機即時回應使用者輸入。
  3. 根據裝置與需求調整速度、阻尼與限制參數,避免操作過快或過慢。
  4. 適時釋放資源dispose())以防止記憶體洩漏。

把這些原則內化後,你就能在任何 Three.js 專案中,輕鬆打造出 流暢、專業 的 3D 互動體驗。祝你玩得開心,創作出更多令人驚豔的 Web 3D 作品!