π§ͺ A Three.js renderer plugin for AR.js-core: mounts a WebGL canvas, consumes AR marker + camera events, and exposes perβmarker Three.js
Groupanchors for you to attach content.
π§ Defaults replicate classic AR.js axis handling.
π Designed for extensibility, testability (renderer injection), and modern ESM builds.
- Features
- Install / Build
- Quick Start
- Events
- Options
- Camera Projection
- Anchors & Adding Content
- Testing
- CI
- Compatibility
- Roadmap Ideas
- License
- β
Unified handling for
ar:marker, rawar:getMarker, legacyar:markerFound / Updated / Lost - π Automatic AR.js classic axis transform chain (
R_y(Ο) * R_z(Ο) * modelViewMatrix * R_x(Ο/2)) - 𧬠Optional experimental path (
invertModelView,applyAxisFix) - πͺ Lazy anchor creation (create Three.js
Grouponly when a marker first appears) - π Debug helpers: scene & perβanchor
AxesHelper - π§ͺ Test-friendly: inject your own renderer via
rendererFactory - π Dual render triggers:
engine:updateorrequestAnimationFramefallback - π‘ Confidence filtering on marker events
- π§Ή Clean disable/dispose lifecycle
npm run build:viteOutputs:
- ESM:
dist/arjs-plugin-threejs.mjs - CJS:
dist/arjs-plugin-threejs.js - Source maps included
Serve the example (choose one):
# If example has its own dev scripts
cd examples/minimal
npm i
npm run dev
# OR from repo root (so relative dist path works)
npx http-server .
# Open: http://localhost:8080/examples/minimal/import { Engine, webcamPlugin, defaultProfilePlugin } from "ar.js-core";
import { ThreeJSRendererPlugin } from "@AR-js-org/arjs-plugin-threejs";
// 1) Engine & core plugins
const engine = new Engine();
engine.pluginManager.register(defaultProfilePlugin.id, defaultProfilePlugin);
engine.pluginManager.register(webcamPlugin.id, webcamPlugin);
const ctx = engine.getContext();
await engine.pluginManager.enable(defaultProfilePlugin.id, ctx);
await engine.pluginManager.enable(webcamPlugin.id, ctx);
// 2) Artoolkit plugin
const { ArtoolkitPlugin } = await import(
"./vendor/arjs-plugin-artoolkit/arjs-plugin-artoolkit.esm.js"
);
const artoolkit = new ArtoolkitPlugin({
cameraParametersUrl: "/path/to/camera_para.dat",
minConfidence: 0.6,
});
await artoolkit.init(ctx);
await artoolkit.enable();
// 3) Projection
const proj = artoolkit.getProjectionMatrix?.();
const arr = proj?.toArray ? proj.toArray() : proj;
if (Array.isArray(arr) && arr.length === 16) {
engine.eventBus.emit("ar:camera", { projectionMatrix: arr });
}
// 4) Three.js plugin
const threePlugin = new ThreeJSRendererPlugin({
container: document.getElementById("viewport"),
useLegacyAxisChain: true,
changeMatrixMode: "modelViewMatrix",
preferRAF: true,
// debugSceneAxes: true,
// debugAnchorAxes: true,
});
await threePlugin.init(engine);
await threePlugin.enable();
// 5) Start engine loop
engine.start();| Event | Payload | Purpose |
|---|---|---|
ar:marker |
{ id, matrix?, visible? } |
Unified high-level marker pose/visibility |
ar:getMarker |
{ matrix, marker: {...} } |
Raw worker-level pose (plugin extracts ID/confidence) |
ar:markerFound / Updated / Lost |
legacy shapes | Adapted internally to ar:marker |
ar:camera |
{ projectionMatrix } |
Sets camera projection |
engine:update |
any | Optional frame trigger (in addition to RAF) |
| Option | Type | Default | Description |
|---|---|---|---|
container |
HTMLElement | document.body |
Mount target for canvas |
preferRAF |
boolean | true |
Render each RAF even w/o engine:update |
minConfidence |
number | 0 |
Ignore ar:getMarker below confidence |
useLegacyAxisChain |
boolean | true |
Use classic AR.js transform chain |
changeMatrixMode |
string | modelViewMatrix |
Or cameraTransformMatrix (inverts) |
invertModelView |
boolean | false |
Experimental (disabled if legacy chain on) |
applyAxisFix |
boolean | false |
Experimental axis correction (Y/Z Ο) |
debugSceneAxes |
boolean | false |
Show AxesHelper at scene origin |
sceneAxesSize |
number | 2 |
Size for scene axes helper |
debugAnchorAxes |
boolean | false |
Add AxesHelper per anchor |
anchorAxesSize |
number | 0.5 |
Size for anchor axes helper |
rendererFactory |
function | null |
Inject custom renderer (testing) |
Classic AR.js chain:
finalMatrix = R_y(Ο) * R_z(Ο) * modelViewMatrix * R_x(Ο/2)
If changeMatrixMode === 'cameraTransformMatrix', invert at end.
const proj = artoolkit.getProjectionMatrix();
const arr = proj?.toArray ? proj.toArray() : proj;
if (Array.isArray(arr) && arr.length === 16) {
engine.eventBus.emit("ar:camera", { projectionMatrix: arr });
}Look for log: Projection applied.
Anchors are created lazily from the first pose event.
engine.eventBus.on("ar:getMarker", (d) => {
const id = String(
d?.marker?.markerId ??
d?.marker?.id ??
d?.marker?.pattHandle ??
d?.marker?.uid ??
d?.marker?.index ??
"0",
);
// Add content once anchor exists
setTimeout(() => {
const anchor = threePlugin.getAnchor(id);
if (anchor && !anchor.userData._content) {
anchor.userData._content = true;
const cube = new THREE.Mesh(
new THREE.BoxGeometry(0.5, 0.5, 0.5),
new THREE.MeshBasicMaterial({ color: 0xff00ff }),
);
cube.position.y = 0.25;
anchor.add(cube);
}
}, 0);
// Bridge raw to unified
if (Array.isArray(d?.matrix) && d.matrix.length === 16) {
engine.eventBus.emit("ar:marker", { id, matrix: d.matrix, visible: true });
}
});Run tests:
npm testWatch:
npm run test:watchCoverage includes:
- Axis chain vs experimental path
- Inversion & axis fix effects
- Confidence filtering
- Anchor lifecycle (create, reuse, visibility)
- RAF fallback vs engine:update
- Projection & inverse
- Disable/Dispose cleanup
- Debug helpers presence
- Matrix invariants (
matrixAutoUpdate=false)
Test renderer injection example:
const fakeRenderer = {
domElement: document.createElement("canvas"),
setPixelRatio() {},
setClearColor() {},
setSize() {},
render() {},
dispose() {},
};
const plugin = new ThreeJSRendererPlugin({
rendererFactory: () => fakeRenderer,
});GitHub Actions workflow (.github/workflows/ci.yml) runs:
- Install
- Build
- Tests (Node version defined in
.nvmrcfile) Badge above shows current status.
TypeScript declaration files are included in the types folder. Prefer importing types from the provided declarations:
types/index.d.tstypes/threejs-renderer-plugin.d.ts
Source maps (.d.ts.map) are included for better editor/IDE support.
- Built & tested with Three.js 0.161.x
- Requires AR.js-core engine abstraction with an event bus (
on/off/emit) - Should work with any tracking plugin that can emit marker IDs + 4x4 pose matrices
- π Additional renderer plugins (Babylon / PlayCanvas)
- π§· Multi-marker composition helpers
- π Pose smoothing module (optional add-on)
- π‘ Example gallery with animated models & GLTF loader integration
- π§ͺ Visual regression tests (screenshot-based) in CI
MIT Β© AR.js Org
Made with β€οΈ for Web AR. Contributions welcome! Open an issue / PR π