docs
Getting Started
Tutorials
How-To Guides
Explanation
Reference
Help
Getting Started
Tutorials
How-To Guides
Explanation
Reference
Help

gltf-progressive

Blazingly fast loading for glTF, GLB, and VRM files with smart density-based LOD selection for meshes and textures.

@needle-tools/gltf-progressive ships as part of Needle Engine and powers its progressive loading pipeline out of the box. In a Needle Engine project, progressive meshes and textures are generated automatically during production builds — no extra setup required.

The package is also available as a standalone npm module for any three.js-based project. Whether you're using vanilla three.js, React Three Fiber, or <model-viewer>, you can add progressive loading with a single line of code.

Quick Links

Needle Engine: Progressive loading is built in — see Optimization & Compression

Standalone: npm i @needle-tools/gltf-progressive

Generate Assets: Needle Cloud or Needle Engine integrations

Live Demos: three.js | React Three Fiber | model-viewer | Comparison


Why Progressive Loading?

Traditional 3D optimization with tools like gltf-transform produces a single optimized file. It's compressed and smaller — but users still have to download the entire file before seeing anything. Every mesh and every texture at full resolution, whether visible or not.

gltf-progressive takes a fundamentally different approach. Instead of one big file, it creates a tiny initial file with embedded low-quality proxies plus a set of higher-quality LOD files that stream in on demand. The result:

Standard gltf-transformgltf-progressive
Initial displayWait for full downloadInstant — proxy geometry renders immediately
What gets loadedEverything, alwaysOnly the detail needed for the current view
Quality over timeAll-or-nothingProgressive refinement as LODs stream in
Bandwidth usageFull file regardless of viewportAdapts to what's on screen
Mobile / slow networksSame payload as desktopAutomatic quality reduction
CachingFile-level onlyPer-LOD caching with content hashing
Typical initial savings—~90% smaller initial download

A 56 MB asset can be reduced to a 300 KB initial download with up to 8 MB of progressive streaming — and most users will never need the full 8 MB because they don't need 4K textures until they zoom in close.

What This Unlocks

Progressive loading isn't just an optimization — it changes what's possible on the web:

  • Massive scenes become viable. Environments with hundreds of high-poly objects load in seconds instead of minutes. Only nearby objects get full detail; distant ones stay lightweight.
  • E-commerce at any scale. Product configurators with dozens of variants don't need to preload everything. Each option streams in at the quality the viewer needs.
  • Mobile-first 3D. Instead of choosing between "looks good" and "loads on mobile," you get both. The same asset adapts automatically to the device and network.
  • Instant previews everywhere. Embed 3D content on landing pages, in emails, or in social cards without worrying about load time. The proxy loads as fast as an image.
  • Real-time collaboration on heavy assets. Multiple users viewing the same scene each load only the LODs relevant to their camera — no server-side rendering needed.

Features

  • Single-line integration for any three.js project
  • Mesh LODs — loaded lazily based on screen density, not just distance
  • Texture LODs — progressive texture streaming from low-res to full quality
  • Smart density selection — consistent, predictable quality across screen sizes
  • Mobile optimization — automatic quality reduction and data-saving support
  • Compression support — automatically handles KTX2, WebP, Draco, and Meshopt
  • Fast raycasting — uses low-poly LOD meshes for smooth interactions with high-poly models
  • Plugin system — extensible LOD event hooks for custom behavior
  • Asset generation via Needle Cloud with global CDN, versioning, and optimization

How It Works

Mesh LODs

The system generates up to 6 mesh LOD levels using progressive simplification. Each level is roughly half the triangle count of the previous one, with the lowest quality embedded directly in the main file for instant display. Higher-quality levels are stored as separate files and streamed in as needed.

At runtime, the LOD manager selects which level to show based on screen-space density — how many triangles per pixel the mesh would have at its current screen size. A mesh filling the screen gets full detail; the same mesh as a small element in the background gets a much simpler version. This density-based approach ensures consistent visual quality regardless of camera distance or screen resolution.

Default target: 200,000 triangles on screen when a mesh fills the view.

Texture LODs

Textures are handled similarly. The main file embeds a small preview (128px by default), while full-resolution versions stream in progressively. Each texture LOD is half the resolution of the previous one, and the runtime selects the appropriate level based on how many pixels the texture actually covers on screen.

On mobile devices, 8K textures are automatically skipped. When the browser's data-saving mode is active, textures above 2K are skipped as well.

Streaming

  1. The main glTF loads with embedded low-quality proxies — your scene appears immediately
  2. The runtime evaluates what's visible and at what screen size
  3. Higher-quality LOD files are fetched on demand as the camera moves
  4. Geometry and textures are swapped in seamlessly — no visual disruption
  5. Previously loaded LODs are cached and reused

Compression Methods

gltf-progressive applies state-of-the-art compression to every LOD level automatically. Understanding which formats are used helps you make informed decisions about quality vs. file size.

Texture Compression

FormatBest ForGPU MemoryFile SizeQuality
ETC1S (KTX2)Color textures, UI, albedo mapsLowLowMedium
UASTC (KTX2)Normal maps, metallic/roughness, detail texturesLowHigherVery high
WebPPhotographic content where ETC1S quality isn't sufficientHigh (uncompressed in GPU)Very lowConfigurable

How formats are chosen automatically:

  • Color textures (base color, emissive, occlusion) → ETC1S for the best balance of size and quality
  • Data textures (normal maps, metallic/roughness) → UASTC to preserve the precision these textures need
  • Product visualization → WebP or UASTC for maximum visual fidelity

ETC1S vs UASTC

Both are GPU-compressed formats (low GPU memory), but they serve different purposes. ETC1S is much smaller on disk and great for color textures where minor quality loss is acceptable. UASTC preserves more detail and is essential for normal maps and PBR data textures where compression artifacts would be visible.

Mesh Compression

FormatBest ForFile SizeMorph Targets & Animation Data
DracoStatic meshesSmallestNot supported — Draco compresses mesh geometry only
MeshoptAnimated meshes, blend shapesSmallSupported — preserves morph targets and animation data alongside mesh compression

How mesh compression is chosen:

  • Static meshes (no blend shapes) → Draco for maximum geometry compression (~20x reduction)
  • Meshes with blend shapes or morph targets → Meshopt, which can compress mesh geometry while preserving morph target and animation data that Draco would discard
  • Each LOD level gets the same compression as the original, ensuring consistency

Optimization Profiles

When generating assets through Needle Cloud or Needle Engine builds, two optimization profiles are available:

WorldProduct
Use caseEnvironments, scenes, gamesProduct visualization, e-commerce
Mesh LOD levels63
Min texture size128px512px
Texture compressionETC1S for colors, UASTC for dataUASTC / WebP for max fidelity
PriorityFast load, low bandwidthVisual quality

Generating Progressive Assets

Progressive loading requires assets that have been processed to include LOD data.

Needle Engine (Automatic)

If you're using Needle Engine, progressive assets are generated automatically. When you make a production build from Unity or Blender, the build pipeline generates all mesh and texture LODs, applies compression, and outputs progressive glTF files — ready to deploy.

No manual steps are needed. You can configure the compression settings and LOD generation per project or per asset (see below).

Needle Cloud

For standalone three.js projects or external assets, upload any glTF, GLB, VRM, FBX, USD, or OBJ file to Needle Cloud. It automatically generates progressive mesh and texture LODs, applies compression, and serves assets via a global CDN.

Use the Progressive-World or Progressive-Product download link to get a URL ready for progressive loading.

See Needle Cloud documentation for uploading, versioning, and sharing.

Configuration: Unity

Progressive LODs are generated automatically during production builds. Use the Compression and LOD Settings component to configure the behavior.

Add the component: Add Component > Needle Engine > Optimization > Compression and LOD Settings

Texture settings:

  • Texture Format — Choose between Automatic, ETC1S, UASTC, WebP, or per-use-case modes (World, Product)
  • Max Texture Size — Maximum resolution (default: 8192)
  • Generate Texture LODs — Enable/disable progressive texture loading (default: on)
  • LODs Max Size — Max resolution for the initially loaded texture (default: 128px)
  • Per-texture overrides — Override compression format, max size, or LOD generation for individual textures

Mesh settings:

  • Mesh Compression — Choose between Draco, Meshopt, Automatic, or None
  • Generate Mesh LODs — Enable/disable progressive mesh loading (default: on)
  • Per-mesh overrides — Override LOD generation for individual meshes
Build and compression actions

You can also trigger compression manually from the Unity menu:

  • Needle Engine > Compression > Run Full Compression — Compression + LOD generation
  • Needle Engine > Compression > Run LODs Generator — LOD generation only
  • Needle Engine > Compression > Run Compression — Compression only

Configuration: Blender

In Blender, progressive loading settings are part of the main Needle Engine scene settings panel:

  • Use Progressive — Enable progressive texture loading (default: on)
  • Progressive Texture Size — Preview size for initial texture load (default: 128px, options: 32–2048)
  • Use Progressive Meshes — Enable progressive mesh loading (default: on)
  • Compress On Save — Generate compressed and progressive assets after export to preview production quality locally

Standalone Usage

While gltf-progressive is built into Needle Engine, it can also be used as a standalone package in any three.js project.

Installation

npm i @needle-tools/gltf-progressive

Peer dependency: three.js >= 0.160.0

three.js

Add progressive loading to any three.js project with a single function call.

import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { useNeedleProgressive } from "@needle-tools/gltf-progressive";

const gltfLoader = new GLTFLoader();
const renderer = new WebGLRenderer();

// Register the progressive loader plugin — call once before loading
useNeedleProgressive(gltfLoader, renderer);

// Load models as usual — progressive loading happens automatically
const url = "https://cloud.needle.tools/-/assets/Z23hmXBZN45qJ-ZN45qJ-world/file";
gltfLoader.load(url, (gltf) => {
    scene.add(gltf.scene);
});

When using import maps in HTML:

<script type="importmap">
{
    "imports": {
        "three": "https://cdn.jsdelivr.net/npm/three@latest/build/three.module.js",
        "three/addons/": "https://cdn.jsdelivr.net/npm/three@latest/examples/jsm/",
        "@needle-tools/gltf-progressive": "https://cdn.jsdelivr.net/npm/@needle-tools/gltf-progressive/gltf-progressive.min.js"
    }
}
</script>

React Three Fiber

import { useGLTF } from "@react-three/drei";
import { useThree } from "@react-three/fiber";
import { useNeedleProgressive } from "@needle-tools/gltf-progressive";

function MyModel() {
    const { gl } = useThree();
    const url = "https://cloud.needle.tools/-/assets/Z23hmXBZN45qJ-ZN45qJ-world/file";
    const { scene } = useGLTF(url, false, false, (loader) => {
        useNeedleProgressive(loader, gl);
    });
    return <primitive object={scene} />;
}

model-viewer

For Google's <model-viewer> web component, just include the script — no code changes needed. The plugin automatically patches model-viewer to support progressive loading.

<head>
    <script type="importmap">
    {
        "imports": {
            "three": "https://cdn.jsdelivr.net/npm/three/build/three.module.js",
            "three/": "https://cdn.jsdelivr.net/npm/three/"
        }
    }
    </script>

    <!-- Include gltf-progressive before model-viewer -->
    <script type="module"
        src="https://cdn.jsdelivr.net/npm/@needle-tools/gltf-progressive@latest/gltf-progressive.min.js">
    </script>

    <!-- Standard model-viewer -->
    <script type="module"
        src="https://ajax.googleapis.com/ajax/libs/model-viewer/4.0.0/model-viewer.min.js">
    </script>
</head>
<body>
    <model-viewer
        src="https://cloud.needle.tools/-/assets/Z23hmXBZN45qJ-ZN45qJ-world/file"
        camera-controls
        auto-rotate>
    </model-viewer>
</body>

Needle Engine (Built In)

gltf-progressive ships with Needle Engine — progressive loading works out of the box with zero configuration. When you make a production build, progressive meshes and textures are generated automatically. The runtime LOD manager is already integrated into the rendering pipeline, so your scenes benefit from density-based LOD selection, progressive texture streaming, and optimized raycasting without any additional setup.

See Optimization & Compression to configure compression formats and LOD generation settings in Unity and Blender.


Runtime Configuration

LOD Manager Settings

Access the LOD manager to fine-tune progressive loading at runtime:

import { LODsManager } from "@needle-tools/gltf-progressive";

const lodsManager = LODsManager.get(renderer);
SettingDefaultDescription
targetTriangleDensity200000Target max triangles on screen when a mesh fills the view. Increase for higher quality, decrease for better performance.
updateInterval"auto"How often LODs are recalculated (in frames). 0 = every frame, 2 = every other frame. "auto" adapts to framerate.
skinnedMeshAutoUpdateBoundsInterval30How often skinned mesh bounds are recalculated (in frames). Higher values improve performance for animated models.
pausefalseTemporarily stop LOD updates.
manualfalseDisable automatic updates. Use lodsManager.update(scene, camera) to trigger manually.
overrideLodLevelundefinedForce a specific LOD level (0–6) for all meshes. Set to undefined to disable.
// Example: Higher quality, less frequent updates
const lodsManager = LODsManager.get(renderer);
lodsManager.targetTriangleDensity = 300000;
lodsManager.updateInterval = 2;

Raycasting Optimization

Enable low-poly meshes for raycasting to get faster, smoother interactions with high-poly models:

import { useRaycastMeshes, getRaycastMesh } from "@needle-tools/gltf-progressive";

// Enable globally — call once
useRaycastMeshes(true);

// Raycasting now automatically uses low-poly meshes
const raycaster = new Raycaster();
raycaster.setFromCamera(mouse, camera);
const hits = raycaster.intersectObjects(scene.children, true);

// Get a low-poly mesh for physics or custom use
const lowPolyGeometry = getRaycastMesh(myMesh);

Wait for LODs

Wait for all in-flight LODs to finish loading — useful for screenshots or transitions:

const result = await lodsManager.awaitLoading({
    frames: 3,                // Wait for LODs triggered over 3 frames
    waitForFirstCapture: true // Wait for initial LOD registration
});

Debug Mode

Add ?debugprogressive to your URL to enable debug logging and keyboard shortcuts:

KeyAction
PCycle through LOD levels
WToggle wireframe view
0–6Force specific LOD level

To disable progressive loading entirely, add ?noprogressive to your URL.


Plugin System

Extend the LOD manager with custom plugins to react to LOD changes:

import { LODsManager, NEEDLE_progressive_plugin } from "@needle-tools/gltf-progressive";

class MyPlugin implements NEEDLE_progressive_plugin {
    onBeforeUpdateLOD(renderer, scene, camera) {
        // Called before LOD calculation each frame
    }
    onAfterUpdatedLOD(renderer, scene, camera, mesh, levels) {
        // Called after LOD levels are determined
        console.log(`${mesh.name}: mesh LOD ${levels.mesh_lod}, texture LOD ${levels.texture_lod}`);
    }
    onRegisteredNewMesh(mesh) {
        // Called when LOD info is registered for a new mesh
    }
    onBeforeGetLODMesh(mesh) {
        // Called before a mesh LOD is fetched
    }
}

LODsManager.addPlugin(new MyPlugin());

Live Examples

  • Loading Comparison — Side-by-side with standard three.js loading
  • Vanilla three.js — Multiple models and animations
  • React Three Fiber — R3F integration
  • model-viewer (single) — Single element
  • model-viewer (multiple) — Multiple elements on one page
  • Needle Engine — Needle Engine integration
  • Needle Cloud Viewer — View progressive assets on Needle Cloud

Related Documentation

  • Optimization & Compression — Texture/mesh compression, build types, and optimization best practices
  • Needle Cloud — Upload, optimize, and host progressive assets
  • Export Guide — Best practices for exporting 3D assets
  • npm package — Full API reference and changelog
  • GitHub — Source code and issue tracker
Suggest changes
Last Updated: 2/11/26, 9:47 PM

Extras

ChatGPT Ask ChatGPT Claude Ask Claude
Copy Markdown

Navigation

  • Getting Started
  • Tutorials
  • How-To Guides
  • Explanation
  • Reference
  • Help

Extras

ChatGPT Ask ChatGPT Claude Ask Claude
Copy Markdown