本文 AI 產出,尚未審核

Three.js – 載入外部模型

OBJLoader、FBXLoader 介紹


簡介

在 3D 網頁應用中,模型檔案的載入往往是最先要解決的問題。無論是簡單的產品展示、互動式遊戲,或是 AR/VR 體驗,都需要把設計師在 Blender、Maya、3ds Max 等軟體裡製作好的模型,帶入 Three.js 場景中呈現。

Three.js 官方提供了多種 Loader,其中最常用的兩個就是 OBJLoader(支援 Wavefront OBJ)與 FBXLoader(支援 Autodesk FBX)。這兩種格式各有優缺點:OBJ 結構簡潔、易於手動編輯;FBX 則能一次攜帶材質、動畫、骨架等完整資訊。掌握它們的使用方法,才能讓你的 3D 專案快速從「模型檔」變成「可互動的 Web 場景」。

本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,帶你一步步熟悉 OBJLoader 與 FBXLoader 的使用,適合 初學者 也能提供 中級開發者 在專案中優化模型載入流程的參考。


核心概念

1. 為什麼要使用 Loader?

Three.js 內部只能處理 幾何體 (Geometry)材質 (Material)貼圖 (Texture) 等原始資料。模型檔案則是把這些資訊以特定格式封裝起來,Loader 的任務就是將檔案解析成 Three.js 能識別的物件,並把它們加入場景。

  • 同步 vs 非同步:Loader 皆採用非同步方式(基於 Promise 或回呼),避免阻塞主執行緒,確保頁面渲染流暢。
  • 支援的資源:OBJ 只包含幾何與材質參考;FBX 能同時攜帶 動畫、骨架、貼圖、光照資訊。根據需求選擇合適的格式。

2. 安裝與匯入

Three.js 從 r125 起把所有官方 Loader 放到 examples/jsm/loaders,建議使用 ES6 模組 方式匯入:

// 基本 Three.js
import * as THREE from 'three';

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

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

若使用舊版 <script> 標籤,則改為載入 OBJLoader.jsFBXLoader.js,並從全域 THREE 物件取得對應類別。

3. OBJLoader 基本使用

OBJ 檔案通常會配合一個 .mtl(Material)檔案,兩者必須一起載入才能正確呈現材質。

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

// 1️⃣ 載入 MTL
const mtlLoader = new THREE.MTLLoader();
mtlLoader.setPath( '/models/obj/' );
mtlLoader.load( 'car.mtl', (mtl) => {
  // 2️⃣ 讓 OBJLoader 使用已解析的材質
  mtl.preload();
  const objLoader = new OBJLoader();
  objLoader.setMaterials( mtl );
  objLoader.setPath( '/models/obj/' );

  // 3️⃣ 載入 OBJ
  objLoader.load(
    'car.obj',
    (object) => {
      // 調整位置、縮放
      object.position.set(0, 0, 0);
      object.scale.set(0.5, 0.5, 0.5);
      scene.add(object);
    },
    // 讀取進度
    (xhr) => console.log( `${(xhr.loaded / xhr.total * 100).toFixed(2)}% loaded` ),
    // 錯誤處理
    (error) => console.error('OBJ load error:', error)
  );
});

小技巧:如果模型只有單一材質,直接在 OBJLoader 內使用 MeshStandardMaterial 取代 .mtl,可減少一次 HTTP 請求。

4. FBXLoader 基本使用

FBX 檔案的結構更複雜,但 Loader 已幫我們把 動畫、骨架、貼圖 都解析好,只要把回傳的 Object3D 加入場景即可。

const fbxLoader = new FBXLoader();
fbxLoader.setPath('/models/fbx/');
fbxLoader.load(
  'character.fbx',
  (fbx) => {
    // FBX 可能包含多個子物件,常見的做法是直接遍歷
    fbx.traverse( child => {
      if (child.isMesh) {
        // 讓材質支援光照
        child.castShadow = true;
        child.receiveShadow = true;
        child.material = new THREE.MeshStandardMaterial({
          map: child.material.map,
          normalMap: child.material.normalMap,
          metalness: 0.5,
          roughness: 0.4
        });
      }
    });

    // 位置與縮放
    fbx.position.set(0, 0, 0);
    fbx.scale.set(0.01, 0.01, 0.01);
    scene.add(fbx);

    // 若有動畫,使用 AnimationMixer 播放
    const mixer = new THREE.AnimationMixer(fbx);
    const action = mixer.clipAction(fbx.animations[0]);
    action.play();

    // 在 render loop 中更新 mixer
    function animate() {
      requestAnimationFrame(animate);
      const delta = clock.getDelta();
      mixer.update(delta);
      renderer.render(scene, camera);
    }
    animate();
  },
  (xhr) => console.log(`FBX ${(xhr.loaded / xhr.total * 100).toFixed(2)}% loaded`),
  (error) => console.error('FBX load error:', error)
);

5. 多模型同時載入(Promise 範例)

在實務專案中往往需要一次載入多個模型,使用 Promise.all 可以讓程式碼更簡潔且易於錯誤統一處理。

function loadOBJ(name, path) {
  const mtlLoader = new THREE.MTLLoader();
  const objLoader = new OBJLoader();

  return new Promise((resolve, reject) => {
    mtlLoader.setPath(path).load(`${name}.mtl`, (mtl) => {
      mtl.preload();
      objLoader.setMaterials(mtl);
      objLoader.setPath(path).load(
        `${name}.obj`,
        (obj) => resolve(obj),
        null,
        (err) => reject(err)
      );
    });
  });
}

function loadFBX(name, path) {
  const loader = new FBXLoader();
  return new Promise((resolve, reject) => {
    loader.setPath(path).load(
      `${name}.fbx`,
      (fbx) => resolve(fbx),
      null,
      (err) => reject(err)
    );
  });
}

// 同時載入
Promise.all([
  loadOBJ('car', '/models/obj/'),
  loadFBX('character', '/models/fbx/')
]).then(([car, character]) => {
  scene.add(car);
  scene.add(character);
}).catch(err => console.error('Model load error:', err));

重點Promise 讓載入流程可讀性更高,也方便在 async/await 中使用。


常見陷阱與最佳實踐

陷阱 說明 解決方式
路徑錯誤 Loader 的 setPath 必須以斜線結尾,且相對於網頁根目錄或 CDN。 使用 new URL('file.obj', import.meta.url) 產生絕對 URL,或在開發環境檢查 webpack-dev-server 的 publicPath。
材質不顯示 OBJ 只載入了幾何,卻忘記載入 .mtl,或 .mtl 中的貼圖路徑錯誤。 確認 .mtl 中的 map_Kd 路徑正確,或直接在程式碼裡手動指定 material.map = textureLoader.load('...')
FBX 動畫不播放 忘記在渲染迴圈中呼叫 mixer.update(delta) animate() 裡加入 mixer.update(clock.getDelta()),且確保 clock 已宣告。
模型過大/過小 載入的模型單位與 Three.js 不同,導致畫面全看不見。 使用 object.scale.setScalar(0.01)object.scale.multiplyScalar(...) 調整,或在建模時統一單位。
記憶體洩漏 多次載入後未釋放舊的 GeometryTexture 在不需要時呼叫 geometry.dispose()material.dispose()texture.dispose(),並從場景 remove 物件。

最佳實踐

  1. 使用 CDN 或模組化打包:在大型專案中,建議把 threeOBJLoaderFBXLoader 透過 npm 安裝,並使用打包工具(Webpack、Vite)產生最小化檔案,減少載入時間。
  2. 預先壓縮貼圖:使用 BasisKTX2 等現代壓縮格式,可大幅降低貼圖檔案大小,提升載入速度。
  3. LOD(Level of Detail):對遠距離模型使用低解析度版本,近距離再切換高解析度模型,減少渲染負擔。
  4. 進度條 UI:利用 Loader 的 onProgress 參數,結合 THREE.LoadingManager,為使用者呈現載入進度,提升體驗。
  5. 統一座標系:在導出模型時,固定 Y 軸為「上」或「前」的慣例,避免在程式中反覆調整 rotation

實際應用場景

場景 使用 Loader 為何選擇
產品展示網站(如家具、汽車) OBJLoader + MTL OBJ 檔案較小、可快速載入,且只需靜態材質。
角色動畫遊戲 FBXLoader FBX 能一次帶入骨架、動畫、貼圖,減少多檔案管理。
AR/VR 互動體驗 FBXLoader(含動畫) + GLTFLoader(若支援) FBX 的動畫資訊可直接套用於 VR 控制器驅動。
大型城市模型 OBJLoader(分塊)或 FBXLoader(分層) 以分段載入方式結合 LOD,避免一次載入過大檔案。
即時設計工具 OBJLoader(即時編輯) OBJ 結構簡單,開發者可在程式中動態修改頂點或材質。

總結

  • OBJLoader 適合靜態、低檔案大小的模型,搭配 .mtl 可快速呈現材質;
  • FBXLoader 則是完整動畫、骨架、貼圖的首選,尤其在遊戲與互動式專案中不可或缺。
  • 正確的 路徑設定、材質處理、動畫更新,是避免常見錯誤的關鍵。
  • 採用 Promise / async‑awaitLoadingManager資源壓縮LOD 等最佳實踐,能讓模型載入更流暢、效能更佳。

掌握上述概念與技巧後,你就能在 Three.js 中自如地載入各式外部模型,為網站或應用注入豐富的 3D 體驗。祝開發順利,玩得開心! 🎉