My Introduction To Three.js

Sarah L
9 min readMar 19, 2018

--

One of the most satisfying things about learning to code is that you often get instant feedback about whether or not the code you have written is working. This comes in the form of logs, tests, and even error messages! While learning three.js, the instant feedback I got was in the form of shapes, colors, and animation. That was really satisfying. With this tutorial, I hope you can get some simple takeaways that will make your own foray into three.js just as enjoyable as mine!

Three.js is built on top of WebGL, which allows rendering of 2D and 3D graphics in the browser. WebGL works well for image processing because it takes advantage of the computer’s GPU, instead of operating on the CPU. WebGL is one of the reasons that three.js is so great- it is fast, and runs directly in the browser without the need for a plugin. Read more about WebGL below:

https://webglfundamentals.org/webgl/lessons/webgl-fundamentals.html

I was pleasantly surprised at how simple it was to get started with Three.js. The documentation is very clear in terms of the basics that you need to set up before you start customizing the images. The information is all laid out in the documentation, but I felt that it was helpful to have an understanding of what each piece of the puzzle was doing before I started changing things on my own. You can think of each building block as an aspect of a movie in production: each item has a particular purpose, and all are necessary to make the show go on!

The first thing you’ll need is an index.html file, with a script tag where your three.js code will actually go. I’m struggling to come up with an analogy for this one, but let’s call it the film that the movie ends up on. You can’t play a movie if you can’t get it from the camera to the projector. The index.html is what gets your JavaScript to the browser. I promise my metaphors will get better!

Rotating Rings

The code snippets I show in this article are going to be from my RotatingRings project, which was the first thing I ever made in Three.js. Check it out on Github: https://github.com/sjlaine/RotatingRings.

<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
<title>Rotating Rings</title>
<style>
body { margin: 0; }
canvas { width: 100%; height: 100% }
</style>
</head>
<body>
<script src="threejs/three.js"></script>
<script src="shapes.js"></script>
</body>
</html>

Instead of keeping all of my JavaScript in my index.html file like the example in the three.js documentation, I decided to write it in a separate file. I felt that even on a simple project, it was better to just keep this good habit going.

When you’re doing this, don’t forget that your first script tag should be a reference to the three.js source code, which you should have downloaded and added to your project directory. You can’t do anything in three.js if you haven’t included three.js!

Here comes the JavaScript…

Now we can get into the basic building blocks of any three.js project. Let’s go back to talking about that movie we’re making.

  1. Renderer: this is the function that actually makes use of WebGL, in the sense that it renders everything that you tell it to. The renderer gets appended directly to the DOM. You can even have more than one three.js scene rendered by a single Three.js renderer! In my mind, the renderer is analogous to the director for a movie, making sure that all the moving parts are coming together, and making sure that the work gets done.
  const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );

First, you create an instance of the renderer, then you set its size. The size will usually be dependent on what else you have going on in your webpage. You’ll often just want your three.js to take up the whole window, so that’s what I did here. Finally, you append the new renderer to the DOM. We’ll tell the renderer what it is actually going to render later on.

2. Scene: I like to think of this as the stage where all of the action of our three.js movie takes place. If this is the case, then a scene in three.js really is like a scene in a movie. It’s where all of the actors (geometries), lighting (three.js lights), and setting (background color/images) will be presented to the viewer. The name actually makes a lot of logical sense.

const scene = new THREE.Scene();

Wow, that was pretty simple! All we have to do is instantiate a scene for now, but we will be adding a lot of great things to it as we build them. One more thing about my scene: it’s purple! So now’s a good time to talk about colors.

Three.js gives you a separate constructor function to create colors to add to your scene. I first found this to be a little bit annoying and unnecessary, but now I really like it. I’m pretty sure this isn’t the best practice for the future, but for the sake of my experimentation I’ve gotten in the habit of creating some variables for colors I’d like to use in each project at the top of my code. That way when I’m changing the color of anything I can just pass the color’s name instead of having to think in or look up hexadecimals. Here’s what I did in RotatingRings:

  const pink = new THREE.Color( 0xFF33F3 );
const blue = new THREE.Color( 0x3049FC );
const purple = new THREE.Color( 0xA200FF );
const indigo = new THREE.Color( 0x6600FF );

That way, when I changed the background color of my scene, all I did was:

scene.background = purple;

Let’s get back on track.

3. Camera: Last but not least, what would a movie be without a camera to capture the action? Without a camera, you may have a scene with plenty of beautiful, exciting images, but no viewer will be able to see it to give you credit for that.

const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );

The perspective camera takes in four parameters: the frustum field of view, frustum aspect ratio, frustum near plane, and frustum far plane. Each of these are responsible for altering what the camera can see on the screen. There are other options for cameras in three.js, but the perspective camera is meant to mimic what the human eye sees, and is the most commonly used in three.js according to the docs. You can also set the position of the camera like so:

camera.position.z = 50;

Now for the fun part!

The next step is to code the fun shapes that will be placed in your scene. These are called Three.js meshes, which are each made up of a geometry and material.

  1. Geometry: The geometry is the shape that you specify in three.js. The nice thing about the library is that it comes with some predefined geometries for you to use right off the bat. I started with a cube (called Box Geometry in Three.js lingo).
  const geometry = new THREE.TorusGeometry( 10, 1, 100, 400);
const geometry2 = new THREE.TorusGeometry( 17, 2, 100, 400);
const geometry3 = new THREE.TorusGeometry( 25, 3, 100, 400);

This is the code that specifies the shape and size of my objects. In three.js, a ring is made with a torus geometry. This geometry requires specifying three parameters, respectively: the radius of the geometry as a whole, the radius of the tube, the number of radial segments, and the number of tubular segments. The more segments the torus has, the more rounded it becomes.

Fun Fact: The minimum number of tubular segments a torus can have is three. That makes a tubular triangle 😉

2. Material: this is what specifies how your geometry will actually look on the screen. It takes an options object as a parameter. This allows you to specify a variety of values, such as the roughness and metalness. These are fun to play with because they make the objects you render look like they are actually made of different materials. Just like geometries, three.js gives you some materials to choose from right off the bat. I started off trying to work with the MeshBasicMaterial, but ended up finding that MeshStandardMaterial was more fun because it interacts with/reflects light in the scene. I also changed the color of the material by dotting off of it and setting it to one of the colors that I set higher up in my code. I made three different materials (one for each ring) but here’s just one as an example:

const material = new THREE.MeshStandardMaterial({
wireframe: false,
flatShading: true,
metalness: 0.7,
roughness: 0.01
});
material.color = pink;

Now let’s mash them together! Or should I say, *Mesh*?

The geometry is the actor in our film, and the material is the costume. Put them together, and we get our character! The mesh is just the combination of a geometry and material. Thus, it takes those two as parameters.

const ring1 = new THREE.Mesh( geometry, material );

Don’t forget to add the mesh to your scene!

const ring1 = new THREE.Mesh( geometry, material );

Lights! Camera! Oh wait…

We haven’t done our lights yet. I made a lights array and put each of my lights in that. I used point lights, because it seemed intuitive how to position them in the scene and get the effect I was looking for. In point lights you can specify the color, intensity, and distance as the three parameters.

  const lights = [];  lights[ 0 ] = new THREE.PointLight( 0x3049FC, 3, 0 );
lights[ 1 ] = new THREE.PointLight( 0xFF6060, 3, 0 );
lights[ 2 ] = new THREE.PointLight( 0xFFFFFF, 1, 0 );
lights[ 3 ] = new THREE.PointLight( 0xA200FF, 2, 0 );
lights[ 4 ] = new THREE.PointLight( 0xFFFFFF, 1, 0 );

Next was specifying where the light would be coming from:

  lights[ 0 ].position.set( 0, 200, 0 );
lights[ 1 ].position.set( 100, 200, 150 );
lights[ 2 ].position.set( -100, -200, -200 );
lights[ 3 ].position.set( -300, -200, 200);
lights[ 4 ].position.set( 300, -200, 300 );

Finally, adding them all to the scene:

  scene.add( lights[ 0 ] );
scene.add( lights[ 1 ] );
scene.add( lights[ 2 ] );
scene.add( lights[ 3 ] );
scene.add( lights[ 4 ] );

Action!

The last piece of the three.js puzzle was pretty valuable to me because it introduced me to the function requestAnimationFrame, which I had never been introduced to before. This is a pretty useful function, kind of similar to setTimeout or setInterval. It takes a callback that it calls repeatedly, usually 60 times per second. We use it in three.js to make our meshes come to life!

function animate() {
requestAnimationFrame( animate );
ring1.rotation.x += 0.009 ring1.rotation.y += 0.035;
ring2.rotation.y -= 0.01;
ring3.rotation.y += 0.03;
renderer.render( scene, camera );
}
animate();

The function animate is one of the many big building blocks provided to you in the three.js documentation. Its first line calls requestAnimationFrame on the animate function itself, which means that the function is being called recursively at the rate requestAnimationFrame runs. Next, the positioning changes are set, on the x and y axes (can also be set for z). As you may have guessed, the bigger the number, the faster the rotation. You can do some trial and error with these to see what values you want to set. Finally, renderer.render is called! The renderer takes in the scene and the camera as parameters. That way, every time the animate function is called, the scene and camera are at your service!

There you have it, now you can make things in three.js! It’s pretty simple to get started. Three.js is a great tool to learn if you want to make cool images in the browser. The three.js website has tons of sample projects to give you some inspiration and an idea of what you could aspire to make with tons of practice. Feel free to check out the code for my RotatingRings project on Github — the first thing I ever made in three.js. Happy rendering!

Feedback

I’d love to hear what you thought of this overview! Feel free to comment below to let me know your thoughts.

--

--