本文 AI 產出,尚未審核

Three.js 實戰專案:3D 互動網站

主題:建立 3D 場景與互動流程


簡介

在現代網頁設計中,3D 互動 已成為提升使用者體驗與品牌辨識度的重要手段。透過 Three.js,開發者只需要少量程式碼,就能在瀏覽器裡呈現高效能、跨平台的立體畫面,無需安裝任何外掛。

本篇文章將帶領讀者從 零基礎 建立一個完整的 3D 場景,並實作常見的互動流程(如滑鼠點擊、拖曳、動畫播放)。內容涵蓋核心概念、實作範例、常見陷阱與最佳實踐,適合 初學者 以及 中階開發者 快速上手並套用於實務專案。


核心概念

1. 基本構件:Scene、Camera、Renderer

Three.js 的渲染流程可抽象為三大核心物件:Scene(場景容器)、Camera(觀察者)與 Renderer(渲染器)。只有把這三者正確組合,才能在畫布上看到 3D 內容。

// 建立場景
const scene = new THREE.Scene();

// 建立相機 (PerspectiveCamera)
const camera = new THREE.PerspectiveCamera(
  75,                         // 視野角度 (FOV)
  window.innerWidth / window.innerHeight, // 長寬比
  0.1,                        // 近剪裁面
  1000                        // 遠剪裁面
);
camera.position.set(0, 1.6, 3); // 將相機提升至人眼高度

// 建立渲染器,綁定至 <canvas>
const renderer = new THREE.WebGLRenderer({ antialias: true, canvas: document.querySelector('#webgl') });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);

小技巧:在開發階段可先加入 renderer.setClearColor(0x202020); 設定背景色,方便辨識座標系統。


2. 光源與材質

沒有光源,任何 Mesh 都會是全黑的。Three.js 提供多種光源(Ambient、Directional、Point、Spot、Hemisphere),根據場景需求挑選並調整強度、位置與顏色。

// 環境光:為整體提供基礎亮度
const ambient = new THREE.AmbientLight(0xffffff, 0.4);
scene.add(ambient);

// 平行光:模擬太陽光,投射陰影
const directional = new THREE.DirectionalLight(0xffffff, 0.8);
directional.position.set(5, 10, 7);
directional.castShadow = true;               // 啟用陰影
directional.shadow.mapSize.width = 1024;     // 陰影解析度
directional.shadow.mapSize.height = 1024;
scene.add(directional);

實務建議:若場景中有大量物件,僅保留 AmbientLight + DirectionalLight,可降低 GPU 負載。


3. 載入外部模型(GLTF / GLB)

在實務專案中,通常會使用 3D 建模軟體(Blender、Maya)匯出 GLTF/GLB 檔案,並於 Three.js 中動態載入。

// 建立 GLTFLoader
const loader = new THREE.GLTFLoader();

// 載入模型
loader.load(
  'models/room.glb',
  (gltf) => {
    const model = gltf.scene;
    model.traverse((node) => {
      if (node.isMesh) {
        node.castShadow = true;
        node.receiveShadow = true;
      }
    });
    scene.add(model);
  },
  (xhr) => {
    console.log(`模型載入進度: ${(xhr.loaded / xhr.total) * 100}%`);
  },
  (error) => {
    console.error('模型載入失敗', error);
  }
);

重點:載入完成後,務必將 meshcastShadowreceiveShadow 設為 true,否則陰影不會顯示。


4. 使用 OrbitControls 實作相機操作

為了讓使用者能自由旋轉、縮放視角,我們常加入 OrbitControls(官方附屬套件)。

// 引入 OrbitControls
const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;        // 平滑慣性
controls.dampingFactor = 0.05;
controls.minDistance = 2;            // 最近距離
controls.maxDistance = 10;           // 最遠距離
controls.target.set(0, 1, 0);         // 目標焦點
controls.update();

小提醒controls.update() 必須在每一次的渲染迴圈中呼叫,否則慣性效果不會生效。


5. 互動:Raycaster 取得點擊資訊

Raycaster 是 Three.js 處理滑鼠或觸控點擊的核心工具,能將螢幕座標轉換為 3D 世界的射線,進而偵測哪些物件被點擊。

const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();

function onPointerDown(event) {
  // 正規化螢幕座標 (-1 ~ +1)
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

  // 設定射線
  raycaster.setFromCamera(mouse, camera);

  // 取得與射線相交的物件
  const intersects = raycaster.intersectObjects(scene.children, true);
  if (intersects.length > 0) {
    const hit = intersects[0].object;
    console.log('點擊物件:', hit.name);
    // 範例:改變顏色
    hit.material = hit.material.clone();
    hit.material.color.setHex(Math.random() * 0xffffff);
  }
}
window.addEventListener('pointerdown', onPointerDown);

技巧:若只想偵測特定類別的物件,可先將它們放入 clickableObjects 陣列,再傳給 raycaster.intersectObjects(clickableObjects, true),可大幅提升效能。


6. 動畫循環與時間控制

Three.js 的渲染循環通常使用 requestAnimationFrame,並結合 Clock 取得每幀的時間差,以實作平滑動畫(例如模型旋轉、移動)。

const clock = new THREE.Clock();

function animate() {
  requestAnimationFrame(animate);

  const delta = clock.getDelta(); // 兩幀之間的秒數

  // 示例:讓模型緩緩自轉
  if (scene.getObjectByName('RotatingBox')) {
    const box = scene.getObjectByName('RotatingBox');
    box.rotation.y += delta * 0.5; // 每秒 0.5 rad
  }

  controls.update();   // 更新 OrbitControls
  renderer.render(scene, camera);
}
animate();

常見陷阱與最佳實踐

陷阱 說明 解決方案
模型材質消失 匯入 GLTF 時未設定 renderer.shadowMap.enabled = true 或光源未開啟陰影。 確認 renderer.shadowMap.enabled = true,並為每顆光源設定 castShadow
畫面卡頓 場景內物件過多、陰影解析度過高、未使用 requestAnimationFrame 使用 Level of Detail (LOD)、降低 shadow.mapSize、適度使用 InstancedMesh
Raycaster 效能低 每幀遍歷全部物件。 只在需要時(點擊/觸控)呼叫 raycaster.intersectObjects,並限制搜尋範圍。
視窗尺寸改變未同步 窗口縮放後相機與渲染器尺寸未更新,導致畫面變形。 監聽 resize 事件,重新設定 camera.aspectcamera.updateProjectionMatrix()renderer.setSize()
記憶體洩漏 動態載入模型但未釋放資源。 使用 dispose() 釋放幾何體、材質與紋理,或在切換場景時呼叫 renderer.dispose()

最佳實踐

  1. 模組化:將場景、相機、控制器、載入器等功能分離成獨立檔案,提升可維護性。
  2. 使用 ES6+:利用 import/exportasync/await 讓程式碼更易讀。
  3. 開發環境:搭配 ViteWebpack,自動熱重載與資源壓縮。
  4. 資源最佳化:GLTF 模型使用 Draco 壓縮,圖片使用 WebPBasis
  5. 測試相容性:在行動裝置上測試觸控操作,必要時加入 TouchControls 或自訂手勢。

實際應用場景

領域 典型案例 Three.js 解決方案
產品展示 電子產品 3D 旋轉、拆解說明 使用 GLTF 載入高細節模型、Raycaster 點擊部件顯示說明文字。
教育訓練 虛擬實驗室、解剖模型 結合 dat.GUI 調整參數、動畫播放教學步驟。
建築視覺化 房屋戶型漫遊、光照模擬 透過 HDRI 環境光、光源控制即時調整日夜景。
互動藝術 網頁裝置互動裝置、音樂視覺化 結合 Web Audio API、ShaderMaterial 動態產生粒子特效。
行銷活動 3D 互動遊戲、抽獎機制 加入 GSAP 動畫、狀態機管理互動流程,提升使用者黏著度。

總結

本篇從 Three.js 的核心概念出發,示範了建立 3D 場景、配置光源、載入模型、加入相機控制與互動的完整流程。透過 RaycasterOrbitControlsClock 等工具,我們可以在瀏覽器內即時呈現流暢且具交互性的 3D 體驗。

同時,我們也列舉了開發過程中常見的陷阱與最佳實踐,讓讀者在實作時能避免效能與穩定性問題。最後,透過表格說明了多種實務應用場景,期望讀者能將所學套用到 產品展示、教育訓練、建築視覺化 等領域,打造出令人印象深刻的 3D 互動網站。

加油!只要掌握了「場景 + 相機 + 渲染器」的三大要素,再加上適當的光源、模型與互動控制,你就能在短時間內完成一個完整的 3D 互動專案。祝開發順利,期待看到你的作品在網路上閃耀!