Three.js 與 glTF 網頁管線 是做什麼的?
本技能涵蓋將 Blender 或 vcad 模型轉換為體積小巧的 .glb 檔案的步驟。該檔案必須能夠快速載入,並在 Three.js 中輕鬆製作動畫。
1. Blender 與 vcad 的匯出檢查清單
節點名稱
- 為每個可動零件取一個清晰且穩定的名稱:
Head、Visor、Eye_L、Eye_R、Torso_Door、Wheel_FL、Wheel_FR、Steering_Wheel。 - 之後 Three.js 便可透過
model.getObjectByName("Torso_Door")找到該零件。
樞軸
- 將模型根部原點置於模型底部中心。這樣在 Three.js 中,模型將在
Y = 0處著地。 - 將每個活動零件的樞軸放在其旋轉軸心上。例如:門鉸鏈、車輪中心、頸部底部、天線底部。
變形
- 在匯出前套用所有變形。
- 縮放必須為
1, 1, 1。初始旋轉必須為0, 0, 0。 - 此規則可防止程序化動畫過程中產生偏移與剪切變形。
幾何體預算
- 合併共用同一材質的靜態網格。此步驟可減少繪製呼叫次數。
- 僅在腳本需要單獨動畫時,才將網格保持獨立。對於需要柔軟變形的部分,請使用帶有骨架的
SkinnedMesh。 - 主要資產的目標為數千個三角形。背景道具的目標則在 1,000 個三角形以下。
2. 使用 @gltf-transform/cli 進行命令列最佳化
本專案使用 Bun。請透過 bunx 執行工具。若機器有安裝 Node.js,也可使用 npx。
# 顯示網格、紋理與動畫資訊
bunx @gltf-transform/cli inspect model.glb
# 完整的網頁最佳化:Meshopt 壓縮 + WebP 紋理 + 清理
bunx @gltf-transform/cli optimize model.glb model-optimized.glb \
--compress meshopt \
--texture-compress webp \
--texture-size 2048
# 替代方案:Draco 幾何壓縮
bunx @gltf-transform/cli draco model.glb model-draco.glb
Meshopt 或 Draco
- Meshopt (
EXT_meshopt_compression) 解碼速度極快。它保留頂點順序,同時壓縮動畫關鍵幀與形態目標。適合用於有動畫的資產。 - Draco (
KHR_draco_mesh_compression) 能為密集的靜態網格提供最小的檔案體積。例如:車身與掃描物件。解碼器在啟動時會耗費較多時間。
其他實用步驟
# 移除重複網格與未使用資料
bunx @gltf-transform/cli dedup model.glb model.glb
# 將紋理轉換為 KTX2 (BasisU)
bunx @gltf-transform/cli etc1s model.glb model-ktx2.glb
在每個步驟後檢查檔案。確認檔案大小、三角形數量以及紋理尺寸。
3. Three.js 整合
支援 Draco 與 Meshopt 的穩健載入器
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/1.5.7/');
const gltfLoader = new GLTFLoader();
gltfLoader.setDRACOLoader(dracoLoader);
gltfLoader.setMeshoptDecoder(MeshoptDecoder);
const gltf = await gltfLoader.loadAsync('/models/bender-optimized.glb');
const root = gltf.scene;
scene.add(root);
兩種動畫方法
- 使用節點變形的程序化動畫。 此方法適用於機器人、車輛及其他剛性零件。例如:車輪旋轉、門開關、頭部追蹤、天線搖擺。
const head = root.getObjectByName('Head');
const door = root.getObjectByName('Torso_Door');
const antenna = root.getObjectByName('Antenna');
function animate(time) {
if (antenna) {
antenna.rotation.z = Math.sin(time * 0.005) * 0.15;
}
if (head) {
head.rotation.y = THREE.MathUtils.lerp(head.rotation.y, targetYaw, 0.1);
}
}
- 使用
THREE.AnimationMixer的預錄片段。 此方法適用於 Blender 匯出的複雜循環動作:走路、跳舞與臉部變形。
const mixer = new THREE.AnimationMixer(root);
const idleClip = THREE.AnimationClip.findByName(gltf.animations, 'Idle');
if (idleClip) {
mixer.clipAction(idleClip).play();
}
// 在渲染迴圈中:
// mixer.update(clock.getDelta());
金屬零件的 PBR 光照
當 Three.js 場景中沒有環境包圍時,金屬材質(metalness > 0.8)會顯示為黑色。請設定環境貼圖:
import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
const pmremGenerator = new THREE.PMREMGenerator(renderer);
scene.environment = pmremGenerator.fromScene(new RoomEnvironment(), 0.04).texture;
使用 scene.environmentIntensity 調整反射強度。對於金屬資產,建議將色調映射保持在 THREE.ACESFilmicToneMapping。
效能規則
- 每個網格群組使用一個材質。每個材質會消耗一次繪製呼叫。
- 重複使用紋理。將小紋理打包到一張圖集中。
- 設定
renderer.outputColorSpace = THREE.SRGBColorSpace。glTF 載入器會設定紋理色彩空間。 - 當某個資產需要出現多次時,請使用
THREE.InstancedMesh。 - 移除模型時記得釋放資源:遍歷場景,然後調用
geometry.dispose()與material.dispose()。