Loading...
Loading...
Shrinking glTF assets for three.js with gltf-transform: inspect, validate and optimize commands, Draco, meshopt and quantization, KTX2, WebP and AVIF textures, the loader setup each output needs, and gltfjsx for React. Use when compressing, optimizing or validating 3D models before loading them.
npx skill4agent add cloudai-x/threejs-skills threejs-asset-pipelineinspectnpx @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.glbimport * 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);inspectextensionsUsedextensionsRequirednpx @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_NORMALIZEDextensionsRequiredKHR_draco_mesh_compressionEXT_meshopt_compressionKHR_texture_basisuKHR_mesh_quantizationEXT_mesh_gpu_instancingEXT_texture_webpEXT_texture_avifvalidateinspectoptimizeinspect# 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
# GPU textures instead of download size, keep every triangle
npx @gltf-transform/cli optimize model.glb model.opt.glb --texture-compress ktx2 --simplify false--simplify false--compress draco|meshopt|quantizemeshopt--texture-compress ktx2|webp|avifauto--texture-size <px>--simplify <bool>--simplify-ratio <0-1>--simplify-error <fraction>--simplify-lock-border <bool>--instance <bool>--instance-min <count>--join <bool>--join-meshes <bool>--join-named <bool>--flatten <bool>--join--flatten--weld <bool>draco--weld falsesimplify--weld true--prune <bool>--resample <bool>--palette <bool>--sparse <bool>--meshopt-level medium|high--texture-compress ktx2optimize--compress dracodraco--compress meshoptmeshoptquantizenpx @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.glbgzipmodel.opt.glb.gzContent-Encoding: gzipdracomeshoptoptimize# 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
# Download size: WebP or AVIF, quality 1-100
npx @gltf-transform/cli webp model.glb model.opt.glb --quality 80
# Cap dimensions of PNG or JPEG textures; never enlarges them
npx @gltf-transform/cli resize model.glb model.opt.glb --width 1024 --height 1024etc1suastcoptimize --texture-compress ktx2ktxetc1suastcoptimize --texture-compress ktx2--slotsdetectSupportinspectnpx @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.glbwelddracosimplifymeshoptweldsimplifymeshoptsimplify--ratio--error--lock-border truejoininstanceinstance--mindedupprunesetDecoderPathsetTranscoderPathimport { 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);detectSupport.detectSupport( renderer )node pipeline.mjs// 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);"meshopt.encoder": MeshoptEncoderregisterDependenciesawait MeshoptEncoder.readymeshopt({ encoder: MeshoptEncoder })textureCompress({ encoder, targetFormat: "webp" })gltfjsxnpx gltfjsx model.glb --transformModel.jsx--types--transformmodel-transformed.glb--resolution--resolution--simplify--ratio--errormesh.materialKTX2Loader.detectSupport(renderer)await renderer.init()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);import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"npx @gltf-transform/cli optimize model.glb out.glb --compress ktx2npx @gltf-transform/cli optimize model.glb out.glb --compress draco --texture-compress ktx2--texture-compress--compressnpx @gltf-transform/cli draco model.glb a.glb && npx @gltf-transform/cli webp a.glb out.glb --quality 80npx @gltf-transform/cli webp model.glb a.glb --quality 80 && npx @gltf-transform/cli draco a.glb out.glbdracomeshoptoptimizenew GLTFLoader().loadAsync("draco.glb")new GLTFLoader().setDRACOLoader(new DRACOLoader()).loadAsync("draco.glb")new GLTFLoader().loadAsync("meshopt.glb")new GLTFLoader().setMeshoptDecoder(MeshoptDecoder).loadAsync("meshopt.glb")new GLTFLoader().loadAsync("ktx2.glb")new GLTFLoader().setKTX2Loader(new KTX2Loader().detectSupport(renderer)).loadAsync("ktx2.glb")setKTX2Loader(new KTX2Loader())setKTX2Loader(new KTX2Loader().detectSupport(renderer)).detectSupport( renderer )dracoLoader.setDecoderPath(cdnPathFromAnotherRelease)new GLTFLoader().setDRACOLoader(new DRACOLoader())dracoLoader.setDecoderConfig({ type: "js" })new DRACOLoader()ktx2Loader.detectSupportAsync(renderer)await renderer.init(); ktx2Loader.detectSupport(renderer);npx @gltf-transform/cli dequantize model.glb out.glbnpx @gltf-transform/cli quantize model.glb out.glbthreejs-loadersthreejs-react-three-fiberthreejs-performance