本文 AI 產出,尚未審核

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

主題:載入模型與動畫


簡介

在 3D 網站或 WebGL 遊戲中,模型與動畫是最能提升使用者沉浸感的要素。單純的幾何圖形雖然可以快速示範 Three.js 的基礎操作,但要打造商業級或藝術感十足的作品,離不開外部 3D 模型(如 .glb/.gltf.fbx.obj)以及由藝術家製作好的動畫序列。

本篇文章將從 載入模型解析動畫控制播放 三個核心流程出發,結合實作範例,說明如何在 Three.js 中把外部模型與動畫完整呈現在瀏覽器上。文章適合已具備基礎 Three.js 知識(Scene、Camera、Renderer)的讀者,並提供從新手到中階開發者都能直接套用的技巧與最佳實踐。


核心概念

1. 為什麼選擇 glTF / GLB?

  • 跨平台、輕量:glTF 是被稱為「JPEG of 3D」的開放格式,支援 PBR(Physically Based Rendering)材質,檔案大小相對較小。
  • 原生支援動畫:GLTF/GLB 可直接內嵌骨骼動畫(skinning)或變形動畫(morph target),Three.js 的 GLTFLoader 能自動解析。
  • 瀏覽器友好:使用 ArrayBuffer 讀取,無需額外的網路請求或轉檔步驟。

小技巧:若模型檔案較大(> 5 MB),建議使用 Draco 壓縮Meshopt,Three.js 已提供相應的解碼器。


2. 載入模型的基本流程

  1. 建立 LoaderGLTFLoader(或 FBXLoaderOBJLoader
  2. 設定解碼器(可選):若使用 Draco 壓縮,需要先載入 DRACOLoader
  3. 呼叫 load 方法:提供模型路徑、成功回呼、進度回呼與錯誤回呼。
  4. 將模型加入 Scenescene.add( gltf.scene )

以下是一個最簡單的 GLTFLoader 範例:

import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';

// 1. 建立場景、相機、渲染器(略)

const loader = new GLTFLoader();

// 2. 載入模型
loader.load(
  'models/robot.glb',               // 模型路徑
  (gltf) => {
    // 成功回呼
    const model = gltf.scene;
    model.position.set(0, 0, 0);
    scene.add(model);
    console.log('模型載入完成!');
  },
  (xhr) => {
    // 進度回呼(可顯示 loading bar)
    console.log(`${(xhr.loaded / xhr.total) * 100}% loaded`);
  },
  (error) => {
    // 錯誤回呼
    console.error('載入失敗', error);
  }
);

重點gltf.scene 只是一個 Group,裡面可能還包含 animationscameraslights 等子物件,視需求自行取用。


3. 解析與播放動畫

GLTFLoader 會在 gltf.animations 陣列中返回 THREE.AnimationClip 物件。使用 THREE.AnimationMixer 可以控制播放、暫停、混合等操作。

3.1 建立 AnimationMixer

let mixer; // 全域變數,方便在 animate loop 中使用

loader.load('models/character.glb', (gltf) => {
  const model = gltf.scene;
  scene.add(model);

  // 1. 建立 Mixer,綁定到模型
  mixer = new THREE.AnimationMixer(model);

  // 2. 取得第一段動畫 Clip(可自行挑選)
  const clip = gltf.animations[0];

  // 3. 建立 Action,並立即播放
  const action = mixer.clipAction(clip);
  action.play();
});

3.2 在渲染循環中更新 Mixer

const clock = new THREE.Clock();

function animate() {
  requestAnimationFrame(animate);

  const delta = clock.getDelta(); // 每幀時間差(秒)
  if (mixer) mixer.update(delta); // 更新所有動畫

  renderer.render(scene, camera);
}
animate();

3.3 多動畫切換(走路、跑步、跳躍)

// 假設 gltf.animations 包含 walk、run、jump 三段
const actions = {};
gltf.animations.forEach((clip) => {
  actions[clip.name] = mixer.clipAction(clip);
});

// 預設播放走路動畫
actions['Walk'].play();

// 切換到跑步動畫
function switchToRun() {
  actions['Walk'].fadeOut(0.5);   // 淡出走路
  actions['Run'].reset().fadeIn(0.5).play(); // 淡入跑步
}

技巧:使用 fadeIn / fadeOut 可以實現平滑過渡,避免動畫突變。


4. 使用 Draco 壓縮的 GLB

若模型檔案已使用 Draco 壓縮,需要先載入 DRACOLoader 並設定給 GLTFLoader

import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';

const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('/libs/draco/'); // Draco 解碼器所在路徑

const gltfLoader = new GLTFLoader();
gltfLoader.setDRACOLoader(dracoLoader);

gltfLoader.load('models/compressed_house.glb', (gltf) => {
  scene.add(gltf.scene);
});

注意:如果使用 CDN,記得在 setDecoderPath 時指向正確的 .js.wasm 檔案,否則會出現「Failed to load Draco decoder」錯誤。


5. 變形動畫(Morph Targets)

除了骨骼動畫,glTF 也支援 Morph Targets(頂點變形),常用於臉部表情或形狀變化。Three.js 會自動將 morph 資料掛在 mesh.morphTargetInfluences 陣列中。

loader.load('models/face_expression.glb', (gltf) => {
  const mesh = gltf.scene.getObjectByName('Head'); // 取得目標 Mesh
  // 預設全部 Influence 為 0
  mesh.morphTargetInfluences[0] = 0; // 例如「笑容」
  mesh.morphTargetInfluences[1] = 0; // 例如「皺眉」

  // 動畫:在 2 秒內從無表情過渡到微笑
  const duration = 2; // 秒
  const start = performance.now();

  function morphAnimate(now) {
    const elapsed = (now - start) / 1000;
    const t = Math.min(elapsed / duration, 1);
    mesh.morphTargetInfluences[0] = t; // 線性插值

    if (t < 1) requestAnimationFrame(morphAnimate);
  }
  requestAnimationFrame(morphAnimate);
});

小提醒:若模型使用 MeshStandardMaterial,記得把 morphTargets: true 設為 true,否則變形不會生效。


常見陷阱與最佳實踐

陷阱 說明 解決方案 / 最佳實踐
模型過大 大於 5 MB 時載入時間過長,影響使用者體驗。 使用 DracoMeshopt 壓縮;搭配 LOD(Level of Detail) 方案。
動畫播放不同步 多個 AnimationMixer 同時更新,導致時間基準不同。 共用同一個 Mixer,或在 clock.getDelta() 前統一時間來源。
材質失效 GLTF 中的 PBR 材質在舊版 Three.js 會變成白色。 確保使用 r150+ 版本;若仍有問題,手動設定 material.metalnessroughness
Skeleton 失真 載入 FBX 時骨骼軸向不一致,導致動畫扭曲。 盡量使用 GLTF;若必須使用 FBX,先在 3D 軟體內 Apply Transform
記憶體泄漏 每次切換模型卻未移除舊的 Mixer、Geometry、Texture。 在切換前呼叫 dispose()geometry.dispose(); material.dispose(); texture.dispose();,並將 mixer 設為 null

其他最佳實踐

  1. 預載(Preload)資源:利用 LoadingManager 統一管理進度條,提升使用者感知。
  2. 分離模型與動畫:將模型檔與動畫檔分開(如 character.glb + walk.glb),可在同一模型上重用不同動畫。
  3. 使用 SkeletonHelper:開發階段可視化骨骼結構,快速定位權重問題。
  4. 保持相同的時間基準:在 animate 迴圈中只呼叫一次 clock.getDelta(),再分別傳給所有 Mixer。
  5. 優化渲染:對於靜態模型使用 mesh.frustumCulled = falseBake lighting,降低 GPU 負擔。

實際應用場景

場景 需求 實作要點
產品展示網站 需要載入高品質的產品模型,提供旋轉、拆解動畫。 使用 GLTFLoader 搭配 KTX2 壓縮貼圖;利用 AnimationMixer 播放拆解序列。
互動式教育平台 角色模型須根據使用者選擇切換走路、跑步、跳躍等動畫。 為每個動作建立 clipAction,使用 fadeIn/fadeOut 實現平滑切換;利用 UI 按鈕觸發 switchToX()
AR/VR 體驗 大量模型需即時載入,且動畫必須保持低延遲。 採用 WebXR 結合 GLTFLoader,搭配 LODDraco 壓縮;預載關鍵模型與動畫。
遊戲化網站 角色表情變化(Morph Targets)與動作混合。 使用 morphTargetInfluences 控制臉部表情,配合 AnimationMixerWeight blending 同時播放走路與說話。
數據可視化 需要將 3D 數據點映射成動畫粒子。 先載入簡易的 Points 模型,再透過 ShaderMaterial 動態改變位置與顏色;不需要骨骼動畫。

總結

載入模型與動畫是 Three.js 開發中最常見也最具挑戰性的環節。透過本篇文章,我們了解了:

  • 為何 glTF/GLB 是首選格式,並掌握 DRACOMeshopt 壓縮的使用方式。
  • GLTFLoaderAnimationMixerAnimationClip 的完整工作流程,從模型載入到動畫播放、混合與過渡。
  • Morph TargetsSkeleton 動畫的差異與實作技巧。
  • 常見的陷阱(模型過大、動畫同步、記憶體泄漏)以及對應的 最佳實踐
  • 多種 實務應用場景,幫助你在產品展示、教育平台、AR/VR 以及遊戲化網站中快速落地。

只要遵循「先壓縮、再載入、最後釋放」的流程,並善用 LoadingManagerAnimationMixer 的混合功能,你就能在瀏覽器裡呈現流暢且具表現力的 3D 互動體驗。祝你在 Three.js 的實戰專案中玩得開心,創造出令人驚豔的 3D 網站!