Getting Into Three.js

Getting Into Three.js

The Making of Mars
Ferenc AlmasiLast updated 2021 November 11 • Read time 9 min read
Get your weekly dose of webtips
  • twitter
  • facebook
JavaScript

Three.js is a 3D JavaScript library that enables us to build beautiful 3D worlds right inside the browser. If you only know JavaScript but wanted your imagination to come to life on a canvas, now it’s the time.

With it, you don’t have to worry about physics and mathematical computations as every important thing is exposed through a simplified API.

If you visit the official website of three.js, you can see a great variety of examples of what is truly possible with the library.

examples on the official three.js library home page
Different examples on threejs.org

With the help of three.js, we will dive into the basics of 3D, and at the end of the tutorial, you will achieve something like this:

The end result of the tutorial

Setting Up Three.js

Before we can start creating the world, we need a canvas to display anything. Add the following index file to your project root:

Copied to clipboard! Playground
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <meta http-equiv="X-UA-Compatible" content="ie=edge" />
        <title>Three.js - Mars</title>
        <style>
            body {
                margin: 0;
                overflow: hidden;
            }

            canvas {
                width: 100%;
                height: 100%;
            }
        </style>
    </head>
    <body>
        <script src="https://threejs.org/build/three.js"></script>
        <script src="mars.js"></script>
    </body>
</html>
index.html

I pulled in the library from the official threejs.org site. Everything we implement will go into mars.js, so you can create an empty file next to index.html at this stage.

You might have also noticed that there is no canvas element in the document. We don’t need to specify one as we will create it inside our script. I also added a minor styling just to make the canvas fullscreen.

Now to display anything on the canvas, we need three things: a scene, a camera, and a renderer. To create all three of them, add the following lines to your empty mars.js file:

Copied to clipboard!
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
mars.js

First, we have the scene.

Scene

The scene is just a space where you can place all kind of objects like geometries, lights or cameras. Inside the scene, you can tell three.js where to display these objects. Next up we have the camera.

Camera

In this example, we are using the PerspectiveCamera of three.js which takes in four parameters:

The first parameter is the field of view or FOV for short. It is the visible portion of the scene that is seen by our camera, expressed in degrees.

field of view or visible area
Field of view / visible area

The second parameter is the aspect ratio. You almost always want it to be set to the width of the canvas divided by its height. Otherwise, you’ll get a distorted image.

Differences between aspect ratio
Aspect ratio set to 1, width/height, 2

Lastly, we define the near and far clipping planes. This means that everything closer to the camera than the first number won’t be rendered. The same applies to the second number. Everything further from the camera than the specified value won’t be rendered.

Renderer

And then we have the renderer. We will use it to render everything into the scene. Three.js comes with a few built-in renderer. Usually, you would go with WebGL, others are often used for older browsers as fallbacks.

To finally add the canvas to the document, extend mars.js with the following two lines:

Copied to clipboard!
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
mars.js

Here we tell the renderer to use the available width and height for the canvas element and then we can append it to the body. If you open this in a browser, you’ll see a black empty canvas. Let’s add the planet now!


Adding Mars to the Scene

To create the planet, we need three things again:

  • A geometry which defines the shape of the object
  • A material —The look and feel of the geometry, such as color and texture
  • A mesh which is a combination of a geometry and a material which can be later added to the scene

Let’s add the following to our script:

Copied to clipboard! Playground
const geometry = new THREE.SphereGeometry(1, 32, 32);
const material = new THREE.MeshPhongMaterial();
const mesh = new THREE.Mesh(geometry, material);

scene.add(mesh);
mars.js

First, we create a sphere by calling SphereGeometry. The first argument in the function call is the radius, the second and the third are the width and height segments of the sphere. Notice how we get a more fine-grained sphere as I increase their values up to 32:

changing the parameters of SphereGeometry

For material, we are using MeshPhongMaterial. All you need to know now is that this way the sphere is going to be affected by light. Unlike if we were to use a MeshBasicMaterial for example.

To create a mesh, we simply pass geometry and material to Three.Mesh and add it to our scene calling scene.add.

Right at this stage, if you refresh the browser, you’ll still see nothing. This is because we are missing three more things once more:

Rendering the scene

The most important one is that we did not tell three.js to render the scene. For this, we need a render loop:

Copied to clipboard! Playground
const animate = () => {
    requestAnimationFrame(animate);
    renderer.render(scene, camera);
};

animate();
mars.js

We can use requestAnimationFrame which will result in 60fps for most screens. To render out everything we need to call renderer.render with our scene and our camera.

Zooming out

There are two more obstacles preventing us from seeing anything. First, we don’t have a light source, which means everything is pure black. Second, the position of our camera is inside the center of the sphere. So even if we were to have a light source, we would still see nothing. So first, let’s move the camera back a little bit:

Copied to clipboard!
camera.position.z = 3;
mars.js
Initial vs configured position of the camera
By default, the camera is inside the sphere
Moving the camera out of the sphere

Let there be light

To finally see something, let’s extend mars.js with these three lines:

Copied to clipboard!
const light = new THREE.DirectionalLight(0xcccccc, 1);

light.position.set(5, 3, 5);
scene.add(light);
mars.js

We add a new DirectionalLight, setting its color to #ccc and its intensity to 100%. By default, its direction is set to 0, 1, 0, which means 1 on the y axis and 0 on the x and z axis. We want this to be set to 5, 3, 5 to achieve the same effect we usually see for planets in movies.

Adjusting the x, y and z axis of the directional lighting
Adjusting the x, y and z-axis of the directional light
Looking to improve your skills? Check out our interactive course to master JavaScript from start to finish.
Master JavaScriptinfo Remove ads

Adding Materials

To make the sphere look like Mars, we need to add some textures. I used two images to get the end result. One for the diffuse map and one for the bump map. Diffuse map is used for the color and texture of the planet while the bump map is used for creating the uneven surface, making the mountains stand out and indent the trenches and canals.

I will provide a GitHub link for the whole project at the end of the article with textures included, but you can also download the images from the following sources:

Create a new folder in your project structure called “textures” and put the images there. Then extend the script file with the following three lines:

Copied to clipboard!
material.map = new THREE.TextureLoader().load('textures/diffuse.jpg');
material.bumpMap = new THREE.TextureLoader().load('textures/bump.jpg');     
material.bumpScale = 0.015;
mars.js

Note that in order to load local files, you need to run a webserver. I’ve been using the http-server package throughout this tutorial.

We set these for the MeshPhongMaterial we created earlier for the planet. I’ve found that using 0.015 for the bumpScale works best. Otherwise, the effect is too strong.

bumpScale reduced to 0.015
Initial value of bumpScale vs 0.015

Lastly, let’s add the stars. For that, I’ve used this nice image from Jake Weirick on Unsplash. We can create another mesh for it, using MeshBasicMaterial:

Copied to clipboard! Playground
const starsGeometry = new THREE.SphereGeometry(4, 32, 32);
const starsMaterial = new THREE.MeshBasicMaterial();
const starsMesh = new THREE.Mesh(starsGeometry, starsMaterial)

starsMaterial.map = new THREE.TextureLoader().load('textures/stars.jpg');
starsMaterial.side = THREE.BackSide;

scene.add(starsMesh);
mars.js

To achieve the stars effect we use another sphere, but this time, with a larger radius set to 4. You also want only the backside of the sphere to be rendered. This is what we do by setting starsMaterial.side. If we zoom all the way out, you will see that essentially, we wrap the whole planet inside a bigger sphere. And it has the texture of the stars.

We can see the shape of the stars by zooming out
We can see the shape of the sphere by zooming all the way out

To wrap everything up, let’s add some animations.


Adding Interactivity

First, let’s add a small rotation to the planet. Extend the animate function with the following lines:

Copied to clipboard! Playground
const animate = () => {
    requestAnimationFrame(animate);
    renderer.render(scene, camera);
    
    mesh.rotation.y -= 0.001;
};

animate();
mars.js

We also want to move the camera around the planet as the user moves around their cursor. For this, we can add the event listener below to the document:

Copied to clipboard!
document.addEventListener('mousemove', (e) => {
    camera.position.x = (e.x - (window.innerWidth / 2)) * 0.005;
    camera.lookAt(scene.position);
});
mars.js

The original x position of the camera is set to 0. When we move the mouse to the left side we want to decrease the value, and when we move the mouse to the right, we want to increase it.

The problem is that e.x which holds the horizontal position of the mouse, starts from 0 at the left edge of the screen and goes all the way up until it reaches the max-width. There’s no negative value. We want to begin with 0 in the middle of the screen. This is why we negate half of the screen’s width from e.x. To reduce the scale of the movement, I also multiplied the number by 0.005. This way, we only have a small amount of movement. And to keep the camera looking at our planet, we need to call camera.lookAt.

Calculating the x position of the camera

And now whenever the user moves their cursor around the planet, the camera changes with it:

The end result of the tutorial

If you would like to deepen your knowledge in Three.js, I highly suggest going through both the examples and the official documentation on threejs.org.

You’ll not only find throughout documentation about its API, but multiple step by step tutorials on how to get started, draw lines, create text and animations, load 3D models and so much more.

Hope you learned something new and useful. To get the source code of the final project, visit my Threejs repository on Github. 🪐 Thank you for taking the time to read this article, happy coding!

  • twitter
  • facebook
JavaScript
Did you find this page helpful?
📚 More Webtips
Frontend Course Dashboard
Master the Art of Frontend
  • check Access 100+ interactive lessons
  • check Unlimited access to hundreds of tutorials
  • check Prepare for technical interviews
Become a Pro

Courses

Recommended

This site uses cookies We use cookies to understand visitors and create a better experience for you. By clicking on "Accept", you accept its use. To find out more, please see our privacy policy.