Bringing 3D to the Browser with Three.js

Three.js has become a popular choice for developers wanting to add 3D graphics to their web projects. It handles much of the heavy lifting required to get started, offering a choice of renderers — HTML5 Canvas, WebGL, or SVG — which means you can tailor the output to your needs and your target browser's capabilities.

This guide walks through the core components you'll need to create a basic 3D scene: a scene graph, a renderer, a camera, and at least one object with a material.

Renderer and Browser Support

The first decision is which renderer to use. Canvas is the most widely supported option, while WebGL taps into the GPU directly, freeing up the CPU for other tasks such as physics calculations or user input handling. If you choose WebGL, keep in mind that you will be dealing with shaders — GLSL programs that run on the GPU. Three.js provides pre-built materials that handle this for you, so you can avoid the intricate math of lighting and reflection. Should you want more control, you can also write custom shaders via MeshShaderMaterial.

Browser support varies. Chrome and Firefox (version 4+) offer the broadest support for Canvas, WebGL, and SVG. Opera and Safari are still catching up, with WebGL support coming to newer versions. Internet Explorer 9 and later only support Canvas rendering.

Setting Up a Scene

Once you've included the Three.js library in your HTML, creating a scene, camera, and renderer is straightforward:

// set the scene size
var WIDTH = 400,
HEIGHT = 300;

// set some camera attributes
var VIEW_ANGLE = 45,
ASPECT = WIDTH / HEIGHT,
NEAR = 0.1,
FAR = 10000;

// get the DOM element to attach to
// - assume we've got jQuery to hand
var $container = $('#container');

// create a WebGL renderer, camera
// and a scene
var renderer = new THREE.WebGLRenderer();
var camera = new THREE.PerspectiveCamera(
                VIEW_ANGLE,
                ASPECT,
                NEAR,
                FAR );

var scene = new THREE.Scene();

// the camera starts at 0,0,0 so pull it back
camera.position.z = 300;

// start the renderer
renderer.setSize(WIDTH, HEIGHT);

// attach the render-supplied DOM element
$container.append(renderer.domElement);

Note that 3D rendering is not a lightweight task. It's important to write optimized JavaScript to avoid bottlenecks, especially when you're animating a scene.

Adding Shapes and Materials

With the core setup in place, you can add geometric primitives like spheres, planes, cubes, and cylinders:

// set up the sphere vars
var radius = 50, segments = 16, rings = 16;

// create a new mesh with sphere geometry -
// we will cover the sphereMaterial next!
var sphere = new THREE.Mesh(
new THREE.SphereGeometry(radius,
segments,
rings),

sphereMaterial);

// add the sphere to the scene
scene.add(sphere);

This code references a material that hasn't been defined yet. Three.js offers several common material types, including basic (unlit), Lambert, and Phong. These materials abstract away the need to write your own shaders. For a Lambert-shaded sphere, you would define it like this:

// create the sphere's material
var sphereMaterial = new THREE.MeshLambertMaterial(
{
// a gorgeous red.
color: 0xCC0000
});

You can specify additional properties when creating a material, such as smoothing or environment maps. The official Three.js wiki and the newer threejs.org site document these options.

Adding Light and Rendering

If you were to render the scene now, the sphere would appear as a flat red circle. With no explicit light source, Three.js applies full ambient light by default. To get a more realistic look, add a point light:

// create a point light
var pointLight = new THREE.PointLight( 0xFFFFFF );

// set its position
pointLight.position.x = 10;
pointLight.position.y = 50;
pointLight.position.z = 130;

// add to the scene
scene.add(pointLight);

Now you can render the scene to see the result:

// draw!
renderer.render(scene, camera);

If you plan to animate, use a loop with requestAnimationFrame, which is the most efficient method for browser rendering. Since browser support isn't universal, Paul Irish's shim is a safe fallback.

Object3D Properties and a Common Gotcha

Most objects in Three.js inherit from the base Object3D, which provides position, rotation, and scale properties. A Mesh adds geometry and materials to this list. You can modify these properties to manipulate your objects on the fly:

// sphere geometry
sphere.geometry

// which contains the vertices and faces
sphere.geometry.vertices // an array
sphere.geometry.faces // also an array

// its position
sphere.position // has x, y and z properties
sphere.rotation // same
sphere.scale // ... same

However, a change such as moving a vertex will not appear in the render loop immediately. Three.js caches mesh data for performance. To force a recalculation, you must flag the change explicitly:

// changes to the vertices
sphere.geometry.__dirtyVertices = true;

// changes to the normals
sphere.geometry.__dirtyNormals = true;

Only mark the properties that have actually changed to keep unnecessary recalculations to a minimum. This is a key step to remember when manipulating geometry or materials dynamically.