threejs-asset-pipeline

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Three.js Asset Pipeline

Three.js 资源管线

Targets three.js r186. Examples use WebGLRenderer; the WebGPU and TSL section uses WebGPURenderer.
以 three.js r186 为目标。示例使用 WebGLRenderer;WebGPU 和 TSL 一节使用 WebGPURenderer。

Quick Start

快速开始

Compress on the command line, then load the result. Run
inspect
before and after to see what the pass saved:
bash
npx @gltf-transform/cli inspect model.glb
npx @gltf-transform/cli optimize model.glb model.opt.glb --compress draco --texture-compress webp --texture-size 2048
npx @gltf-transform/cli inspect model.opt.glb
Draco output needs a DRACOLoader attached; WebP textures decode in the browser:
javascript
import * as THREE from "three";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { DRACOLoader } from "three/addons/loaders/DRACOLoader.js";

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const scene = new THREE.Scene();
const sun = new THREE.DirectionalLight(0xffffff, 2); // glTF PBR materials render black without lights or an environment
sun.position.set(3, 5, 2);
scene.add(sun, new THREE.HemisphereLight(0xffffff, 0x444444, 1.5));

const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(2, 1.5, 3);
camera.lookAt(0, 0, 0);

const gltf = await new GLTFLoader()
  .setDRACOLoader(new DRACOLoader()) // the Draco WASM decoder loads from the three package
  .loadAsync("model.opt.glb");
scene.add(gltf.scene);

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

renderer.setAnimationLoop(animate);
先在命令行中压缩,再加载结果。运行
inspect
前后对比,看看这一轮处理节省了多少:
bash
npx @gltf-transform/cli inspect model.glb
npx @gltf-transform/cli optimize model.glb model.opt.glb --compress draco --texture-compress webp --texture-size 2048
npx @gltf-transform/cli inspect model.opt.glb
Draco 输出需要挂载 DRACOLoader;WebP 纹理可直接在浏览器中解码:
javascript
import * as THREE from "three";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { DRACOLoader } from "three/addons/loaders/DRACOLoader.js";

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const scene = new THREE.Scene();
const sun = new THREE.DirectionalLight(0xffffff, 2); // glTF PBR materials render black without lights or an environment
sun.position.set(3, 5, 2);
scene.add(sun, new THREE.HemisphereLight(0xffffff, 0x444444, 1.5));

const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(2, 1.5, 3);
camera.lookAt(0, 0, 0);

const gltf = await new GLTFLoader()
  .setDRACOLoader(new DRACOLoader()) // the Draco WASM decoder loads from the three package
  .loadAsync("model.opt.glb");
scene.add(gltf.scene);

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

renderer.setAnimationLoop(animate);

Inspect and Validate

检查与验证

Never guess where the bytes are.
inspect
prints an overview (generator,
extensionsUsed
,
extensionsRequired
) and one table each for scenes, meshes, materials, textures and animations, so a geometry-heavy file and a texture-heavy file are obvious at a glance.
bash
npx @gltf-transform/cli inspect model.glb
npx @gltf-transform/cli inspect model.opt.glb --format csv
npx @gltf-transform/cli validate model.glb --ignore ACCESSOR_WEIGHTS_NON_NORMALIZED
extensionsRequired
is the runtime contract:
KHR_draco_mesh_compression
,
EXT_meshopt_compression
and
KHR_texture_basisu
each map to a piece of loader wiring below, while
KHR_mesh_quantization
,
EXT_mesh_gpu_instancing
,
EXT_texture_webp
and
EXT_texture_avif
need nothing at runtime.
validate
runs the official glTF validator and reports errors, warnings and hints; it exits nonzero when errors are found, so it works as a CI gate. The commands in this skill that write a file log the input and output sizes when they finish;
inspect
the output to see which table the savings came from.
不要凭空猜测体积花在何处。
inspect
会打印概览(generator、
extensionsUsed
、
extensionsRequired
),并分别为场景、网格、材质、纹理和动画输出一张表,因此一眼就能看出文件是几何体偏重还是纹理偏重。
bash
npx @gltf-transform/cli inspect model.glb
npx @gltf-transform/cli inspect model.opt.glb --format csv
npx @gltf-transform/cli validate model.glb --ignore ACCESSOR_WEIGHTS_NON_NORMALIZED
extensionsRequired
是运行时契约:
KHR_draco_mesh_compression
、
EXT_meshopt_compression
和
KHR_texture_basisu
分别对应下文的一段加载器配置,而
KHR_mesh_quantization
、
EXT_mesh_gpu_instancing
、
EXT_texture_webp
和
EXT_texture_avif
不需要任何运行时配置。
validate
会运行官方 glTF validator,并报告错误、警告和提示;发现错误时退出码非零,因此可以用作 CI 门禁。本 skill 中会写入文件的命令完成后都会记录输入和输出大小;检查输出文件即可看出节省来自哪一类内容。

The optimize Command

optimize 命令

optimize
chains the common passes in one run: dedup, instance, palette, flatten, join, weld, simplify, resample, prune and sparse data, then texture compression, and geometry compression last. With no flags it applies meshopt compression and recompresses textures in their original format. Defaults are aggressive but generic —
inspect
first, then tune:
bash
undefined
optimize
会在一次运行中串联常用处理:dedup、instance、palette、flatten、join、weld、simplify、resample、prune 和稀疏数据处理,然后进行纹理压缩,最后执行几何体压缩。不指定任何参数时,它会应用 meshopt 压缩,并以原始格式重新压缩纹理。默认设置较为激进且偏通用——应先运行
inspect
,再根据实际情况调整:
bash
undefined

draco geometry + webp textures, capped at 2048px

draco geometry + webp textures, capped at 2048px

npx @gltf-transform/cli optimize model.glb model.opt.glb --compress draco --texture-compress webp --texture-size 2048
npx @gltf-transform/cli optimize model.glb model.opt.glb --compress draco --texture-compress webp --texture-size 2048

GPU textures instead of download size, keep every triangle

GPU textures instead of download size, keep every triangle

npx @gltf-transform/cli optimize model.glb model.opt.glb --texture-compress ktx2 --simplify false

Main flags (booleans take an explicit value, like `--simplify false`):

- `--compress draco|meshopt|quantize` — geometry and animation compression (default `meshopt`).
- `--texture-compress ktx2|webp|avif` — texture codec (default `auto`, recompress in the original format).
- `--texture-size <px>` — cap texture dimensions, default 2048.
- `--simplify <bool>`, `--simplify-ratio <0-1>`, `--simplify-error <fraction>`, `--simplify-lock-border <bool>` — mesh simplification; ratio 0 and error 0.0001 by default.
- `--instance <bool>`, `--instance-min <count>` — GPU instancing for repeated meshes.
- `--join <bool>`, `--join-meshes <bool>`, `--join-named <bool>`, `--flatten <bool>` — draw-call reduction; `--join` requires `--flatten`.
- `--weld <bool>` — merge duplicate vertices; `draco` welds by itself even with `--weld false`, while `simplify` welds only unindexed primitives, so keep `--weld true` when simplifying.
- `--prune <bool>`, `--resample <bool>`, `--palette <bool>`, `--sparse <bool>`, `--meshopt-level medium|high`.

With `--texture-compress ktx2`, `optimize` picks bitstreams itself: UASTC for the normal, occlusion and metallic-roughness slots, ETC1S for everything else. For alternatives outside this skill's lane, gltfpack covers similar ground; gltf-transform is enough here.
npx @gltf-transform/cli optimize model.glb model.opt.glb --texture-compress ktx2 --simplify false

主要参数(布尔参数需要显式指定值,例如 `--simplify false`):

- `--compress draco|meshopt|quantize`——几何体和动画压缩,默认使用 `meshopt`。
- `--texture-compress ktx2|webp|avif`——纹理编解码器,默认值为 `auto`,以原始格式重新压缩。
- `--texture-size <px>`——限制纹理尺寸,默认值为 2048。
- `--simplify <bool>`、`--simplify-ratio <0-1>`、`--simplify-error <fraction>`、`--simplify-lock-border <bool>`——网格简化;ratio 默认为 0,error 默认为 0.0001。
- `--instance <bool>`、`--instance-min <count>`——对重复网格使用 GPU instancing。
- `--join <bool>`、`--join-meshes <bool>`、`--join-named <bool>`、`--flatten <bool>`——减少 draw call;使用 `--join` 时必须同时启用 `--flatten`。
- `--weld <bool>`——合并重复顶点;即使设置 `--weld false`,`draco` 也会自行执行顶点合并,而 `simplify` 只会处理未索引 primitive,因此在简化时应保持 `--weld true`。
- `--prune <bool>`、`--resample <bool>`、`--palette <bool>`、`--sparse <bool>`、`--meshopt-level medium|high`。

使用 `--texture-compress ktx2` 时,`optimize` 会自行选择 bitstream:法线、遮蔽和 metallic-roughness 槽位使用 UASTC,其余使用 ETC1S。对于本 skill 范围之外的替代方案,gltfpack 也覆盖类似场景;这里使用 gltf-transform 已经足够。

Geometry Compression

几何体压缩

Three codecs with different tradeoffs — pick per model, not per project:
  • Draco (
    --compress draco
    ,
    draco
    command): compresses triangle-mesh geometry only — animation, morph targets, textures, points and lines are not compressed. Runtime: DRACOLoader.
  • Meshopt (
    --compress meshopt
    ,
    meshopt
    command): compresses point, line and triangle geometry, morph targets and animation tracks, and decodes very quickly; pair it with lossless gzip or brotli over the wire. Runtime: MeshoptDecoder.
  • Quantize (
    quantize
    command): stores float attributes as 8- or 16-bit integers under KHR_mesh_quantization — smaller on disk and in GPU memory, and no decoder to wire; GLTFLoader reads quantized attributes natively.
bash
npx @gltf-transform/cli draco model.glb model.opt.glb --method edgebreaker --quantize-position 14
npx @gltf-transform/cli meshopt model.glb model.opt.glb --level high
npx @gltf-transform/cli quantize model.glb model.opt.glb
npx @gltf-transform/cli gzip model.opt.glb
gzip
writes
model.opt.glb.gz
next to its input; serve it with
Content-Encoding: gzip
and the browser decompresses it transparently. It adds little on top of Draco or JPEG textures, and earns the most on top of meshopt and quantize.
Make
draco
,
meshopt
or
optimize
the last command in a chain: every other command that rewrites the model decodes Draco and meshopt geometry when it reads the file (it warns "Decoded KHR_draco_mesh_compression. Further compression will be lossy.") and writes it back uncompressed.
三种 codec 各有取舍——应按模型选择,而不是按项目选择:
  • Draco(
    --compress draco
    ,
    draco
    命令):仅压缩三角形网格几何体——不会压缩动画、morph target、纹理、点和线。运行时需要 DRACOLoader。
  • Meshopt(
    --compress meshopt
    ,
    meshopt
    命令):可压缩点、线和三角形几何体、morph target 以及动画轨道,解码速度非常快;可通过网络传输搭配无损 gzip 或 brotli。运行时需要 MeshoptDecoder。
  • Quantize(
    quantize
    命令):按照 KHR_mesh_quantization 将浮点属性存储为 8 位或 16 位整数——在磁盘和 GPU 内存中的占用更小,并且无需配置解码器;GLTFLoader 可原生读取量化属性。
bash
npx @gltf-transform/cli draco model.glb model.opt.glb --method edgebreaker --quantize-position 14
npx @gltf-transform/cli meshopt model.glb model.opt.glb --level high
npx @gltf-transform/cli quantize model.glb model.opt.glb
npx @gltf-transform/cli gzip model.opt.glb
gzip
会在输入文件旁写出
model.opt.glb.gz
;使用
Content-Encoding: gzip
提供服务,浏览器会自动解压。在 Draco 或 JPEG 纹理基础上使用它,额外收益很小;在 meshopt 和 quantize 基础上使用则收益最大。
务必将
draco
、
meshopt
或
optimize
放在命令链的最后:其他所有会重写模型的命令在读取文件时都会解码 Draco 和 meshopt 几何体,并警告
Decoded KHR_draco_mesh_compression. Further compression will be lossy.
,随后以未压缩形式写回。

Texture Compression

纹理压缩

Two different problems: KTX2 (Basis Universal) stays compressed in GPU memory wherever the GPU supports one of its transcode targets (KTX2Loader falls back to uncompressed RGBA otherwise) and uploads fast — it optimizes GPU memory and performance — while WebP and AVIF shrink the download and are decompressed to raw pixels in GPU memory, which can occupy 4-8x more there.
bash
undefined
这里涉及两个不同问题:KTX2(Basis Universal)在 GPU 支持其任一转码目标时,会以压缩形式保留在 GPU 内存中(否则 KTX2Loader 会回退到未压缩的 RGBA),并且上传速度很快——它优化的是 GPU 内存和性能;而 WebP 与 AVIF 缩减的是下载体积,解压后在 GPU 内存中会变为原始像素,占用可能达到 4 至 8 倍。
bash
undefined

KTX2: ETC1S for color (smaller, lossier), UASTC for normal maps (larger, cleaner)

KTX2: ETC1S for color (smaller, lossier), UASTC for normal maps (larger, cleaner)

npx @gltf-transform/cli etc1s model.glb model.opt.glb --quality 128 npx @gltf-transform/cli uastc model.glb model.opt.glb --level 4
npx @gltf-transform/cli etc1s model.glb model.opt.glb --quality 128 npx @gltf-transform/cli uastc model.glb model.opt.glb --level 4

Download size: WebP or AVIF, quality 1-100

Download size: WebP or AVIF, quality 1-100

npx @gltf-transform/cli webp model.glb model.opt.glb --quality 80
npx @gltf-transform/cli webp model.glb model.opt.glb --quality 80

Cap dimensions of PNG or JPEG textures; never enlarges them

Cap dimensions of PNG or JPEG textures; never enlarges them

npx @gltf-transform/cli resize model.glb model.opt.glb --width 1024 --height 1024

KTX2 output — `etc1s`, `uastc` or `optimize --texture-compress ktx2` — shells out to the `ktx` command from KTX-Software (4.4.0 or newer), so install that first; WebP and AVIF are encoded in-process. The single `etc1s` and `uastc` commands convert every texture to one bitstream; the per-slot split (UASTC for normal, occlusion and metallic-roughness, ETC1S for the rest) is what `optimize --texture-compress ktx2` does for you, and `--slots` selects texture roles by name when you need a custom split. Runtime: KTX2 needs KTX2Loader with `detectSupport`; WebP and AVIF decode in the browser.
npx @gltf-transform/cli resize model.glb model.opt.glb --width 1024 --height 1024

KTX2 输出——包括 `etc1s`、`uastc` 或 `optimize --texture-compress ktx2`——会调用 KTX-Software 提供的 `ktx` 命令(需 4.4.0 或更高版本),因此必须先安装它;WebP 和 AVIF 则在进程内编码。单独的 `etc1s` 和 `uastc` 命令会将所有纹理转换为一种 bitstream;`optimize --texture-compress ktx2` 会按槽位自动拆分:法线、遮蔽和 metallic-roughness 使用 UASTC,其余使用 ETC1S。需要自定义拆分时,可使用 `--slots` 按名称选择纹理角色。运行时,KTX2 需要带 `detectSupport` 的 KTX2Loader;WebP 和 AVIF 可直接在浏览器中解码。

Scene Optimization Commands

场景优化命令

Run these when
inspect
shows the problem is draw calls or vertex count rather than bytes:
bash
npx @gltf-transform/cli weld model.glb model.opt.glb
npx @gltf-transform/cli simplify model.glb model.opt.glb --ratio 0.5 --error 0.001
npx @gltf-transform/cli join model.glb model.opt.glb
npx @gltf-transform/cli instance model.glb model.opt.glb --min 5
npx @gltf-transform/cli dedup model.glb model.opt.glb
npx @gltf-transform/cli prune model.glb model.opt.glb
  • weld
    merges bitwise-identical vertices and indexes the primitive (points are skipped).
    draco
    welds by itself;
    simplify
    welds only unindexed primitives and
    meshopt
    none, and meshopt's reorder step skips unindexed primitives (it warns when none are indexed), so run
    weld
    before
    simplify
    and
    meshopt
    .
  • simplify
    removes triangles, aiming for
    --ratio
    (fraction of vertices to keep) while staying under
    --error
    (fraction of mesh extent);
    --lock-border true
    preserves open edges. It is lossy.
  • join
    merges compatible primitives to cut draw calls; it runs dedup and flatten itself first. In scenes that reuse one mesh heavily it inflates vertex count — use
    instance
    there instead.
  • instance
    writes EXT_mesh_gpu_instancing for meshes referenced by several nodes (
    --min
    defaults to 5); GLTFLoader renders those batches as an InstancedMesh with no runtime setup. It skips any file that contains an animation, and instanced meshes must share materials.
  • dedup
    merges redundant accessors, materials, meshes, skins and textures left by exporters.
  • prune
    deletes properties no Scene references, including animations that target nothing attached to a scene.
当
inspect
显示问题在于 draw call 或顶点数,而非文件字节数时,运行以下命令:
bash
npx @gltf-transform/cli weld model.glb model.opt.glb
npx @gltf-transform/cli simplify model.glb model.opt.glb --ratio 0.5 --error 0.001
npx @gltf-transform/cli join model.glb model.opt.glb
npx @gltf-transform/cli instance model.glb model.opt.glb --min 5
npx @gltf-transform/cli dedup model.glb model.opt.glb
npx @gltf-transform/cli prune model.glb model.opt.glb
  • weld
    合并逐位完全相同的顶点,并将 primitive 转换为索引形式(会跳过点)。
    draco
    会自行执行顶点合并;
    simplify
    只会处理未索引 primitive,
    meshopt
    则不会处理,而且 meshopt 的重排步骤会跳过未索引 primitive(未找到索引 primitive 时会发出警告),因此应在
    simplify
    和
    meshopt
    之前运行
    weld
    。
  • simplify
    通过移除三角形进行简化,目标是保留
    --ratio
    指定比例的顶点,同时将误差控制在
    --error
    指定的网格范围比例以内;
    --lock-border true
    可保留开放边缘。此过程有损。
  • join
    合并兼容的 primitive,以减少 draw call;它会先自行执行 dedup 和 flatten。在大量重复使用同一个网格的场景中,这可能增加顶点数,此时应改用
    instance
    。
  • instance
    会为被多个节点引用的网格写入 EXT_mesh_gpu_instancing(
    --min
    默认值为 5);GLTFLoader 会将这些批次渲染为 InstancedMesh,无需运行时设置。包含动画的文件会被跳过,并且实例化网格必须共用材质。
  • dedup
    合并导出器遗留的冗余 accessor、材质、网格、蒙皮和纹理。
  • prune
    删除 Scene 未引用的所有属性,包括目标未附加到 Scene 的动画。

Loader Setup for Each Output

每种输出的加载器配置

The pipeline choice decides the loader wiring. The Draco decoder and the Basis transcoder load from the three package by default — call
setDecoderPath
or
setTranscoderPath
only when you host those files yourself, and then pin the path to the three release you ship. Create one KTX2Loader for the app and share it between GLTFLoaders: each active instance loads its own transcoder and workers, and three.js warns when several are active.
javascript
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { DRACOLoader } from "three/addons/loaders/DRACOLoader.js";
import { KTX2Loader } from "three/addons/loaders/KTX2Loader.js";
import { MeshoptDecoder } from "three/addons/libs/meshopt_decoder.module.js";

// Wire every codec the pipeline may emit; a missing one throws at load time
const gltfLoader = new GLTFLoader()
  .setDRACOLoader(new DRACOLoader()) // KHR_draco_mesh_compression
  .setMeshoptDecoder(MeshoptDecoder) // EXT_meshopt_compression
  .setKTX2Loader(new KTX2Loader().detectSupport(renderer)); // KHR_texture_basisu

const gltf = await gltfLoader.loadAsync("model.opt.glb");
scene.add(gltf.scene);
What each missing piece throws: a Draco file without a DRACOLoader ("No DRACOLoader instance provided"), a meshopt file without a decoder ("setMeshoptDecoder must be called before loading compressed files"), a KTX2 file without a KTX2Loader ("setKTX2Loader must be called before loading KTX2 textures"), and a KTX2Loader that never ran
detectSupport
("Missing initialization with
.detectSupport( renderer )
"). WebP and AVIF textures, quantized attributes and EXT_mesh_gpu_instancing need none of this.
管线方案决定加载器的配置方式。Draco decoder 和 Basis transcoder 默认从 three 包加载——仅当自行托管这些文件时,才调用
setDecoderPath
或
setTranscoderPath
,并将路径固定到所使用的 three 版本。为应用创建一个 KTX2Loader,并在多个 GLTFLoader 之间共享:每个活跃实例都会加载自己的 transcoder 和 worker,多个实例同时处于活跃状态时 three.js 会发出警告。
javascript
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { DRACOLoader } from "three/addons/loaders/DRACOLoader.js";
import { KTX2Loader } from "three/addons/loaders/KTX2Loader.js";
import { MeshoptDecoder } from "three/addons/libs/meshopt_decoder.module.js";

// Wire every codec the pipeline may emit; a missing one throws at load time
const gltfLoader = new GLTFLoader()
  .setDRACOLoader(new DRACOLoader()) // KHR_draco_mesh_compression
  .setMeshoptDecoder(MeshoptDecoder) // EXT_meshopt_compression
  .setKTX2Loader(new KTX2Loader().detectSupport(renderer)); // KHR_texture_basisu

const gltf = await gltfLoader.loadAsync("model.opt.glb");
scene.add(gltf.scene);
缺少相应组件时分别会抛出以下错误:Draco 文件未配置 DRACOLoader 会报
No DRACOLoader instance provided
;meshopt 文件未配置 decoder 会报
setMeshoptDecoder must be called before loading compressed files
;KTX2 文件未配置 KTX2Loader 会报
setKTX2Loader must be called before loading KTX2 textures
;KTX2Loader 未运行
detectSupport
会报
Missing initialization with .detectSupport( renderer )
。WebP 和 AVIF 纹理、量化属性以及 EXT_mesh_gpu_instancing 均不需要这些配置。

Scripted Pipelines

脚本化管线

When the CLI stops fitting — per-project defaults, CI, many files — script the same transforms with the SDK. A Node script, run with
node pipeline.mjs
:
javascript
// deps: @gltf-transform/core @gltf-transform/extensions @gltf-transform/functions draco3dgltf
import { NodeIO } from "@gltf-transform/core";
import { ALL_EXTENSIONS } from "@gltf-transform/extensions";
import { dedup, prune, weld, draco } from "@gltf-transform/functions";
import draco3d from "draco3dgltf";

const io = new NodeIO()
  .registerExtensions(ALL_EXTENSIONS) // an extension left out is dropped on read, or throws if required
  .registerDependencies({
    "draco3d.decoder": await draco3d.createDecoderModule(), // reads Draco input
    "draco3d.encoder": await draco3d.createEncoderModule(),
  });

const document = await io.read("model.glb");
await document.transform(
  dedup(),
  weld(), // shared vertices, before compression
  prune(), // drop what no Scene references
  draco({ method: "edgebreaker", quantizePosition: 14 }),
);
await io.write("model.opt.glb", document);
Compression runs when the I/O class writes the file, so it stays the last step. Register every extension: the I/O writes only registered extensions, so WebP textures or quantized attributes without theirs make an invalid file. For meshopt instead, add
"meshopt.encoder": MeshoptEncoder
to
registerDependencies
after
await MeshoptEncoder.ready
(MeshoptEncoder from the meshoptimizer package) and pass
meshopt({ encoder: MeshoptEncoder })
; texture compression in scripts takes
textureCompress({ encoder, targetFormat: "webp" })
with sharp as the encoder.
当 CLI 不再适用时——例如需要项目级默认配置、CI 或处理大量文件——可以使用 SDK 编排相同的转换。通过
node pipeline.mjs
运行以下 Node 脚本:
javascript
// deps: @gltf-transform/core @gltf-transform/extensions @gltf-transform/functions draco3dgltf
import { NodeIO } from "@gltf-transform/core";
import { ALL_EXTENSIONS } from "@gltf-transform/extensions";
import { dedup, prune, weld, draco } from "@gltf-transform/functions";
import draco3d from "draco3dgltf";

const io = new NodeIO()
  .registerExtensions(ALL_EXTENSIONS) // an extension left out is dropped on read, or throws if required
  .registerDependencies({
    "draco3d.decoder": await draco3d.createDecoderModule(), // reads Draco input
    "draco3d.encoder": await draco3d.createEncoderModule(),
  });

const document = await io.read("model.glb");
await document.transform(
  dedup(),
  weld(), // shared vertices, before compression
  prune(), // drop what no Scene references
  draco({ method: "edgebreaker", quantizePosition: 14 }),
);
await io.write("model.opt.glb", document);
压缩会在 I/O 类写入文件时执行,因此必须保留为最后一步。需要注册所有 extension:I/O 只会写入已注册的 extension,因此 WebP 纹理或量化属性如果没有对应 extension,就会生成无效文件。如果改用 meshopt,则应在
await MeshoptEncoder.ready
之后,将
"meshopt.encoder": MeshoptEncoder
添加到
registerDependencies
(MeshoptEncoder 来自 meshoptimizer 包),并传入
meshopt({ encoder: MeshoptEncoder })
;脚本中的纹理压缩使用
textureCompress({ encoder, targetFormat: "webp" })
,并以 sharp 作为 encoder。

React Output with gltfjsx

使用 gltfjsx 生成 React 输出

gltfjsx
turns a model into a React Three Fiber component — declarative JSX you can edit, instead of a loaded black box:
bash
npx gltfjsx model.glb --transform
This writes
Model.jsx
(a component for @react-three/fiber;
--types
for a .tsx) and, because of
--transform
, also rewrites the asset:
model-transformed.glb
gets palette materials, joined meshes, welded and pruned geometry, Draco compression, and textures re-encoded as WebP, capped at
--resolution
(1024 by default) — normal maps are re-encoded as JPEG, capped at 2048 px or
--resolution
, whichever is larger — and the generated component loads that file. Add
--simplify
with
--ratio
and
--error
for simplification. The component side (Canvas, Suspense, useGLTF) is the threejs-react-three-fiber skill.
gltfjsx
会将模型转换为 React Three Fiber 组件——生成可编辑的声明式 JSX,而不是一个加载后无法修改的黑盒:
bash
npx gltfjsx model.glb --transform
该命令会写出
Model.jsx
(用于 @react-three/fiber 的组件;使用
--types
可生成 .tsx)。由于启用了
--transform
,它还会重写资源:
model-transformed.glb
将包含调色板材质、合并后的网格、经过顶点合并和清理的几何体、Draco 压缩,以及重新编码为 WebP 的纹理;纹理尺寸限制为
--resolution
,默认值为 1024——法线贴图会重新编码为 JPEG,尺寸上限为 2048 px 或
--resolution
中的较大者——生成的组件也会加载该文件。添加
--simplify
并配合
--ratio
和
--error
,即可执行简化。组件侧内容(Canvas、Suspense、useGLTF)由 threejs-react-three-fiber skill 负责。

WebGPU and TSL

WebGPU 与 TSL

The loaders and addons above are identical under WebGPURenderer, which renders the loaded materials as node materials by itself (
mesh.material
stays the class GLTFLoader created). One ordering rule:
KTX2Loader.detectSupport(renderer)
needs the renderer's backend, so create the loader after
await renderer.init()
.
javascript
import * as THREE from "three/webgpu";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { KTX2Loader } from "three/addons/loaders/KTX2Loader.js";
// renderer is an initialized WebGPURenderer (its init() was awaited)
const gltfLoader = new GLTFLoader()
  .setKTX2Loader(new KTX2Loader().detectSupport(renderer));
const gltf = await gltfLoader.loadAsync("model.opt.glb");
scene.add(gltf.scene);
上述加载器和 addon 在 WebGPURenderer 下完全相同,WebGPURenderer 会自行将加载的材质渲染为 node 材质(
mesh.material
仍是 GLTFLoader 创建的类)。有一条顺序规则:
KTX2Loader.detectSupport(renderer)
需要 renderer's backend,因此必须在
await renderer.init()
之后创建 loader。
javascript
import * as THREE from "three/webgpu";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { KTX2Loader } from "three/addons/loaders/KTX2Loader.js";
// renderer is an initialized WebGPURenderer (its init() was awaited)
const gltfLoader = new GLTFLoader()
  .setKTX2Loader(new KTX2Loader().detectSupport(renderer));
const gltf = await gltfLoader.loadAsync("model.opt.glb");
scene.add(gltf.scene);

Common Mistakes

常见错误

  • import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"
    →
    import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"
    : the three/examples/jsm specifier does not resolve under the import map three.js documents.
  • npx @gltf-transform/cli optimize model.glb out.glb --compress ktx2
    →
    npx @gltf-transform/cli optimize model.glb out.glb --compress draco --texture-compress ktx2
    : texture codecs belong to
    --texture-compress
    ;
    --compress
    takes draco, meshopt or quantize.
  • npx @gltf-transform/cli draco model.glb a.glb && npx @gltf-transform/cli webp a.glb out.glb --quality 80
    →
    npx @gltf-transform/cli webp model.glb a.glb --quality 80 && npx @gltf-transform/cli draco a.glb out.glb
    : every command except
    draco
    ,
    meshopt
    and
    optimize
    decodes Draco and meshopt geometry when it reads a file and writes it uncompressed, so geometry compression runs last.
  • new GLTFLoader().loadAsync("draco.glb")
    →
    new GLTFLoader().setDRACOLoader(new DRACOLoader()).loadAsync("draco.glb")
    : loading a KHR_draco_mesh_compression file without a DRACOLoader throws "No DRACOLoader instance provided".
  • new GLTFLoader().loadAsync("meshopt.glb")
    →
    new GLTFLoader().setMeshoptDecoder(MeshoptDecoder).loadAsync("meshopt.glb")
    : an EXT_meshopt_compression file throws "setMeshoptDecoder must be called before loading compressed files".
  • new GLTFLoader().loadAsync("ktx2.glb")
    →
    new GLTFLoader().setKTX2Loader(new KTX2Loader().detectSupport(renderer)).loadAsync("ktx2.glb")
    : a KHR_texture_basisu file throws "setKTX2Loader must be called before loading KTX2 textures".
  • setKTX2Loader(new KTX2Loader())
    →
    setKTX2Loader(new KTX2Loader().detectSupport(renderer))
    : a KTX2Loader that never ran detectSupport throws "Missing initialization with
    .detectSupport( renderer )
    ".
  • dracoLoader.setDecoderPath(cdnPathFromAnotherRelease)
    →
    new GLTFLoader().setDRACOLoader(new DRACOLoader())
    : the Draco decoder loads from the three package by default, and a path pinned to a different release mismatches the loader.
  • dracoLoader.setDecoderConfig({ type: "js" })
    →
    new DRACOLoader()
    : setDecoderConfig is deprecated in r185; without it DRACOLoader uses the WASM decoder wherever WebAssembly exists.
  • ktx2Loader.detectSupportAsync(renderer)
    →
    await renderer.init(); ktx2Loader.detectSupport(renderer);
    : detectSupportAsync is deprecated in r181; call detectSupport once the renderer is initialized.
  • npx @gltf-transform/cli dequantize model.glb out.glb
    →
    npx @gltf-transform/cli quantize model.glb out.glb
    : GLTFLoader reads KHR_mesh_quantization attributes with no runtime setup, so dequantize only when your own code needs float vertex data in model units: quantized attributes hold normalized integers, and quantized positions are scaled back by their node's transform.
  • import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"
    →
    import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"
    :在 three.js 文档提供的 import map 下,
    three/examples/jsm
    说明符无法解析。
  • npx @gltf-transform/cli optimize model.glb out.glb --compress ktx2
    →
    npx @gltf-transform/cli optimize model.glb out.glb --compress draco --texture-compress ktx2
    :纹理 codec 应通过
    --texture-compress
    指定;
    --compress
    只接受 draco、meshopt 或 quantize。
  • npx @gltf-transform/cli draco model.glb a.glb && npx @gltf-transform/cli webp a.glb out.glb --quality 80
    →
    npx @gltf-transform/cli webp model.glb a.glb --quality 80 && npx @gltf-transform/cli draco a.glb out.glb
    :除
    draco
    、
    meshopt
    和
    optimize
    外,所有命令在读取文件时都会解码 Draco 和 meshopt 几何体,并将其以未压缩形式写出,因此几何体压缩必须最后执行。
  • new GLTFLoader().loadAsync("draco.glb")
    →
    new GLTFLoader().setDRACOLoader(new DRACOLoader()).loadAsync("draco.glb")
    :加载 KHR_draco_mesh_compression 文件时若未配置 DRACOLoader,会抛出
    No DRACOLoader instance provided
    。
  • new GLTFLoader().loadAsync("meshopt.glb")
    →
    new GLTFLoader().setMeshoptDecoder(MeshoptDecoder).loadAsync("meshopt.glb")
    :EXT_meshopt_compression 文件会抛出
    setMeshoptDecoder must be called before loading compressed files
    。
  • new GLTFLoader().loadAsync("ktx2.glb")
    →
    new GLTFLoader().setKTX2Loader(new KTX2Loader().detectSupport(renderer)).loadAsync("ktx2.glb")
    :KHR_texture_basisu 文件会抛出
    setKTX2Loader must be called before loading KTX2 textures
    。
  • setKTX2Loader(new KTX2Loader())
    →
    setKTX2Loader(new KTX2Loader().detectSupport(renderer))
    :未运行 detectSupport 的 KTX2Loader 会抛出
    Missing initialization with .detectSupport( renderer )
    。
  • dracoLoader.setDecoderPath(cdnPathFromAnotherRelease)
    →
    new GLTFLoader().setDRACOLoader(new DRACOLoader())
    :Draco decoder 默认从 three 包加载,指向其他版本路径会导致 decoder 与 loader 不匹配。
  • dracoLoader.setDecoderConfig({ type: "js" })
    →
    new DRACOLoader()
    :setDecoderConfig 在 r185 中已弃用;不调用它时,只要环境支持 WebAssembly,DRACOLoader 就会使用 WASM decoder。
  • ktx2Loader.detectSupportAsync(renderer)
    →
    await renderer.init(); ktx2Loader.detectSupport(renderer);
    :detectSupportAsync 在 r181 中已弃用;应在 renderer 初始化完成后调用一次 detectSupport。
  • npx @gltf-transform/cli dequantize model.glb out.glb
    →
    npx @gltf-transform/cli quantize model.glb out.glb
    :GLTFLoader 无需运行时配置即可读取 KHR_mesh_quantization 属性,因此仅当自有代码需要以模型单位表示的浮点顶点数据时才应使用 dequantize:量化属性保存的是归一化整数,量化位置会通过所属节点的 transform 还原缩放。

See Also

另请参阅

  • threejs-loaders
    - the runtime side: loading progress, caching, cloning skinned models and disposal of what the pipeline produces.
  • threejs-react-three-fiber
    - the React app around a gltfjsx component: Canvas, useGLTF, Suspense and drei helpers.
  • threejs-performance
    - measuring what the pipeline bought: draw calls, GPU memory and renderer.info.
  • threejs-loaders
    ——运行时部分:加载进度、缓存、克隆蒙皮模型,以及释放管线生成的资源。
  • threejs-react-three-fiber
    ——围绕 gltfjsx 组件构建 React 应用:Canvas、useGLTF、Suspense 和 drei helper。
  • threejs-performance
    ——衡量管线带来的收益:draw call、GPU 内存和 renderer.info。