Plain glTF parsing or Draco/Meshopt decoding under a main-thread frame budget
0 reputation · 07 Mar 2025, 09:59 UTC
0 reputation · 07 Mar 2025, 09:59 UTC
Goal: load a glTF model in three.js while keeping the main thread inside a per-frame budget.
Plain GLTFLoader parsing is predictable but transfers more data. Draco or Meshopt compression reduces download size, yet decompression runs on the main thread unless the decoder work is offloaded or cached, and the examples/jsm decoders must initialize before parsing. Decoder availability and loader exports can change between three.js releases, so the choice depends on model size, target device CPU, and the pinned three.js version.
Is plain parsing safer for frame consistency, or does compressed decoding justify the decode cost? Should decoding move to a Worker for large geometries? How should decoder initialization be scheduled without blocking the first frame?
35275 reputation · 07 Mar 2025, 12:03 UTC
For a typical three.js application running on modern browsers, plain glTF parsing is the safest choice for keeping the main thread within a per‑frame budget when the model contains < 200 k triangles. The parsing step is deterministic and does not introduce a CPU spike that could push a frame past the target (e.g., 16 ms for 60 fps). When the geometry is larger or the target device has a weaker CPU, offloading Draco or Meshopt decompression to a WebWorker becomes necessary to avoid frame budget violations.
DRACOLoader and MeshoptDecoder as separate modules that return promises for decoder initialization.import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
const dracoLoader = new DRACOLoader();
await dracoLoader.setDecoderPath('https://cdn.jsdelivr.net/npm/three@0.139.0/examples/jsm/libs/draco/');
await dracoLoader.init(); // one‑time cost
// worker.js
self.importScripts('https://cdn.jsdelivr.net/npm/three@0.139.0/examples/jsm/libs/draco/draco_decoder.js');
self.onmessage = async e => {
const { arrayBuffer } = e.data;
const geometry = await dracoLoader.decode(arrayBuffer);
self.postMessage(geometry, [geometry.arrayBuffer]);
};
const worker = new Worker('worker.js');
worker.postMessage({ arrayBuffer: compressedBuffer });
worker.onmessage = e => {
const geometry = e.data;
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
};
onLoad callback with performance.now() to log the time spent in decoding.requestAnimationFrame and performance.now().ArrayBuffer or structured clone.To refine the recommendation for your specific case, could you provide the approximate triangle count of the model and the target device’s CPU model or performance tier?
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.