Get started with WebGL using three.js

WebGL, which is widely supported on all modern browsers, enables you to work with hardware-accelerated 3D graphics in JavaScript, opening up a huge range of possibilities for browser-based apps and games – just check out these 20 amazing examples of WebGL in action, for proof.

WebGL itself is rather low-level, and it's unlikely you'll want to work with it in its vanilla form. There are a range of libraries and even game engines available to provide higher-level functionality. 

In this tutorial I'm going to show you how to make an app or site that uses WebGL in combination with three.js, which is a free open-source library providing abstraction of WebGL.

01. Get three.js

You'll need to start by getting hold of three.js. Once you have the latest build, place three.js into your project folder. We'll then add it as a script to our page like any other JavaScript library. We'll place our own code into a separate JavaScript file.

 <!DOCTYPE html>
  <meta charset=utf-8>
  <title>Getting started with three.js</title>
  <body style="margin: 0;">
  <script src="three.js"></script>
  <script src="demo.js"></script>

02. Set the scene

We need three things to get started with WebGL: a scene, a camera and a renderer. A scene is where we place objects to be displayed by three.js. A camera is the point of view from which we will see them. The renderer brings the two together and draws the screen accordingly. Once we have these set up, we add the renderer to the document.

var width = window.innerWidth;
var height = window.innerHeight;
var viewAngle = 45;
var nearClipping = 0.1;
var farClipping = 9999;
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( viewAngle, width / height, nearClipping, farClipping );
var renderer = new THREE.WebGLRenderer();
renderer.setSize( width, height );
document.body.appendChild( renderer.domElement );

03. Create a loop

Next, we need to create a loop to actually render our scene. We do this using the renderer.render() function, but the key part is to recursively use requestAnimationFrame(), which is a built-in function that enables the browser to request another frame when it is ready to draw one. Using requestAnimationFrame() is easier and results in smoother animation than trying to time drawing of frames yourself.

function animate() {
  requestAnimationFrame( animate );
  renderer.render( scene, camera );

04. Create basic objects

Now we can start adding some objects to our scene. We can do this by creating Mesh objects and adding them to it. A mesh is composed of geometry (the shape of the object), and a material (the colour or texture used to paint it). We'll create some basic objects by defining three different geometries, and assigning different colour materials to them.

var cubeGeometry = new THREE.BoxGeometry( 1, 1, 1 );
var cubeMaterial = new THREE.MeshLambertMaterial( { color: 0xff0000 } );
var cube = new THREE.Mesh( cubeGeometry, cubeMaterial );
var coneGeometry = new THREE.ConeGeometry( 0.5, 1, 4 );
var coneMaterial = new THREE.MeshLambertMaterial( {color: 0x00ff00} );
var cone = new THREE.Mesh( coneGeometry, coneMaterial );
var sphereGeometry = new THREE.SphereGeometry( 0.5, 8, 8 );
var sphereMaterial = new THREE.MeshLambertMaterial( { color: 0x0000ff } );
var sphere = new THREE.Mesh( sphereGeometry, sphereMaterial );

05. Specify a position

By default, objects are added at the origin (x=0, y=0, z=0) of the scene, which is also where our camera is, so we'll need to specify a position for them as well. We're then ready to add the meshes to our scene, which will mean they are automatically rendered in the animate() loop.

cube.position.x = -2
cube.position.z = -5;
cone.position.z = -5;
sphere.position.z = -5;
sphere.position.x = 2;
cube.position.z = -5;

06. Add a light source

If you view your page now, you'll find things are looking a little flat. There's no lighting applied to the objects, so they are solid primary colours and look two-dimensional. To fix this, we need to switch from MeshBasicMaterial to a material that supports lighting; we'll use MeshLambertMaterial. We also need to add a light source to the scene.

var light = new THREE.PointLight(0xFFFFFF);
light.position.x = 0;
light.position.y = 10;
light.position.z = 0;

07. Move the source

Now we're getting there! You should see what are quite evidently three-dimensional objects on your page. What we haven't yet done is taken full advantage of the animate() function. Let's make a few changes to have our light source move around in a circular motion above the objects.

var lightAngle = 0;
function animate() {
  lightAngle += 5;
  if (lightAngle > 360) { lightAngle = 0;};
  light.position.x = 5 * Math.cos(lightAngle * Math.PI / 180);
  light.position.z = 5 * Math.sin(lightAngle * Math.PI / 180);
  requestAnimationFrame( animate );
  renderer.render( scene, camera ); }

08. Add texture

In practice, we probably don't want our objects to be flat colours. It would be more typical to apply some texturing to them from a file. We can do this using THREE.TextureLoader(). Let's change how our cone object is created to utilise a texture we've loaded from a file. The function is called when the file is loaded.

var textureLoader = new THREE.TextureLoader();
textureLoader.load("./grass_texture.jpg", texture => {
  var coneGeometry = new THREE.ConeGeometry( 0.5, 1, 4 );
  var coneMaterial = new THREE.MeshLambertMaterial( { map: texture } );
  var cone = new THREE.Mesh( coneGeometry, coneMaterial);
  cone.position.z = -5;

09. Make it natural

Things are looking better, but something still isn't quite natural. The texture looks very flat and doesn't respond to the lighting. We can solve this through the use of bump mapping, which enables us to use light and dark parts of an image to simulate texturing on the surface of an object. Let's try this out with a different texture on the sphere. We'll switch the material to MeshPhongMaterial, which allows us to specify a bumpMap attribute.

var textureLoader = new THREE.TextureLoader();
textureLoader.load("./bump_map.jpg", texture => {
  var sphereGeometry = new THREE.SphereGeometry( 0.5, 8, 8 );
  var sphereMaterial = new THREE.MeshPhongMaterial( { color: 0x0000ff, bumpMap: texture, bumpScale: 1.0 } );
  var sphere = new THREE.Mesh( sphereGeometry, sphereMaterial );
  sphere.position.z = -5;
  sphere.position.x = 2;

10. Add controls

As a final touch, give the user a little control over the scene. To add the ability to pan around, there's an extra library that ships with three.js that makes it incredibly easy to do just that. Make sure to extract OrbitControls.js from the three.js package to your project directory, and include it in your page. This is one of several control libraries that ship with three.js to fulfil common styles of camera control.

  <script src="OrbitControls.js"></script>

11. Apply to camera

Now all we need to do is create a THREE.OrbitControls object and apply it to our camera. The library will take care of the rest for you: you won't need to listen for clicks, mouse movements, and so on. You might also want to move your objects back to the origin point, and offset the camera instead so that it can pan neatly around the objects. 

With that, we're done with our introduction. You should have three objects with varying styles of texture, some dynamic lighting, and a user-controlled camera to play with.

camera.position.z = 10;
var controls = new THREE.OrbitControls( camera );

This article featured in issue 268 of Web Designer, the creative web design magazine – offering expert tutorials, cutting-edge trends and free resources. Subscribe to Web Designer now.

Liked this? Try these: