Building Marker-Based Augmented Reality with AR.js and Three.js

Augmented Reality (AR) has traditionally required specialized native frameworks, but modern web tools are changing the game. With AR.js and Three.js, developers can now create marker-based AR experiences that run entirely in a browser — no app downloads required.

This article guides you through setting up a lightweight, mobile-friendly AR project using AR.js with raw Three.js, bypassing the need for A-Frame to give you more control over your 3D scenes.

Technologies Used

  • AR.js – A performance-focused AR library for the web.
  • Three.js – A flexible and powerful 3D rendering library built on WebGL.

Use Case

We’ll build a web-based AR scene that renders a red 3D cube on top of a Hiro marker when detected via a webcam.

Project Structure

ar-threejs-project/
│
├── index.html
├── app.js
└── assets/
    └── data/
        └── camera_para.dat

Download camera_para.dat from the AR.js GitHub and place it in the correct folder.

1. HTML Boilerplate (index.html)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>AR.js with Three.js</title>
  <style>body { margin: 0; overflow: hidden; }</style>
</head>
<body>
  <script src="https://cdn.jsdelivr.net/npm/three@0.152.2/build/three.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/ar.js@3.4.2/three.js/build/ar-threex.min.js"></script>
  <script src="app.js"></script>
</body>
</html>

This includes:

  • Three.js for 3D rendering.
  • AR.js with the Three.js build.
  • A script tag to load our main app logic.

2. Main App Logic (app.js)

// Setup basic Three.js scene
const scene = new THREE.Scene();
const camera = new THREE.Camera();
scene.add(camera);

const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(new THREE.Color('black'), 0);
document.body.appendChild(renderer.domElement);

// Setup AR.js source (webcam)
const arSource = new THREEx.ArToolkitSource({ sourceType: 'webcam' });
arSource.init(() => onResize());
window.addEventListener('resize', onResize);

function onResize() {
  arSource.onResize();
  arSource.copySizeTo(renderer.domElement);
  if (arContext.arController) {
    arSource.copySizeTo(arContext.arController.canvas);
  }
}

// Initialize AR.js context
const arContext = new THREEx.ArToolkitContext({
  cameraParametersUrl: 'assets/data/camera_para.dat',
  detectionMode: 'mono',
});

arContext.init(() => {
  camera.projectionMatrix.copy(arContext.getProjectionMatrix());
});

// Create AR marker controls
const markerRoot = new THREE.Group();
scene.add(markerRoot);

const markerControls = new THREEx.ArMarkerControls(arContext, markerRoot, {
  type: 'pattern',
  patternUrl: 'https://cdn.jsdelivr.net/npm/ar.js@3.4.2/data/data/patt.hiro',
  changeMatrixMode: 'modelViewMatrix',
});

// Add a 3D cube to the marker
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshNormalMaterial();
const cube = new THREE.Mesh(geometry, material);
cube.position.y = 0.5;
markerRoot.add(cube);

// Main render loop
function render() {
  requestAnimationFrame(render);
  if (arSource.ready) arContext.update(arSource.domElement);
  renderer.render(scene, camera);
}
render();

Result

Once everything is set up, open your index.html in a modern browser (preferably over HTTPS or localhost), and point your webcam at a printed Hiro marker (you can find one here). You should see a 3D cube appear on top of the marker in real time.

Leave a comment

Your email address will not be published. Required fields are marked *