Game Development 22 min read

Mastering 3D Web Games: Three.js, glTF, and Cannon.js Essentials

This guide walks you through the core concepts of Three.js, the glTF model format, physics integration with Cannon.js, interaction handling, performance tuning, and a suite of debugging tools, providing practical code snippets and best‑practice tips for building efficient 3D web games.

Aotu Lab
Aotu Lab
Aotu Lab
Mastering 3D Web Games: Three.js, glTF, and Cannon.js Essentials

Introduction

When developing H5 games under tight schedules, many teams stick to 2D visuals, but a 3D physics‑based game can bring fresh experiences. This article shares practical knowledge about Three.js, Cannon.js, glTF models, related tools, and performance considerations to help you launch a 3D project quickly.

Three.js Basics

Scene

A THREE.Scene acts as a container for all objects, lights, and cameras that will be rendered.

Axes

Three.js uses a right‑handed coordinate system: X (thumb), Y (index finger), Z (middle finger).

Camera

Various camera types exist; the most common is PerspectiveCamera. Other types include ArrayCamera, CubeCamera, StereoCamera, and OrthographicCamera for specific use‑cases such as VR or 2.5D views.

Mesh

A mesh combines Geometry (vertices, faces) and Material (surface appearance). Geometry defines the shape, while Material determines color, reflectivity, transparency, etc.

Light

Lights illuminate the scene. Common types are AmbientLight, DirectionalLight, HemisphereLight, PointLight, and SpotLight. Only DirectionalLight, PointLight, and SpotLight can cast shadows.

Shadow

To enable shadows you must set castShadow and receiveShadow on every mesh that participates. The following snippet traverses all child meshes to activate shadows:

object.traverse(function (child) {
  if (child instanceof THREE.Mesh) {
    child.castShadow = true;
    child.receiveShadow = true;
  }
});

glTF Model Format

Overview

glTF, created by the Khronos Group, minimizes file size and speeds up transmission, parsing, and rendering. Version 2.0 (released 2017) is the current standard.

File Composition

.gltf – JSON describing scene hierarchy, cameras, meshes, materials, animations.

.bin – Binary buffer containing geometry, animation data, directly uploadable to GPU.

.jpg/.png – Texture images.

Exporting

Export plugins exist for most 3D tools (3DS Max, Maya, Blender). When a direct exporter is unavailable (e.g., Cinema 4D), export to an intermediate format, import into Blender, then export glTF.

Loading in Three.js

var gltfLoader = new THREE.GLTFLoader();
gltfLoader.load('./assets/box.gltf', function (scene) {
  // model object
  scene.add(scene);
});

Animation

glTF can embed animation clips. After loading, the animations array contains AnimationClip objects that can be played via THREE.AnimationMixer:

let gltfLoader = new THREE.GLTFLoader();
let mixer = null;
gltfLoader.load('./assets/box.gltf', function (scene) {
  let object = scene.scene;
  let animations = scene.animations;
  if (animations && animations.length) {
    mixer = new THREE.AnimationMixer(object);
    for (let i = 0; i < animations.length; i++) {
      mixer.clipAction(animations[i]).play();
    }
  }
  scene.add(object);
});
function update() {
  let delta = clock.getDelta();
  mixer.update(delta);
}

Draco Compression

Draco reduces geometry size by converting glTF to .glb with compressed .bin. Use gltf-pipeline to compress:

$ npm install -g gltf-pipeline
$ gltf-pipeline -i model.gltf -o model.glb

When loading compressed files, include the Draco decoder libraries:

let loader = new THREE.GLTFLoader();
THREE.DRACOLoader.setDecoderPath('/examples/js/libs/draco');
loader.setDRACOLoader(new THREE.DRACOLoader());
loader.load('models/gltf/box.gltf', function (gltf) {
  scene.add(gltf.scene);
});

Cannon.js Physics Engine

Why Cannon.js

Among several JavaScript physics libraries (Cannon.js, Oimo.js, Ammo.js, Energy.js), Cannon.js offers a full‑featured, actively maintained solution with collision detection, friction, restitution, and constraints.

Setup Steps

Initialize World

let world = new CANNON.World();
world.gravity.set(0, -10, 0);
world.broadphase = new CANNON.NaiveBroadphase();

Create Dynamic Sphere

let sphereShape = new CANNON.Sphere(1);
let sphereBody = new CANNON.Body({ mass: 5, position: new CANNON.Vec3(0, 10, 0), shape: sphereShape });
world.add(sphereBody);

Create Static Plane

let groundShape = new CANNON.Plane();
let groundBody = new CANNON.Body({ mass: 0, shape: groundShape });
groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2);
world.add(groundBody);

Visual Meshes (Three.js)

// Plane mesh
let groundGeometry = new THREE.PlaneGeometry(20, 20, 32);
let groundMaterial = new THREE.MeshStandardMaterial({ color: 0x7f7f7f, side: THREE.DoubleSide });
let ground = new THREE.Mesh(groundGeometry, groundMaterial);
scene.add(ground);
// Sphere mesh
let sphereGeometry = new THREE.SphereGeometry(1, 32, 32);
let sphereMaterial = new THREE.MeshStandardMaterial({ color: 0xffff00 });
let sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
scene.add(sphere);

Simulation Loop

function update() {
  requestAnimationFrame(update);
  world.step(1 / 60);
  sphere.position.copy(sphereBody.position);
  sphere.quaternion.copy(sphereBody.quaternion);
}

Advanced Topics

Custom Material Contact

let ground_cm = new CANNON.Material();
let sphere_cm = new CANNON.Material();
let contact = new CANNON.ContactMaterial(ground_cm, sphere_cm, { friction: 1, restitution: 0.4 });
world.addContactMaterial(contact);

Disable Physical Response boxBody.collisionResponse = false; Scale a Body

boxBody.updateMassProperties();
new TimelineMax().to(sphereBody.shapes[0], 2, { radius: 0.2 });

Interaction with Raycaster

Use THREE.Raycaster to convert 2D screen coordinates into a 3D ray and detect intersected objects.

let raycaster = new THREE.Raycaster();
let mouse = new THREE.Vector2();
function onTouchEnd(ev) {
  var event = ev.changedTouches[0];
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
  raycaster.setFromCamera(mouse, camera);
  let intersects = raycaster.intersectObjects(scene, true);
  for (let i = 0; i < intersects.length; i++) {
    console.log(intersects[i]);
  }
}

Performance Tips

Model Detail – Prefer low‑poly meshes and use normal/bump maps to simulate complexity.

Lights & Shadows – Use directional or spot lights for realism; limit shadow map resolution and disable shadows on unnecessary meshes.

Antialiasing – Enable renderer.antialias = true only when performance budget allows.

Pixel Ratio – Set a fixed ratio (e.g., renderer.setPixelRatio(2)) instead of the device’s maximum to avoid excessive GPU load.

Useful Development Tools

OrbitControls – Drag to rotate the camera and scroll to zoom.

new THREE.OrbitControls(camera, renderer.domElement);

glTF Viewer – Web and desktop viewers for quick model inspection.

Camera Helper

let helper = new THREE.CameraHelper(camera);
scene.add(helper);

Light Helper

let lightHelper = new THREE.DirectionalLightHelper(light);
scene.add(lightHelper);

AxesHelper

let axesHelper = new THREE.AxesHelper(10);
scene.add(axesHelper);

Cannon Debug Renderer

let cannonDebugRenderer = new THREE.CannonDebugRenderer(scene, world);
function render() {
  requestAnimationFrame(render);
  cannonDebugRenderer.update();
}

dat.GUI – Real‑time UI for tweaking parameters.

let gui = new dat.GUI();
gui.add(opts, 'x', -3, 3);
gui.add(opts, 'y', -3, 3);
gui.add(opts, 'scale', 1, 3);

Stats.js – Monitor FPS, frame time, and memory usage.

var stats = new Stats();
stats.showPanel(1);
document.body.appendChild(stats.dom);

Conclusion

By mastering the concepts and code patterns described above—Three.js fundamentals, glTF workflow, Cannon.js physics, interaction handling, performance tuning, and debugging utilities—you can efficiently create robust, high‑performance 3D web games. Feel free to share your own improvements in the comments.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Performance OptimizationThree.jsWebGLglTFphysics engineCannon.js3D Game Development
Aotu Lab
Written by

Aotu Lab

Aotu Lab, founded in October 2015, is a front-end engineering team serving multi-platform products. The articles in this public account are intended to share and discuss technology, reflecting only the personal views of Aotu Lab members and not the official stance of JD.com Technology.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.