@needle-tools/three-mesh-effects
v0.2.3
Published
Three.js loaders and TSL effects for Needle Mesh Baker outputs.
Downloads
2,013
Readme
Three.js loaders and TSL materials for GLBs exported by Needle Mesh Baker.
Install
npm install @needle-tools/three-mesh-effects threeThe package supports Three.js r185 and r186. Its Needle Engine adapter is an
optional import. The examples use ordinary Three.js and assume an existing
renderer, scene, and camera.
Features
- Hull effects: Restore the saved effect automatically, optionally binding its source model.
- Octahedral impostors: Load exported glTF extensions with a Three.js TSL material.
- Deformation rigs: Run IK and secondary motion alongside standard animation clips.
- Pixel and voxel impostors: Create the display mode selected in the workbench.
Quick start
Register once, then load any Mesh Baker glTF without knowing its extensions:
import { useLoader } from '@needle-tools/three-mesh-effects';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const loader = useLoader(new GLTFLoader(), renderer);
const { scene: model } = await loader.loadAsync('/model.glb');
scene.add(model);This handles hull effects, deformation rigs, octahedral impostors, and pixel impostor atlas
bindings, including assets combining extensions. It also works with load,
parse, and parseAsync. Each handler is dynamically imported only for assets
declaring that extension; bundlers can keep those imports in separate chunks.
Dynamic imports defer downloads, rather than removing handlers that an asset
might need. The existing individual registration functions remain available.
All extensions are enabled by default. To opt out of specific handlers, pass their glTF extension names in the optional third argument:
useLoader(loader, renderer, { disable: ['NEEDLE_mesh_impostor'] });Supported keys are NEEDLE_hull_effect, NEEDLE_mesh_impostor, NEEDLE_pixel_impostor, and
NEEDLE_deformation_rig. Disabled optional extensions keep their portable
fallback without importing the handler. A disabled extension listed in the
asset's extensionsRequired rejects the load with an error. Calling useLoader
again replaces the renderer and disable list for subsequent parses; omitting
options enables everything again.
If a model uses Draco, Meshopt, or KTX2 compression, you must configure the
corresponding decoder or texture loader on your GLTFLoader yourself.
useLoader handles Needle extensions; it does not configure compression decoders.
Rig animation still needs a per-frame update, pixel display modes remain available
through createPixelImpostorDisplay. Hull effects load alone; optionally use
bindHull to add an original model and animation syncing.
When your loader also supports NEEDLE_progressive, impostor shading and fur
textures can change resolution in place. Material bindings and size-dependent
sampling update when a texture is replaced; fur height keeps its UV channel and
texture transforms. Resize impostor atlases per frame rather than filtering
across neighboring views. Compact shading alpha stores material data and needs
independent channel filtering and lossless PNG encoding. Packed depth/geometry
and sparse pixel-impostor records must retain their authored resolution.
Hull effects
When exporting with a hull effect selected, Mesh Baker saves its mode and
settings in NEEDLE_hull_effect. useLoader restores it without a second model:
import { useLoader } from '@needle-tools/three-mesh-effects';
import { bindHull, getHullEffect } from '@needle-tools/three-mesh-effects/hull-effects';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const loader = useLoader(new GLTFLoader(), renderer);
const { scene: hull } = await loader.loadAsync('/hull.glb');
scene.add(hull);
let effect = getHullEffect(hull);
// Optional: show the original inside the hull and follow its animation.
const { scene: original } = await loader.loadAsync('/original.glb');
effect = bindHull(hull, original, { animate: true });
// Each frame: advance your AnimationMixers, then effect.syncAnimation().
// Call effect.dispose() before removing the hull.bindHull replaces the automatically created effect, preserving its settings.
It borrows the original and leaves the caller's hierarchy and resources intact.
Get the current effect again after rebinding; old bindings no longer control it.
bindHull(hull, null) detaches the original and restores the standalone effect.
getHullEffect(hull) returns undefined for an ordinary or disposed hull.
Standalone animated hulls can follow their own animation; call syncAnimation()
after updating their mixers or deformation rig. TSL effects require
WebGPURenderer from three/webgpu, using either its WebGPU or WebGL2 backend.
The existing manual API remains available for models without the extension.
Here the hull supplies the effect's surface and the original stays visible inside:
Available modes and their defaults are in HULL_EFFECT_MODES and
createHullEffectValues():

import { createHullEffect, createHullEffectValues } from '@needle-tools/three-mesh-effects/hull-effects';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const loader = new GLTFLoader();
const [{ scene: hull }, { scene: original }] = await Promise.all([
loader.loadAsync('/hull.glb'), // Exported by Mesh Baker.
loader.loadAsync('/original.glb'), // The model uploaded to Mesh Baker.
]);
scene.add(hull);
const effect = createHullEffect(hull, original, createHullEffectValues().flames);
// For animated models, advance both mixers and call effect.syncAnimation() each frame.
// Call effect.dispose() at teardown.Hull extension contract
NEEDLE_hull_effect version 1 is stored on the glTF scene containing the hull:
{ "version": 1, "mode": "flames", "values": { "height": 0.51, "speed": 1 } }The name appears in extensionsUsed. Exported hull geometry and standard
materials remain a portable fallback, so it is not marked required. mode must
be a supported HULL_EFFECT_MODES value; omitted controls use their documented
defaults, finite numeric values are clamped to their control ranges. Unknown
versions, unknown modes, and non-finite or non-numeric control values fail
explicitly. The extension stores no source URL and triggers no source download.
Each scene with the extension gets its own runtime; dispose each when unloading.
Octahedral impostors
An 800,000-triangle source represented by a two-triangle impostor:

Register the extension loader before loading an exported impostor GLB:
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { registerNeedleImpostorLoader } from '@needle-tools/three-mesh-effects/impostor-runtime';
const loader = new GLTFLoader();
// Register before loading so the extension can replace the portable fallback.
registerNeedleImpostorLoader(loader, { renderer });
const { scene: model } = await loader.loadAsync('/result_impostor.glb');
scene.add(model);Deformation rigs
For a rigged GLB, register the extension before loading and advance the rig once per frame. Update the animation mixer first:
import { AnimationMixer, Clock } from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { registerNeedleDeformationRigLoader, getDeformationRig } from '@needle-tools/three-mesh-effects/deformation-runtime';
const loader = new GLTFLoader();
// Register before loading so the rig extension is available while parsing.
registerNeedleDeformationRigLoader(loader);
const { scene: riggedModel, animations } = await loader.loadAsync('/rigged.glb');
scene.add(riggedModel);
const clock = new Clock();
const mixer = new AnimationMixer(riggedModel);
if (animations[0]) mixer.clipAction(animations[0]).play();
const rig = getDeformationRig(riggedModel);
renderer.setAnimationLoop(() => {
const dt = clock.getDelta();
mixer.update(dt);
rig?.update(dt);
renderer.render(scene, camera);
});Standard animation clips use an ordinary Three.js AnimationMixer. Update the
mixer before rig.update(deltaSeconds) when also using IK or secondary motion.
The runtime respects the animated bone frame; it does not play the clip itself.
Without procedural rig features, the mixer and standard GLTFLoader are sufficient.
Pixel and voxel impostors
Pixel and voxel impostors have a portable glTF shell. Register the loader and create the display that matches the chosen workbench mode:
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { registerNeedlePixelImpostorLoader, createPixelImpostorDisplay } from '@needle-tools/three-mesh-effects/pixel-impostor-runtime';
const loader = new GLTFLoader();
// Register before loading so the pixel atlas is available while parsing.
registerNeedlePixelImpostorLoader(loader);
const { scene: pixelModel } = await loader.loadAsync('/pixel.glb');
scene.add(pixelModel);
const display = createPixelImpostorDisplay(pixelModel, { renderer, mode: 'screen-pixels', pixelSize: 5 });
// Call display.updateViewport() after a resize and display.dispose() at teardown.Needle Engine and glTF fallbacks
impostor-runtime/needle-engine is a separate optional import that requires
@needle-tools/engine; ordinary Three.js applications never import it.
Each GLB has a portable fallback. Register its extension loader to reproduce the workbench's full result. See the download-time snippet in the workbench for the code and settings specific to an exported model.
Contact
Questions or feedback? Email [email protected].
