Loading and Working with Textures, and Preparing Textures for Use in Your Scenes9.1

Adding Realism with Environment Maps9.2

MeshStandardMaterial and the Metal/Rough Workflow9.3

MeshPhongMaterial and the Specular Workflow9.4

Bump, Normal, and Displacement Maps9.5

Two Ways of Approaching Transparency9.6

Emissive, Light, and Ambient Occlusion Maps9.7

Understanding Geometry10

Basic Geometry Concepts: Vertices, Normals and UVs10.1

Creating A Custom Geometry10.2

Points, Particles Systems, and Sprites11

Particle Systems11.1

Introducing Sprites11.2

Lines, Shapes, and Text12

Throwing Shapes: Recreating the 2D Canvas API in 3D 12.1

Text in 3D: The FontLoader and TextBufferGeometry12.2

Rendering Your Scenes with WebGL13

The WebGLRenderer in Depth13.1

Rendering Offscreen to a WebGLRendererTarget13.2

Animating Your Scenes14

Unraveling the Animation System14.1

Introducing Morph Targets14.2

Bones, Skinning, and Skeletal Animation14.3

Post-Processing, Shaders, and Effects15

Adding Post-Processing To A Scene15.1

Anti-Aliasing A Post-Processed Scene15.2

A Big List of all the Post Effects (currently) Available in three.js15.3

Sound in a 3-Dimensional World16

The WebAudio API16.1

Positional Sound16.1

References and Resources

YOUR FIRST three.js SCENE: HELLO, CUBE!

In this chapter, we’ll create a bare-bones three.js app. We’ll introduce quite a bit of theory along the way, but the actual code is short - just 18 lines without the comments.

// Get a reference to the container element that will hold our scene
constcontainer=document.querySelector('#scene-container');// create a Scene
constscene=newTHREE.Scene();// Set the background color
scene.background=newTHREE.Color('skyblue');// Create a Camera
constfov=35;// AKA Field of View
constaspect=container.clientWidth/ container.clientHeight;constnear=0.1;// the near clipping plane
constfar=100;// the far clipping plane
constcamera=newTHREE.PerspectiveCamera(fov,aspect,near,far);// every object is initially created at ( 0, 0, 0 )
// we'll move the camera back a bit so that we can view the scene
camera.position.set(0,0,10);// create a geometry
constgeometry=newTHREE.BoxBufferGeometry(2,2,2);// create a default (white) Basic material
constmaterial=newTHREE.MeshBasicMaterial();// create a Mesh containing the geometry and material
constmesh=newTHREE.Mesh(geometry,material);// add the mesh to the scene
scene.add(mesh);// create the renderer
constrenderer=newTHREE.WebGLRenderer();renderer.setSize(container.clientWidth,container.clientHeight);renderer.setPixelRatio(window.devicePixelRatio);// add the automatically created <canvas> element to the page
container.appendChild(renderer.domElement);// render, or 'create a still image', of the scene
renderer.render(scene,camera);

Open up the
empty template on Codesandbox.io (or your local equivalent) now, and we’ll get started.

The Components of a Real-Time 3D App

Every three.js app (in fact, nearly every real-time 3D app) will have the following basic components:

a Scene - this is a holder for everything else. You can think of it as a “tiny universe” in which all your 3D objects live.

a Camera - this is directly equivalent to the concept of a camera in the real world, and you’ll use this to view your scene.

a <canvas> - this is an
HTML canvas element, and just like a canvas in the real world, it starts out as a blank rectangle, just waiting to hold your beautiful creations!

a Renderer - this is a machine that takes a Camera and a Scene as input and outputs beautiful drawings (or renderings) onto your <canvas>.

Together, the Scene, Camera, <canvas>, and Renderer give us the scaffolding of an app, however, none of them can actually be seen. We’ll also need to add some kind of visible object, and in this chapter, we’ll add a simple box-shaped Mesh, which has three components.

a Mesh - this is just a holder for a Geometry and a Material and defines the position in 3D space. We can add this to our Scene.

a Geometry - this defines the shape of our Mesh.

a Material - and this defines the way that the surface of the Mesh looks.

What Do We Mean by Real-Time Graphics?

The world of 3D computer graphics is roughly divided into two sides.
On one side, we have pre-rendered graphics. This means things like special effects, fully animated 3D films like Toy Story, or cut scenes in games. It doesn’t matter how long a frame takes to render here. For some cutting-edge special effects, a single frame may take hours or even days to create! A Hollywood special effects studio may have hundreds of computers (a render farm), each working on a single frame, or even just part of a frame. These frames will then all be combined into the final animation.

We’re concerned with real-time graphics here. Instead of hundreds of powerful computers, we may be trying to display our scene on a single smartphone. Not only that, but we want the viewer to be able to interact with the scene - perhaps control a character in a game, view a product from multiple angles, or update an animation as they scroll down the page - and we want to do this, ideally, at a smooth 60 frames per second.

So, instead of having the luxury of spending hours on each frame, we need to listen for feedback from the user, update any animations, perhaps calculate physics and play sound effects, and then draw our scene onto the screen - and we want to do this all 60 times every second on an old phone. Obviously, we are much more constrained here. But throughout this book, we’ll discover a huge range of techniques for achieving this without sacrificing too much visual quality.

Our First three.js App

Now we are finally ready to write some code! We’ll divide our app up into 6 steps:

… and we’ll use querySelector to get a reference to that container in JavaScript

// Get a reference to the container element that will hold our scene
constcontainer=document.querySelector('#scene-container');

Even the most basic of apps requires some setup. We already covered the index.html and main.css files in the previous chapter, so we just need to add the JavaScript here. Turn your attention to app.js, which should currently be empty.

In this simple app, our JavaScript for the setup step is a single line. We’ve created an HTML container element to hold our scene and we’ll need to know its width and height when we’re setting up the Camera and Renderer below, so we’ll store a reference to it in the variable container at the top of the file. Once we’ve done so we’ll be able to use it further down in our code.

2. The Scene

2.1 Create the Scene

constcontainer=document.querySelector('#scene-container');// create a Scene
constscene=newTHREE.Scene();// Set the background color
scene.background=newTHREE.Color('skyblue');

First, we’ll create the scene. We’re calling the Scene constructor to create a scene instance, and storing that instance in a variable also called scene.

2.2 Set the Background Color

Set the scene’s background color

constcontainer=document.querySelector('#scene-container');// create a Scene
constscene=newTHREE.Scene();// Set the background color
scene.background=newTHREE.Color('skyblue');

We’ve also set the background color to a light blue color. As with the scene, this color was also created using a constructor, in this case, the Color constructor, and we’ve passed in as a parameter the word ‘skyblue’, which can be any of the
CSS color names.

Aside from writing the name of the color, there are quite a few other ways of defining colors, as we’ll see soon.

Hang on! What’s a Constructor?

A constructor is something that is used to create a new object. You will call it with the new keyword, and by convention, the name will always start with a capital letter.

The Scene (capital letter) is a constructor for a class, and when you call it with new you’ll get an instance of that class. We’ll store a reference to this instance in a variable which we’ll call scene (small letter) so that we can use it again later.

…and What’s a Parameter?

A parameter is some kind of information that we can give to the constructor to tell it something about the object that we want to create.

Each constructor takes different parameters. For example, the constructor for a box-shaped geometry might take information about the length, width, and height of the box, while a constructor for a sphere shaped geometry would take information about the radius of the sphere, and the constructor for the scene that we just created takes no parameters at all.

The scene instance that we’ve just created is used to make a data structure called a
scene graph. We’ll go over the technical details in of this in Chapter 2.5, but for now, just think of the scene object you have created as a holder for all the visible objects you want to display.

Once we have created an object, if we want to see it we’ll need to add it to the scene using scene.add( object ) and if we later want to remove it from the scene, we can just do scene.remove( object ). Simple!

We’ll need a camera to actually view the scene though. Let’s make one now.

3. The Camera

We’ll make the camera in two steps. First, we’ll call the constructor to create a camera instance, just as we did with the scene in step 2. The only difference is that the camera’s constructor does take several parameters, as we’ll see in just a moment. Next, we’ll need to move the camera back a bit to view the scene, and we’ll take the opportunity to briefly introduce positioning objects in 3D space and the three.js
coordinate system.

There are a couple of different cameras available, but we’ll generally stick with the PerspectiveCamera which uses perspective projection to set up a view of the scene.

Without going into any great detail here, perspective projection renders the scene in the way you expect a 3D scene to look - in other words, it mimics the way the human eye sees the world, with objects getting smaller the further away they are from the camera. Nearly all 3D games and special effects in films use a perspective camera.

The other important type of camera is the OrthographicCamera. This uses
orthographic projection, which means that objects don’t get smaller as they get further away. This is often used for 2D games, or for user interfaces drawn on top of a 3D game (or 3D website). We’ll investigate both of these camera types in detail in Section 3: Cameras.

3.1 Create the Camera

Add the following code to create a camera

scene.background=newTHREE.Color('skyblue');// Create a Camera
constfov=35;// AKA Field of View
constaspect=container.clientWidth/ container.clientHeight;
constnear=0.1;// the near clipping plane
constfar=100;// the far clipping plane
constcamera=newTHREE.PerspectiveCamera(fov,aspect,near,far);// every object is initially created at ( 0, 0, 0 )
// we'll move the camera back a bit so that we can view the scene
camera.position.set(0,0,10);

The PerspectiveCamera’s constructor takes four parameters, which together define its
viewing frustum.

The Camera’s Viewing Frustum

A frustum is a mathematical term meaning a four-sided rectangular pyramid with the top cut off. When we view the scene with our camera, everything inside the frustum is visible, everything outside it is not. In the following diagram, the area in between the Near Clipping Plane and the Far Clipping Plane is the frustum.

The four parameters that we passed into the constructor were used to create this shape. Let’s take a look at each of them now.

Field of View (FOV)

The .fov or Field of View parameter defines the angle of the viewing frustum, that is, how much of the world can be seen through the camera. It’s specified in degrees (as we’ll see soon, this is an exception; most angles in three.js are expressed in radians). Another way to think of this is that the .fov parameter defines how much bigger the far clipping plane will be than the near clipping plane. The valid range for the FOV is from 1 to 179 degrees.

As a human with eyes on the front of your head, you have a smaller FOV than an antelope with eyes on the side of its head. A human’s FOV is about 120 degrees, so for a realistic FOV in your app, you will need to consider how much of this field of view the screen will be likely to take up. Console games designed to be shown on screens far away from the viewer are usually between 40 - 60 degrees, while a PC game might use a higher FOV of around 90 since the screen is likely to be right in front of the player.

You don’t have to be especially accurate here - it’s a pretty huge TV that would take up 60 degrees on the other side of the room, and there’s usually no way to know in advance what kind of screen your app will be viewed on, so this is just a rough figure.

Aspect Ratio

The .aspect ratio is the width divided by the height of the viewing rectangle. An aspect ratio of 1 will be a square, while window.innerWidth / window.innerHeight will be a rectangle with the same proportions as your current browser window (minus the portion at the top with the URL and browser controls). We’ve created a <container> element to hold our scene, and we’re using the width and height from that to calculate the aspect ratio.

The aspect ratio needs to match the ratio of the actual HTML <canvas> element that we are viewing our scene in. If we set this an incorrect value then our scene will look stretched and blurred.

Clipping Planes

The .near parameter defines the near clipping plane. Objects closer to the camera than .near will not be visible. For a PerspectiveCamera, the near plane must be greater than 0, and less than the .far plane. This defines the smaller end of the frustum pyramid shape in the diagram above.

The .far parameter defines the far clipping plane. Objects further away from the camera than this will not be visible. For a PerspectiveCamera, this can be anything bigger than the .near plane. In practice, to make your scene efficient you will want this to be as small as possible. This defines the larger “base” of the frustum.

The goal is to make .near as big as possible, and .far as small as possible while keeping far bigger than .near. This way you can make the area contained in the frustum as small as possible, which is important for efficiency as we chase that elusive goal of 60 frames per second.

3.2 Position the Camera

We need to move the camera back a bit so that we can see the scene

// Create a Camera
constfov=35;// AKA Field of View
constaspect=container.clientWidth/ container.clientHeight;
constnear=0.1;// the near clipping plane
constfar=100;// the far clipping plane
constcamera=newTHREE.PerspectiveCamera(fov,aspect,near,far);// every object is initially created at ( 0, 0, 0 )
// we'll move the camera back a bit so that we can view the scene
camera.position.set(0,0,10);

By default everything that we create is put at the point (0,0,0). This is called the origin and it’s the point where:

$$
\begin{aligned}
x=0 & \cr
y=0 & \cr
z=0 &
\end{aligned}
$$

The camera that we created just now is positioned at (0,0,0), and any objects that we add to the scene will also be positioned at (0,0,0), by default.

This means that we need to move the camera back a bit to let us see the scene. You can also think of the point that the camera is placed at to be the dividing line between your screen, and the 3D scene that you are creating.

Setting the camera’s position like this:

camera.position.set(0,0,10);

…is directly equivalent to setting the components individually like this:

camera.position.x=0;camera.position.y=0;camera.position.z=10;

Since this is the first time that we’ve had to take the position of an object into account, let’s take a few moments to understand the three.js coordinate system.

The three.js Coordinate System

The above diagram shows the three.js coordinate system, along with the camera we just created, and your screen. The point where the x-axis, y-axis, and z-axis meet is (0, 0, 0). As we just mentioned, this is called the origin, and we’ve moved our camera to the point (0, 0, 10) - ten units towards us.

Take note of the direction of the axes relative to the camera and your screen.

+X points to the right of the screen

-X points to the left of the screen

+Y points to the top of the screen

-Y points to the bottom of the screen

+Z points out of the screen (towards you)

-Z points into the screen (away from you)

With the line camera.position.set( 0, 0, 10 ), we’ve left the camera in the middle of the x-axis and y-axis, but moved it 10 units towards us along the z-axis.

The Other Web Coordinate System

In the CSS coordinate system, which you may be more familiar with, -Y points to the top of the screen and +Y points to the bottom.

Take care when converting between CSS and three.js coordinates!

Units in three.js

By convention, one unit on three.js is one meter. Since we’ve set our camera.far value to 100, we now have a scene sized at a scale of about 100 meters. This is a good size for a scene - actually, we could get quite a lot bigger without any problems. Avoid making very big scenes or very tiny scenes and you will be fine. The only time that this will be a problem is if you decide to make a physically accurate space simulation.

Of course, 1 unit = 1 meter is just a convention. It’s useful when sharing models between different scenes, artists and applications. But you don’t have to follow it if it doesn’t suit you. Just make sure that your entire workflow, between all applications being used and all people working on your project, is consistent.

4. Visible Objects

We’ve created a camera to see things with, and a scene to put them in. The next step is to create something to see! We’ll start by making a simple white cube.

To do this, we’ll use a kind of object called a
Mesh, which consists of three components - a
Geometry (or rather a
BufferGeometry, but more on that below), a Material, and the Mesh itself, which is a holder for the geometry and material and is used to set the position of the object inside the scene. We’ll make the geometry and material first, and then create the mesh to hold them.

Meshes are the most common kind of visible object used in 3D computer graphics, and are used to display all kinds of 3D objects - cats and dogs and humans and trees and buildings and flowers and mountains can all be represented using a Mesh.

There are other kinds of visible objects - lines and shapes and sprites and particles and so on - and we’ll see all them in later sections, but to keep things simple we’ll stick with Mesh throughout this section.

4.1 Create a Geometry

Add the following line after the camera to create a box-shaped buffer geometry:

The geometry of an object defines its shape. If we create a box-shaped geometry (like we are doing here), our mesh will be shaped like a box. If we create a sphere shaped geometry, our mesh will be shaped like a sphere. If we create a cat-shaped geometry, our mesh will be shaped like a cat, and so on.

We’re creating our cube using a kind of geometry called a BoxBufferGeometry. The three numbers that we have passed in as parameters define the width, height, and depth of the box, respectively.

Most objects in three.js have built-in defaults, so even though the docs say that BoxBufferGeometry should take 6 parameters, we can get away with ignoring most or all of them and three.js will fill in the blanks with default values.

In this case, if we were to not pass in any parameters:
const geometry = new THREE.BoxBufferGeometry() - we would get default box that is 1 x 1 x 1 - one unit high, one unit wide, and one unit deep. That’s a bit small for us now though, so we’re passing in bigger size parameters to create a 2 x 2 x 2 box.

BufferGeometry?

Wait, why did we just create a BoxBufferGeometry instead of just a
BoxGeometry?

Well, that’s a bit beyond the scope of this section, so for now, we’ll just say that
BufferGeometry is a newer and faster version of
Geometry, and you should (almost) always use a BufferGeometry rather than a Geometry.

We’ll spend all of Section 6: Exploring Geometry to exploring how geometries and buffer geometries work.

Now that we’ve created a geometry… ahem, buffer geometry, that is… we’ve given our object a shape.

The next thing we need to do is describe how it looks, and for that, we need a material.

Materials define the surface properties of objects - that is, which material that the object looks like it is made from. Where the geometry tells us that the mesh is a box, or a car, or a cat, the material tells us that it’s a metal box, or a stone car, or a red-painted cat.

There are quite a few materials in three.js. To start with, we’ll create a MeshBasicMaterial, which is the simplest (and fastest) material type available in three.js. It completely ignores any lights in the scene and just shades a mesh based on its color or any texture maps (which we’ll explain in Ch 1.4: A Brief Introduction to Texture Mapping). This is useful since we have not yet added any lights.

You (Usually) Need a Light to See Anything

If we used most of the other material types right now we wouldn’t be able to see anything at all since the scene is in total darkness. Just as in the real world, we usually need light to see things in our scene. MeshBasicMaterial is an exception to that rule.

This is a common point of confusion when you are just getting started, so if you can’t see anything make sure you have added some lights to your scene and they are shining on the object that you expect to see, or temporarily switch to a MeshBasicMaterial.

Remember what I said about three.js assigning sensible default values if you don’t provide them yourself? Well, we haven’t passed in any parameters to the material so everything is set to default, which in this case means that the material will be white. We’ll see how to change the material’s color in the next chapter.

The Mesh object is one of the most important and basic objects in three.js and you will be using it a lot. Thankfully, just like the Scene object, it’s very simple and its main function is to hold a geometry and a material and tell us where in the scene they are located.

The next step is to create the renderer, which is a machine that takes your scene and camera as inputs and outputs pretty pictures onto the <canvas> element in your HTML page.

Although a number of renderers are in available in three.js, we’ll be concentrating on the WebGLRenderer throughout this book since it’s the most full-featured and powerful.

5.1 Create the Renderer

Create a WebGLRenderer with default settings

scene.add(mesh);// create the renderer
constrenderer=newTHREE.WebGLRenderer();renderer.setSize(container.clientWidth,container.clientHeight);renderer.setPixelRatio(window.devicePixelRatio);// add the automatically created <canvas> element to the page
container.appendChild(renderer.domElement);

We’re creating a WebGLRenderer with default settings for now since we’re not passing in any parameters to the constructor. As usual, three.js will take care of setting up sensible defaults for any of the parameters that we do not specify.

5.2 Set the Renderer’s Width and Height

We previously created a container DIV to hold our canvas

<divid="scene-container"><!-- This div will hold our scene--></div>

..and we set the width and height of the container in CSS, so that it fills the full <body> element.

#scene-container{position:absolute;width:100%;height:100%;}

Once we’ve done that, we can tell the renderer to set the canvas to the same size as the container

// create the renderer
constrenderer=newTHREE.WebGLRenderer();renderer.setSize(container.clientWidth,container.clientHeight);renderer.setPixelRatio(window.devicePixelRatio);// add the automatically created <canvas> element to the page
container.appendChild(renderer.domElement);

The renderer has automatically created a <canvas> element to draw the scene onto. We’ll add this to our page in a moment, but first, let’s make sure that everything is set up the way that we need it.

The automatically created <canvas> has a default size, although this time it’s set by the browser rather than three.js. Currently on Chrome that default size is 150 x 300 pixels, which is rather small, so let’s tell the renderer the size that we do want.

The approach we’re taking to set up the canvas here is:

create an HTML <div class="container"> element to hold the <canvas>

set the size of the container using CSS (in this case, we’ve set it to the full window)

get a reference to this element in JavaScript as the variable container

5.3 Set The Renderer’s Pixel Ratio

We need to set the correct pixel ratio for the device our app is running on

// create the renderer
constrenderer=newTHREE.WebGLRenderer();renderer.setSize(container.clientWidth,container.clientHeight);renderer.setPixelRatio(window.devicePixelRatio);// add the automatically created <canvas> element to the page
container.appendChild(renderer.domElement);

We mentioned back in Ch 0.10: Functions Built-In to Your Browser: the Web API that mobile devices draw your web page onto a virtual viewport, which may not have the same resolution as the device’s physical screen. Then, once the page has been drawn, it gets scaled down to fit onto the device’s physical screen. The pixels on this virtual viewport are referred to as CSS pixels, while the real pixels on the actual screen are referred to as physical pixels.

It’s common for the virtual viewport to be much larger than the real viewport - common ratios are 2x, 3x, 4x or even 5x! This allows for much sharper images to be rendered, which is important for viewing text on small screens.

That’s great, but when it comes to rendering WebGL, if we forget to take this into account then our scenes may look great on our development laptops but will look blurry and low-resolution on mobile devices. So, we need to tell the renderer what the pixel ratio is, which is as simple as adding the above line.

5.4 Add the Canvas Element to Our Page

We’ll append the canvas element as a child of the container

// create the renderer
constrenderer=newTHREE.WebGLRenderer();renderer.setSize(container.clientWidth,container.clientHeight);renderer.setPixelRatio(window.devicePixelRatio);// add the automatically created <canvas> element to the page
container.appendChild(renderer.domElement);

Now we’re finally ready to add the <canvas> to our page.

As we mentioned above, when we created our WebGLRenderer, it automatically created an HTML <canvas> element for us. This element only exists in memory at first and is stored in renderer.domElement. To be able to view our scene, we’ll need to add this <canvas> to our webpage, inside the #scene-container div.

We’ll use a built-in browser method called appendChild to achieve this. Refer back to Ch 0.10: THE WEB API if this method is unfamiliar to you.

Note that we don’t have to use the <canvas> element that the renderer created for us. We can create one manually and tell the renderer to use that instead if we need greater control. For now though, this default one will do fine.

Once we’ve appended the canvas as a child of the scene container, if you take a look at the structure of the HTML in the browser console, you should see that the renderer has indeed appended the <canvas> as a child of the container. It has also set the width and heightCSS properties, and the width and heightattributes on the canvas for us.

6. Render the Scene

container.appendChild(renderer.domElement);// render, or 'create a still image', of the scene
renderer.render(scene,camera);

With this single line, we’re telling the renderer to take a still picture of the scene using the camera and output that picture into the <canvas> element.

You will now see your white cube being rendered against a blue background. It’s a bit hard to see that it’s actually a cube so far since we’re looking at it head-on. But we’ll improve that a lot in the next chapter.

Well done! You’ve completed the first chapter and taken your first giant leap in your career as a three.js developer.