Three.js From Zero · Article s6-10

S6-10 Production Asset Pipeline

Season 6 · Article 10 · Finale

The Production Asset Pipeline

Artist hits "Export" in Blender. Web app ships the model. In between: 8 steps that turn 50MB of raw artistry into a 2MB cached asset optimized for first-paint. The full recipe.

🏁 Season 6 finale — Asset Pipeline concludes

The pipeline end-to-end

1

Artist exports from Blender

Apply modifiers. Principled BSDF materials. +Y Up on. Bake All Actions on. Draco checkbox on (even though we'll re-run gltf-transform).

File → Export → glTF 2.0 → output.glb
2

Validate

Before ANY further processing, check the file is correct.

gltf-transform validate output.glb
# or: npx gltf-validator output.glb

If validation fails, go back to step 1. Don't proceed.

3

Inspect

Know what you're dealing with.

gltf-transform inspect output.glb

Look at: total size, triangle count, texture sizes, extensions used.

4

Clean

Remove cruft.

gltf-transform optimize output.glb cleaned.glb \
  --prune --dedup --weld

Usually 10-30% smaller with no quality loss.

5

Resize oversized textures

Artists love shipping 8K textures for a 50-pixel-on-screen prop. Downscale.

gltf-transform resize cleaned.glb resized.glb \
  --width 2048 --height 2048 \
  --filter lanczos
6

Compress geometry

Pick Draco (smaller) or Meshopt (faster decode).

# Option A: Draco
gltf-transform draco resized.glb geo.glb

# Option B: Meshopt
gltf-transform meshopt resized.glb geo.glb --compression medium
7

Compress textures

UASTC for normals, ETC1S for everything else.

gltf-transform uastc geo.glb --filter "*normal*"
gltf-transform etc1s geo.glb --filter "*baseColor*,*roughness*,*metallic*"

End-product: final.glb.

8

Ship

Upload to CDN. Three.js consumes with DRACOLoader + KTX2Loader wired:

const loader = new GLTFLoader()
  .setDRACOLoader(draco)
  .setKTX2Loader(ktx2)
  .setMeshoptDecoder(MeshoptDecoder);
loader.load('https://cdn.example.com/final.glb', g => scene.add(g.scene));

2. One-script automation

#!/usr/bin/env bash
set -e
IN=$1
OUT=$2

npx gltf-transform validate "$IN"
npx gltf-transform optimize "$IN" tmp1.glb
npx gltf-transform resize tmp1.glb tmp2.glb --width 2048 --height 2048
npx gltf-transform meshopt tmp2.glb tmp3.glb --compression medium
npx gltf-transform uastc tmp3.glb tmp4.glb --filter "*normal*"
npx gltf-transform etc1s tmp4.glb "$OUT" --filter "*"
rm tmp*.glb
echo "Done: $OUT"
echo "Before/after:"
ls -lh "$IN" "$OUT"

Save as pipeline.sh, run ./pipeline.sh raw.glb final.glb.

3. CI integration

# .github/workflows/assets.yml
on: push
jobs:
  build-assets:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npm ci
      - run: |
          for f in raw/*.glb; do
            ./pipeline.sh "$f" "public/assets/$(basename $f)"
          done
      - uses: actions/upload-artifact@v4
        with:
          name: optimized-assets
          path: public/assets/

4. Loading strategy

Your app now:

  • Has DRACOLoader, KTX2Loader, MeshoptDecoder wired (S6-01).
  • Uses LoadingManager for progress (S6-09).
  • Streams near-player, unloads far (S6-09).
  • Uses addressables instead of hardcoded URLs (S6-09).

5. A representative win

StepSize% of raw
Raw Blender export14.5 MB100%
+ validate (no change)14.5 MB100%
+ optimize (prune, dedup, weld)11.2 MB77%
+ resize 8K → 2K textures6.1 MB42%
+ meshopt geometry4.3 MB30%
+ UASTC normals2.8 MB19%
+ ETC1S base/rough/metal1.4 MB10%

10× smaller. Same visual output. That's a 2-second load become 0.2-second.

6. Common pipeline mistakes

  • Skipping validate. Compression amplifies broken files.
  • UASTC on ALL textures. Tripled size vs ETC1S where the quality isn't needed.
  • Running both Draco + Meshopt. Pick one per file (they're alternative encodings).
  • Shipping 8K textures. Mobile: 2K max. Desktop: 4K max.
  • Not caching. Every reload re-fetches. Use HTTP cache headers + THREE.Cache.enabled = true.

7. The monitoring loop

Ship with Lighthouse + RUM:

  • Time to Largest Contentful Paint.
  • Total transfer size.
  • GPU memory used.
  • Frames dropped during asset load.

Iterate pipeline settings based on real user data.

8. Season 6 recap

glTF internals. Draco. KTX2. Meshopt. gltf-transform. LOD tools. Blender export. Atlasing. Streaming. Full pipeline.

Ten articles covering the boring half of shipping 3D apps. The half that decides whether your app loads at all.

Season 7 next: UI & UX in 3D. HUDs. Worldspace UI. 3D menus. Gestures. Accessibility. The human interface to the render.

9. Takeaways

  • Pipeline is 8 deterministic steps: validate, inspect, optimize, resize, compress-geo, compress-tex, ship.
  • Automate in a shell script, run in CI.
  • 10× size reduction with zero visible quality loss.
  • Always monitor real-world load times.
  • Iterate pipeline settings per asset.

Concept article — the pipeline is an offline script. All intermediate demos are in S6-01 through S6-09.