If you have ever shipped a three.js scene with thousands of animated objects, you know where the frame budget goes: JavaScript object updates, matrix math, and the cost of handing draw calls to the GPU. WAMR3D is a new open-source attempt at that problem. It moves the scene graph, transforms, culling, and draw ordering into a Rust core compiled to WebAssembly, pairs it with a WebGPU renderer written from scratch, and keeps an API that reads like three.js.
Here is what it is, what it measures, and where it still falls short.
- Website: wamr3d.org
- npm: wamr3d
- GitHub: aymen94/wamr3d
What is WAMR3D?
WAMR3D is a WebAssembly 3D rendering engine with a Rust core and a separate WebGPU renderer. It is not a wrapper around three.js. It is also not a finished replacement for it. The project is young, and the README says so directly.
Three parts make it up:
- A native core in WebAssembly. The scene graph, world matrices, frustum culling, draw ordering, and raycasting run inside a Wasm module.
- Its own WebGPU renderer. Meshes, lines, points, sprites, and instanced meshes. Basic, Lambert, and standard materials. Colour textures, fog, up to 16 lights, PCF shadows, tone mapping, render targets, and 4× MSAA.
-
A three.js-style object API under
wamr3d/compat, plus a lower-level batched API for large scenes.
The license is MIT. There are no runtime dependencies, and the Wasm module is about 488 KiB.
Why a native core matters for three.js developers
In most three.js apps, scene state lives in JavaScript objects. Every position.set(), rotation.y +=, and matrix update is JavaScript work, and the garbage collector eventually gets involved.
WAMR3D takes a different approach:
- The scene lives in engine memory. Objects are created and destroyed explicitly in WebAssembly, so the engine itself adds no GC pauses.
- Generational handles. A handle to a destroyed object can never silently point at a different object.
- Zero-copy geometry. You can read and write vertex attributes through typed-array views directly over engine memory.
- Batched sync. In the object API, position, rotation, and scale stay in JavaScript, and every change is sent to the engine in a single batch per frame.
The result is that WAMR3D wins when you batch work, and loses when you make many tiny calls. The project documents both.
Measured performance
These are the numbers from the repo's benchmark record, recorded on 2026-10-14 in headless Chrome on Windows x64 with an RTX 2070 SUPER. They are medians from a single run per resolution on one machine. Treat them as a starting point, and measure your own scene.
10,000 separate animated meshes at 1920×1080:
| Engine | fps | Draw submission (CPU) |
|---|---|---|
| WAMR3D, batched API | 60.0 | 12.2 ms |
| WAMR3D, object API | 60.0 | 12.3 ms |
| three.js WebGPU | 9.5 | 100.5 ms |
| three.js WebGL | 33.7 | 23.7 ms |
50,000 instanced bodies, scene update on the CPU: 12.0 ms for WAMR3D's batched API, against 39.1 ms for three.js WebGPU and 39.7 ms for three.js WebGL.
Put simply: in this showcase, WAMR3D spends roughly 8× less CPU time on draw submission than three.js WebGPU, and about 1.9× less than three.js WebGL. The frame rate caps at 60 because the showcase has a display limit.
The project also publishes the other side of the picture. Calls that do very little work, such as a single vector operation or one property at a time, measured 1.3× to 3.8× slower than three.js, because crossing into WebAssembly has a fixed cost. GPU time is not measured separately; it only shows up in the frame rate.
If you want to check these yourself, the showcase page runs all four renderers live: wamr3d.org/examples/showcase.
Same results as three.js, checked by tests
Speed means nothing if the output is wrong. WAMR3D replays over 7,600 reference cases generated from the three.js r186 source against the Rust code. Individual operations agree to about one part in a million. All 23 renderer test cases are compared pixel by pixel against stored three.js images.
The repo also includes 643 Rust tests covering unit, parity, and allocation behaviour, plus a visual parity page that shows each WAMR3D render next to the three.js image and the pixel difference.
Get started in two minutes
npm install wamr3d
Change the import, initialize once, and switch to the WebGPU renderer:
- import * as THREE from 'three';
+ import * as THREE from 'wamr3d/compat';
+ await THREE.init();
- const renderer = new THREE.WebGLRenderer( { antialias: true } );
+ const renderer = new THREE.WebGPURenderer( { antialias: true } );
+ await renderer.init();
A complete example:
import * as WAMR3D from 'wamr3d/compat';
await WAMR3D.init();
const scene = new WAMR3D.Scene();
const camera = new WAMR3D.PerspectiveCamera(50, innerWidth / innerHeight, 0.1, 100);
camera.position.set(3, 2, 5);
camera.lookAt(0, 0, 0);
const cube = new WAMR3D.Mesh(
new WAMR3D.BoxGeometry(),
new WAMR3D.MeshLambertMaterial({ color: 0x4f8cff })
);
scene.add(cube, new WAMR3D.DirectionalLight(0xffffff, 2));
const renderer = new WAMR3D.WebGPURenderer({ antialias: true });
await renderer.init();
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
renderer.setAnimationLoop((time) => {
cube.rotation.y = time / 1000;
renderer.render(scene, camera);
});
scene.add, position.set, rotation.y +=, lookAt, and raycasting work as written for the classes WAMR3D supports. The project's migration guide lists exactly which classes exist and what to watch for.
Requirements: a browser with WebGPU (current Chrome or Edge), served over HTTP rather than file://. Node.js 18 or newer can run the math and scene graph without a renderer.
Batching for large scenes
When your scene grows past a few hundred objects, the batched API is where the gains come from. Handles and calls like setRotations update thousands of objects in one crossing into WebAssembly. You read world matrices through typed-array views without copying them.
The low-level API is also exported directly from wamr3d:
import { init, World } from 'wamr3d';
await init();
const world = new World();
const scene = world.createObject();
const arm = world.createObject();
const hand = world.createObject();
world.add(scene, arm);
world.add(arm, hand);
world.setPosition(arm, 10, 0, 0);
world.setRotation(arm, 0, 0, Math.PI / 2, 'XYZ');
world.setPosition(hand, 1, 0, 0);
world.updateMatrixWorld(scene, false);
console.log(world.matrixWorld(hand)); // Float32Array(16), column-major
Two details to know: matrices are column-major, and the low-level API has no default arguments, so rotation order and similar parameters are always written out.
What is not there yet
This section matters most, so here it is in full. From the project's own status page, the following are not available yet:
- Environment reflections (not connected to
scene.environmentorenvMap) - Point-light shadows and area lights
- Post-processing and scene background textures
- Most loaders, animation, audio, and controls (the exception is
TextureLoader) - Shadow modes other than PCF
- A WebGL fallback
It has also been verified only in Node.js v24 and headless Chrome. Firefox, Safari, and bundler setups have not been tried yet.
For those features, keep three.js for now.
Who this is for
WAMR3D is a good fit if you:
- Have a three.js scene with many objects, and the bottleneck is JavaScript updates or draw submission
- Are experimenting with WebGPU and want a renderer that is not a thin wrapper
- Care about deterministic, checked behaviour, and want to see the parity tests
- Want to contribute to a Rust and WebAssembly graphics engine
It is probably not the right choice yet if you depend on post-processing, environment maps, loaders, animation, or Firefox and Safari support.
Try it, measure it, tell me what breaks
The fastest way to judge WAMR3D is to run it on your own scene:
- Live demos: WebGPU renderer, showcase, side-by-side comparison
- Migration guide: MIGRATION.md
- Status and benchmarks: STATUS.md
- Design decisions: DECISIONS.md
The project has one star and no forks so far, so early feedback will shape it a lot. Issues, benchmark results from other GPUs, and reports from Firefox or Safari are all useful. If you find a case where it is slower than three.js, please open an issue. The project explicitly values those results.
Star it on GitHub, and try npm install wamr3d on a scene you already know.
WAMR3D is an independent project, not affiliated with three.js. It is inspired by three.js and credits it throughout the repository.
Top comments (0)