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.js、FBXLoader.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(...) 調整,或在建模時統一單位。 |
| 記憶體洩漏 | 多次載入後未釋放舊的 Geometry、Texture。 |
在不需要時呼叫 geometry.dispose()、material.dispose()、texture.dispose(),並從場景 remove 物件。 |
最佳實踐
- 使用 CDN 或模組化打包:在大型專案中,建議把
three、OBJLoader、FBXLoader透過 npm 安裝,並使用打包工具(Webpack、Vite)產生最小化檔案,減少載入時間。 - 預先壓縮貼圖:使用
Basis、KTX2等現代壓縮格式,可大幅降低貼圖檔案大小,提升載入速度。 - LOD(Level of Detail):對遠距離模型使用低解析度版本,近距離再切換高解析度模型,減少渲染負擔。
- 進度條 UI:利用 Loader 的
onProgress參數,結合THREE.LoadingManager,為使用者呈現載入進度,提升體驗。 - 統一座標系:在導出模型時,固定 Y 軸為「上」或「前」的慣例,避免在程式中反覆調整
rotation。
實際應用場景
| 場景 | 使用 Loader | 為何選擇 |
|---|---|---|
| 產品展示網站(如家具、汽車) | OBJLoader + MTL | OBJ 檔案較小、可快速載入,且只需靜態材質。 |
| 角色動畫遊戲 | FBXLoader | FBX 能一次帶入骨架、動畫、貼圖,減少多檔案管理。 |
| AR/VR 互動體驗 | FBXLoader(含動畫) + GLTFLoader(若支援) | FBX 的動畫資訊可直接套用於 VR 控制器驅動。 |
| 大型城市模型 | OBJLoader(分塊)或 FBXLoader(分層) | 以分段載入方式結合 LOD,避免一次載入過大檔案。 |
| 即時設計工具 | OBJLoader(即時編輯) | OBJ 結構簡單,開發者可在程式中動態修改頂點或材質。 |
總結
- OBJLoader 適合靜態、低檔案大小的模型,搭配
.mtl可快速呈現材質; - FBXLoader 則是完整動畫、骨架、貼圖的首選,尤其在遊戲與互動式專案中不可或缺。
- 正確的 路徑設定、材質處理、動畫更新,是避免常見錯誤的關鍵。
- 採用 Promise / async‑await、LoadingManager、資源壓縮與 LOD 等最佳實踐,能讓模型載入更流暢、效能更佳。
掌握上述概念與技巧後,你就能在 Three.js 中自如地載入各式外部模型,為網站或應用注入豐富的 3D 體驗。祝開發順利,玩得開心! 🎉