Communitygithub.com

zipang/Three.js 與 glTF 網頁管線

最佳化 3D 素材(.glb 與 .gltf)並整合於 Three.js、React Three Fiber、GSAP 及 gltf-transform 的互動式網頁動畫中。可用於壓縮(Draco、Meshopt、KTX2)、樞軸與動畫設定,以及網頁渲染。

Three.js 與 glTF 網頁管線 是什麼?

這份技能提供了一套完整的 3D 素材管道:從 Blender 或 vcad 模型產出,經由 gltf-transform CLI 工具進行最佳化壓縮(Meshopt、Draco、KTX2),再到 Three.js 環境中進行載入與動畫設定。它特別強調命名規則、樞軸定位與變形套用等前置步驟,以確保模型在網頁上能夠穩定且有效率地運作。此技能同時支援程序化動畫(透過節點變形)與預錄動畫(透過 AnimationMixer),並提供 PBR 材質的實作建議與效能最佳化原則。所有範例皆為平台無關的標準 JavaScript/TypeScript,可直接應用於 Three.js、React Three Fiber 等環境。

相容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/zipang/bender-agent/tree/HEAD/.agents/skills/threejs-web

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

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);

兩種動畫方法

  1. 使用節點變形的程序化動畫。 此方法適用於機器人、車輛及其他剛性零件。例如:車輪旋轉、門開關、頭部追蹤、天線搖擺。
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);
  }
}
  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()。

相關技能