
GLB Compression for Three.js Projects
Compressing GLB files is crucial for optimizing 3D assets in web-based Three.js projects. Large models can slow down load times and impact performance, especially on mobile devices. In this post, we'll explore how to compress GLB files using the gltf-transform library with Draco compression for vanilla Three.js projects. We'll also cover an alternative approach for React Three Fiber users using the gltfjsx tool with its --transform flag, which automates compression and generates reusable JSX components.
The goal is to help you reduce file sizes by up to 70%-90% while maintaining visual quality, making your 3D scenes faster and more efficient. Let's dive in!
Why Compress GLB Files?
GLB files (the binary format of GLTF) are widely used in Three.js for 3D models. However, they can be large due to complex geometries, high-resolution textures, and redundant nodes. Compression addresses these issues by:
- Reducing geometry size with Draco compression.
- Optimizing textures by resizing and converting to efficient formats like WebP.
- Pruning unnecessary nodes to improve rendering performance.
- Deduplicating resources to eliminate redundant data.
This post provides two approaches: a Node.js script for vanilla Three.js users and the gltfjsx tool for React Three Fiber users. You can skip ahead to the section that suits your needs:
Vanilla Three.js: GLB Compression Script
For vanilla Three.js projects, you can use a Node.js script leveraging the gltf-transform library to compress GLB files. This script processes all .glb files in a specified folder, applying Draco compression, texture optimization, and pruning.
Setup
- Create a project directory and initialize a Node.js project:
npm init -y - Install dependencies:
npm install @gltf-transform/core @gltf-transform/extensions @gltf-transform/functions draco3dgltf sharp - Create a
glb-filesfolder in your project directory and place your.glbfiles there. - Add the compression script (see implementation below).
Implementation
Here's the script to compress GLB files:
import fs from "fs";
import path from "path";
import sharp from "sharp";
import draco3d from "draco3dgltf";
import { NodeIO } from "@gltf-transform/core";
import { ALL_EXTENSIONS } from "@gltf-transform/extensions";
import {
dedup,
draco,
prune,
resample,
textureCompress,
} from "@gltf-transform/functions";
// === CONFIGURATION ===
const FOLDER_PATH = "./glb-files"; // folder with your .glb files
const OUTPUT_SUFFIX = ".compressed.glb";
async function compressGLB(filePath) {
const encoder = await draco3d.createEncoderModule();
const decoder = await draco3d.createDecoderModule();
const io = new NodeIO()
.registerExtensions(ALL_EXTENSIONS)
.registerDependencies({
"draco3d.encoder": encoder,
"draco3d.decoder": decoder,
});
console.log(`Processing: ${filePath}`);
const document = await io.read(filePath);
await document.transform(
resample(), // Optimizes animations
prune(), // Removes unused nodes
dedup(), // Removes duplicate resources
draco(), // Applies Draco compression
textureCompress({
encoder: sharp,
targetFormat: "webp",
resize: [1024, 1024], // Resize textures to 1024x1024
})
);
const parsed = path.parse(filePath);
const outputPath = path.join(parsed.dir, `${parsed.name}${OUTPUT_SUFFIX}`);
await io.write(outputPath, document);
console.log(`Saved: ${outputPath}`);
}
async function run() {
const files = fs.readdirSync(FOLDER_PATH);
const glbFiles = files.filter((file) => file.toLowerCase().endsWith(".glb"));
for (const file of glbFiles) {
const fullPath = path.join(FOLDER_PATH, file);
await compressGLB(fullPath);
}
console.log("Compression complete.");
}
run().catch((err) => console.error(err));
How It Works
- Line 1-7: Imports - The script imports Node.js modules (
fs,path),sharpfor texture processing,draco3dgltffor Draco compression, andgltf-transformmodules for GLTF manipulation. - Line 12-13: Configuration - Defines the input folder (
glb-files) and output suffix (.compressed.glb). - Line 15-35:
compressGLBFunction - Processes a single GLB file:- Initializes Draco encoder/decoder for geometry compression.
- Uses
NodeIOto read the GLB file and apply transformations:resample(): Optimizes animations by reducing keyframes.prune(): Removes unused nodes and groups.dedup(): Eliminates duplicate resources (e.g., identical textures).draco(): Applies Draco compression to geometry.textureCompress(): Converts textures to WebP and resizes to 1024x1024.
- Saves the compressed file with the
.compressed.glbsuffix.
- Line 37-47:
runFunction - Scans theglb-filesfolder, filters for.glbfiles, and processes each one.
Usage
- Place your
.glbfiles in theglb-filesfolder. - Run the script:
node index.js - Find compressed files in the
glb-filesfolder (e.g.,model.compressed.glb). - Use the compressed
.glbfiles in your Three.js project by loading them withGLTFLoader.
Package.json
{
"name": "glb-compressor",
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"@gltf-transform/core": "^3.10.0",
"@gltf-transform/extensions": "^3.10.0",
"@gltf-transform/functions": "^3.10.0",
"draco3dgltf": "^1.5.7",
"sharp": "^0.33.2"
}
}
Notes
- Ensure Node.js is installed.
- The compressed
.glbfiles are optimized for web use and can be loaded in Three.js usingGLTFLoader. - For Draco-compressed models, include the Draco decoder in your Three.js project (e.g., via CDN:
https://www.gstatic.com/draco/v1/decoders/).
React Three Fiber: Using gltfjsx --transform
For React Three Fiber users, the gltfjsx tool provides a powerful way to compress GLB files and generate reusable JSX components simultaneously. The --transform flag automates compression tasks similar to the script above.
Why Use gltfjsx?
The default GLTF workflow in Three.js has limitations:
- Models are loaded as a single object, preventing reuse.
- Traversing the scene graph is slow and cumbersome.
- Compression is complex without specialized tools.
gltfjsx addresses these by:
- Creating a JSX component with a virtual graph of nodes and materials.
- Pruning unnecessary nodes for better performance.
- Compressing models (up to 70%-90% size reduction) with Draco, texture resizing, and WebP conversion.
Setup
- Ensure your project uses
@react-three/fiber(>= 5.x) and@react-three/drei(>= 2.x). - Place your
.glbfile in the/publicfolder of your project.
Usage
Run the following command to process your GLB file:
npx gltfjsx public/models/model.glb --transform
This generates:
- A
Model.jsxfile with a React component. - A compressed
model-transformed.glbfile in the/publicfolder.
Example output (Model.jsx):
import { useGLTF, PerspectiveCamera } from "@react-three/drei";
export function Model(props) {
const { nodes, materials } = useGLTF("/model-transformed.glb");
return (
<group {...props} dispose={null}>
<PerspectiveCamera
name="camera"
fov={40}
near={10}
far={1000}
position={[10, 0, 50]}
/>
<pointLight
intensity={10}
position={[100, 50, 100]}
rotation={[-Math.PI / 2, 0, 0]}
/>
<group position={[10, -5, 0]}>
<mesh geometry={nodes.robot.geometry} material={materials.metal} />
<mesh geometry={nodes.rocket.geometry} material={materials.wood} />
</group>
</group>
);
}
useGLTF.preload("/model-transformed.glb");
Using the Component
import { Canvas } from "@react-three/fiber";
import { Model } from "./Model";
function App() {
return (
<Canvas>
<Model position={[0, 0, 0]} />
<Model position={[10, 0, -10]} />
</Canvas>
);
}
Benefits
- Reuse: The component can be reused multiple times with minimal draw calls.
- Dynamic Modifications: Easily change materials, add events, or make parts conditional:
<mesh geometry={nodes.robot.geometry} material={materials.metal} material-color="green" onClick={handleClick} /> - Compression: The
--transformflag applies Draco compression, texture resizing (default: 1024x1024), and WebP conversion, reducing file size significantly.
Additional Options
--types: Adds TypeScript definitions for type safety.--instance: Instances recurring geometry for fewer draw calls.--draco: Uses local Draco binaries (place in/public).--resolution: Adjusts texture resolution (e.g.,--resolution 512).
Read more on gltfjsx GitHub.
Conclusion
Compressing GLB files is essential for performant Three.js applications. For vanilla Three.js, the gltf-transform script provides a flexible way to optimize models with Draco compression and texture resizing. For React Three Fiber users, gltfjsx with the --transform flag simplifies the process by combining compression with JSX component generation, enabling reusable and dynamic 3D components.
Try both approaches based on your project needs, and always test the compressed models to ensure visual quality meets your requirements. For more advanced optimizations, explore additional gltfjsx flags like --instance or --types.
