[This article was originally published on 12 August 2011. It was updated on 14 November 2019 to cover the latest version of Three.js and add mobile support.]
To say that JavaScript has come a long way in recent years is a bit of an understatement. At the turn of the century we were using it mainly for validating forms and opening annoying pop-up windows. These days, JavaScript can play HTML5 video and audio; generate richly-detailed graphics on the fly; query a mobile device to display its location; and even make a web server.
One area where JavaScript has improved in leaps and bounds is 3D graphics. Ten years ago, the idea of smooth, real-time, realistic 3D graphics running inside a web browser seemed out of this world. Nowadays it’s becoming commonplace, thanks to modern browsers supporting both the canvas
element and WebGL, a library that adds hardware-accelerated, OpenGL-like 3D graphics capabilities to the browser.
Building on top of these layers, some enterprising folks have built JavaScript libraries that make it easy for the average coder — that is, someone without a degree in computer graphics — to create realistic, animated 3D scenes, right in the browser. This is opening up a world of possibilities for online games, interactive videos, web apps, and much more.
One such library that’s gained a lot of traction is Mr. doob’s Three.js. This library gives you all you need to create complex 3D scenes in the browser, using nothing but JavaScript.
While Three.js is relatively easy to use, it can still be daunting if you’re not familiar with the concepts involved in computer-generated imagery. In this tutorial, you’ll get a gentle introduction to Three.js’s basic features, and learn how to put them to practical use by creating a rotatable 3D boxshot scene.
What Exactly Is Three.js?
Three.js is a JavaScript library that lets you create and render objects in a 3D scene. It’s designed to be lightweight and easy to use.
Three.js can render its 3D scenes using the WebGL library built into many modern browsers, such as Chrome and Firefox. This is the recommended approach, since it uses the computer’s GPU (graphics chip) to do the work. This makes rendering much faster, takes the load off the CPU, and also gives you more 3D features.
Three.js can do some pretty awesome things — check out some of the demos that come with the library. It is also a major component of The Wilderness Downtown, an interactive video for the band Arcade Fire, and 3 Dreams of Black (below), an interactive film promoting the album Rome.
Not only can you create 3D objects from scratch using Three.js and JavaScript, you can also import ready-made objects created using 3D apps like Blender straight into Three.js. This opens up a world of possibilities for interactive 3D scenes and games, all running within the browser.
For this tutorial, though, we’ll keep things nice and easy, and create a simple box with some bitmap textures wrapped around it.
Ready? Let’s start building!
Step 1: Install Three.js
The easiest way to install Three.js is to click the download link over at the Three.js homepage. When the popup appears, download the latest package (I used version r110 for this tutorial) and unzip it.
Within the package folder, you’ll see a build
folder containing the file three.js
. Create a folder called, for example, boxshot
somewhere in your local website, and copy the three.js
file to the boxshot
folder.
Step 2: Install OrbitControls.js
The other library file you need is OrbitControls.js
. This gives you a ready-made set of controls that allow the user to move the camera around by clicking and dragging (touching and dragging on mobile).
You’ll find the OrbitControls.js
file inside the examples/js/controls
folder in the package folder. Copy this file to the boxshot
folder you created in Step 1.
Step 3: Set Things Up
OK, you’re now ready to create your basic page for the boxshot, and initialize things. Save the following file as book.html
in your boxshot
folder:
<!doctype html> <html> <head> <title>A Rotatable 3D Product Boxshot with Three.js</title> <script type="text/javascript" src="Three.js"></script> <script type="text/javascript" src="OrbitControls.js"></script> <style> body { margin: 0; padding: 0; } </style> <script type="text/javascript"> window.onload = function() { } </script> </head> <body></body> </html>
Here you’ve created a simple HTML5 page with an empty body
element. In the head
, you’ve included the Three.js
and OrbitControls.js
libraries, and you’ve started to build a window.onload
event handler function. We’ll add code to this function in the next few steps to build our boxshot scene.
Step 4: Create the Renderer
The next step is to create a Three.js renderer object. The renderer is in charge of painting the entire scene onto a canvas
element.
Here’s the code to create the renderer — add it to the end of the window.onload
handler function you created in Step 3, before the function’s closing brace:
// Create the renderer and add it to the page's body element var renderer = new THREE.WebGLRenderer( { alpha: true } ); renderer.setPixelRatio( window.devicePixelRatio ); renderer.setSize( window.innerWidth, window.innerHeight ); document.body.appendChild( renderer.domElement );
This code starts by creating a THREE.WebGLRenderer
object, setting the alpha
parameter to true
(this gives the scene a transparent background colour instead of black). Then it sets the renderer’s pixelRatio
property to match the device’s pixel ratio — this will make sure that the scene looks sharp on both regular and HiDPI (Retina) displays.
Next, the code sets the renderer’s size to the width and height of the browser window so that it fills the whole browser. Finally it adds the renderer’s canvas
element — stored in the domElement
property — to the document body so that it appears in the page.
Step 5: Create the Scene and Camera
You’re now ready to set up the scene and camera. The scene object holds all the objects and lights that make up the 3D scene, while the camera object specifies how the scene looks through the virtual camera that points “into” the page and views the scene.
Here’s the code to create the scene and camera — as before, add it to the end of your window.onload
handler:
// Create the scene to hold the object var scene = new THREE.Scene(); // Create the camera var camera = new THREE.PerspectiveCamera( 35, // Field of view window.innerWidth / window.innerHeight, // Aspect ratio 0.1, // Near plane distance 1000 // Far plane distance ); // Position the camera camera.position.set( -15, 10, 20 );
First the code creates a new THREE.Scene
object and stores it in the variable scene
. Next it creates a THREE.PerspectiveCamera
object and stores it in camera
. The constructor takes a number of parameters that define how the scene looks through the camera:
- Field of view defines how much of the scene can be viewed through the camera. It’s measured in degrees. Large numbers make the scene look farther away (180 degrees makes it infinitely far away). Small numbers make it look closer. 35 degrees is a good compromise.
- Aspect ratio defines the camera’s aspect ratio — the ratio of the width of the camera window to its height. Usually you want this to be the same as the renderer’s aspect ratio.
- The Near and far plane distances define the distance from the camera to the near and far planes. Imagine a pyramid, with the pointy end at the camera and the base off in the distance. This pyramid is then intersected by the two planes, which are parallel to the page. The visible scene is the space inside the pyramid and between the two planes. Any part of the scene that falls outside this space is clipped (not shown). Usually you want to use a very small value for the near plane, and a very large value for the far plane.
Once we’ve created our camera, we position it in the scene by calling the set()
method of the camera’s position
property. We place it 15 units to the left of the origin, 10 units above the origin, and 20 units in front of the origin.
Step 6: Add the Lights
Lights allow you to add realism to a scene by creating areas of light and shadow. If you don’t add lights then the scene looks rather flat.
Let’s add a couple of lights to the scene: a point light — which simulates a single point of light such as a light bulb — and an ambient light, which provides a base level of light to illuminate the whole scene and soften the shadows.
Here’s the code — add it to the window.onload
handler, after the code you added in Step 5:
// Add the lights var light = new THREE.PointLight( 0xffffff, .4 ); light.position.set( 10, 10, 10 ); scene.add( light ); ambientLight = new THREE.AmbientLight( 0xbbbbbb ); scene.add( ambientLight );
First we create our point light as a THREE.PointLight
object. We give the light a white colour and an intensity of 0.4, which is fairly low (the default is 1). This stops the light from adding too much glare to the book. We then position the light by calling the set()
method of the light’s position
property. We position it 10 units to the right of the origin, 10 units above the origin, and 10 units in front of the origin. Finally, we add it to the scene by calling scene.add()
, passing in the light object.
Our ambient light is simpler to set up. We give it a light grey colour so that it doesn’t wash out the scene, then we add it to the scene using scene.add()
.
Step 7: Create the Materials
Now comes the part where we make the materials that govern the appearance of the book. In the next step, we’ll wrap these materials around a cuboid shape to create the book object.
Here’s the code to create the materials — again, add it to the end of the window.onload
handler:
// Load the textures (book images) var textureLoader = new THREE.TextureLoader(); var bookCoverTexture = textureLoader.load( 'southern-gems-cover.png' ); var bookSpineTexture = textureLoader.load( 'southern-gems-spine.png' ); var bookBackTexture = textureLoader.load( 'southern-gems-back.png' ); var bookPagesTexture = textureLoader.load( 'southern-gems-pages.png' ); var bookPagesTopBottomTexture = textureLoader.load( 'southern-gems-pages-topbottom.png' ); // Use the linear filter for the textures to avoid blurriness bookCoverTexture.minFilter = bookSpineTexture.minFilter = bookBackTexture.minFilter = bookPagesTexture.minFilter = bookPagesTopBottomTexture.minFilter = THREE.LinearFilter; // Create the materials var bookCover = new THREE.MeshLambertMaterial( { color: 0xffffff, map: bookCoverTexture } ); var bookSpine = new THREE.MeshLambertMaterial( { color: 0xffffff, map: bookSpineTexture } ); var bookBack = new THREE.MeshLambertMaterial( { color: 0xffffff, map: bookBackTexture } ); var bookPages = new THREE.MeshLambertMaterial( { color: 0xffffff, map: bookPagesTexture } ); var bookPagesTopBottom = new THREE.MeshLambertMaterial( { color: 0xffffff, map: bookPagesTopBottomTexture } ); var materials = [ bookPages, // Right side bookSpine, // Left side bookPagesTopBottom, // Top side bookPagesTopBottom, // Bottom side bookCover, // Front side bookBack // Back side ];
First, the code loads the images for the different sides of the book (these images are known as textures). It then sets all of these textures to use the LinearFilter
filter when they’re rendered, instead of the default LinearMipmapLinearFilter
. This filter is better for displaying details such as the cover of the book while reducing blurriness.
Now we’ve loaded the textures, we can create the materials based on these textures. We use MeshLambertMaterial
, which is nice and fast if you don’t need specular (shiny) highlights on your object, which we don’t in this case.
For each material, we first pass a fallback colour of 0xffffff
(white) as the color
argument to the constructor, followed by a map
argument, which is the texture to use for the material.
Once we’ve created all our materials, we group them into an array called materials
so that we can apply them to our book object in the next step.
Here’s how the various texture files look — you’ll find all of the image files in the code download if you want to play with them:
Step 8: Create the Book
Now that we’ve set up the scene and created the materials, adding the book itself is relatively easy. Here’s the code — add it to the end of your window.onload
handler as usual:
// Create the book and add it to the scene var book = new THREE.Mesh( new THREE.BoxGeometry( 7, 10, 1.2, 4, 4, 1 ), materials ); scene.add( book );
First we create the cuboid shape for our book by creating a THREE.BoxGeometry
object. We pass it 6 arguments, as follows:
- The width, height, and depth of the book (7, 10, and 1.2 units respectively).
- The number of segments to use across the width, height and depth of the cuboid (4, 4, and 1 respectively). This defines how many smaller shapes make up the overall cuboid. Too few segments, and the book’s texture can look warped. Too many, and the poor computer grinds to a halt!
Once we have our THREE.BoxGeometry
object, we use it, along with the materials
array we created in Step 7, to create a new THREE.Mesh
object, book
. This is the object that is actually rendered in the scene.
Finally, we add our book mesh to the scene by calling scene.add()
.
Step 9: Create the Controls
Remember the OrbitControls
library we included at the start? Now it’s time to use that library to create the controls that will allow the user to spin the camera around the book. Here’s the code:
// Create the orbit controls for the camera controls = new THREE.OrbitControls( camera, renderer.domElement ); controls.enableDamping = true; controls.dampingFactor = 0.25; controls.enablePan = false; controls.enableZoom = false;
First we create the OrbitControls
object, passing in the object to control (the camera), as well as the HTML element that will receive the click/touch events (in this case, the canvas
element that renders our scene).
Then we set enableDamping
to true
, which adds some inertia to the spinning action, and set the damping factor to 0.25, which gives the inertia a good feel.
Finally we disable panning and zooming so that the user can only spin the book around, to keep things simple.
Step 10: Start Animating
We’ve now built our whole scene. All that’s left to do is render it! Here’s the code to do just that — again, add all these lines of code inside your window.onload
handler, at the end:
// Begin the animation animate(); /* Animate a frame */ function animate() { // Update the orbit controls controls.update(); // Render the frame renderer.render( scene, camera ); // Keep the animation going requestAnimationFrame( animate ); }
Since we want the user to be able to rotate the book, we need to render it not once, but continuously. To that end, we first call a function, animate()
, to kick-start the animation process. Within animate()
, we call the update()
method on the OrbitControls
object we created earlier, to allow the controls to update the camera position if necessary,
Then, we call the renderer
object’s render()
method to render the scene, passing in the scene to render and the camera to use (we created these in Step 5). Finally, the function calls requestAnimationFrame()
, passing in its own function name, so that the function is called again when a new frame is available, and the animation repeats indefinitely.
The End Result
Here’s the boxshot again. It’s fast, it looks great, and — thanks to the joys of Three.js — it was easy to create, too!
Summary
In this article you’ve explored the basics of the awesome Three.js JavaScript 3D engine. You learned how to set up Three.js, and how to create renderers, scenes, cameras, lights, textures, materials, cuboids and meshes. You also saw how to animate 3D scenes using render()
and requestAnimationFrame()
, and how to use the OrbitControls
library to provide an easy way for the user to spin the camera around.
There’s an awful lot more to Three.js than we’ve seen here — the best way to learn is to explore the examples that come with the library — but hopefully this tutorial has given you a useful introduction to Three.js and the world of JavaScript 3D rendering.
Have fun!
Crazyhunk says
quite interesting, love the easy interface…
though I have a small question, I saw the box rotates when I move the mouse, can this be limited to just the a section of the screen or are we talking about the whole page.
matt says
@Crazyhunk: Sure, you could grab the canvas element that the renderer uses with renderer.domElement, then attach the mousemove handler to the canvas element instead of the document. Then the box should only rotate when the mouse is moved inside the canvas element.
fred says
Nice work there, but i found out the script is consuming too much gpu resources, you can check this easly with gpu-z.
To work around this issue, change one couple lines
// animate(); and inside animate function //requestAnimFrame( animate );
to call the script just put setInterval( animate, 1000 / 60 ); before mouse event, gpu resource will drop from 60% to 0% and when you move the object he goes max 10% -15%.
keep going, nice tutorial.
matt says
@fred: Interesting – thanks for the suggestion! 🙂
miguelrivero says
Great tutorial. It helped me do this.
2 things:
1) Why does the cube shows those wires?
2) Can you extend on how to change the mousemove to a mouseover (in this case the cover div)?
Thanks!
matt says
@miguelrivero:
1) Try overdraw: true:
https://github.com/mrdoob/three.js/issues/1044
2) Why would you want to use a mouseover event instead of mousemove? That would only trigger when the user moved their mouse into the div.
georgeCampos says
Great code. On a smartphone the mouse events don’t translate to touch events. If I load jquery and jquerymobile the code stops working. Any guidance or suggestions?
Matt Doyle says
It’s been a while, but as of today the code now works on mobile too 🙂
Tobias says
Thanks for the description – it looks very good and simple – I will try it directly.
We would like to let the 3d object rotate on its own right from the start. How can we create that? Do you have experience with it?
Thanks for your feedback.
Marcus says
Are the lights working?
Matt Doyle says
I think so, yes.
Dan Ware says
The lights will work if you use scene.add( light ).
There is no function addLight() so it won’t work as shown in the article.
Matt Doyle says
Thanks Dan.
addLight()
was the old Three.js method, superseded byadd()
. I had updated the demo, but not the article! Should be fixed up now.