Going 3 Dimensional With Three.JS

Akash Shyam - May 17 '21 - - Dev Community

We've all wanted to create those cool websites with those amazing moving shapes which look 3 dimensional. The technology used for this is called WebGL. It's great and gives us a lot of freedom and flexibility.

However, with this, comes a price. WebGL is complex and we need to write a lot of code for something as simple as a cube. Here's where three.js comes into play. Three.js adds a layer above WebGL while allowing almost the same amount of flexibility.

This is a the first post in a series of Three JS posts. Today, I'll show you how to create some basic shapes in three JS.

We can use Three JS through NPM or by a CDN.

npm i three
Enter fullscreen mode Exit fullscreen mode

We use the canvas element for our 3d figures. Canvas is used for drawing, it's in the name.

<canvas width="800" height="600" id="threejs-canvas"></canvas>
Enter fullscreen mode Exit fullscreen mode

Let's select this canvas through javascript.

const canvas = document.querySelector('#threejs-canvas');
Enter fullscreen mode Exit fullscreen mode

Scene

Firstly, let's begin by creating a Scene. A scene can be understood as the container for our 3d figures.

import { Scene } from 'three';

const scene = new Scene();
Enter fullscreen mode Exit fullscreen mode

Parts of Objects

A basic object in Three JS has 3 fundamental parts:

Geometry

It is the shape/outline of an object. Now, we'll create a geometry of a cube.

// Params are width & height
const geometry = new BoxGeometry(1, 1);
Enter fullscreen mode Exit fullscreen mode
Material

It is the color/texture of the object. We can add unique looks and appearances by combining colors and textures. I'll create a detailed post on this very soon.

const material = new MeshBasicMaterial({ color: 'cyan' });
Enter fullscreen mode Exit fullscreen mode

We can use various color formats like hexadecimal, rgb, hsl etc.

Mesh

It is used to combine the geometry and the material. We can also use it for rotations, scaling, transformations etc.

const cube = new Mesh(geometry, material);
Enter fullscreen mode Exit fullscreen mode

If you run the code, you'll see a black background but no cube. We may have created the cube but we have not added the cube to the scene.

scene.add(cube);
Enter fullscreen mode Exit fullscreen mode

After running that again, there's still no cube:

Screenshot 2021-05-16 at 20.32.42

This is because we need to render our scene.

const renderer = new WebGLRenderer({ canvas });

// Dimensions of the canvas
renderer.setSize(800, 600);
Enter fullscreen mode Exit fullscreen mode

There are various renders but WebGLRenderer is what we need. Finally, we can see our cube:

Screenshot 2021-05-16 at 20.36.40

Controls

I think it be great if we could look around the cube i.e. view it from other angles. We can do this by changing the position of the camera. We could implement this on our own but three JS gives us a class we can use.

const controls = new OrbitControls(camera, canvas);

// Adding easing for better UX
controls.enableDamping = true;
Enter fullscreen mode Exit fullscreen mode

Now, let's try some other shapes:

Sphere

// The first argument is the radius
const geometry = new SphereGeometry(1);
const material = new MeshBasicMaterial({ color: 'cyan' });
const sphere = new Mesh(geometry, material);

scene.add(sphere);
Enter fullscreen mode Exit fullscreen mode

If we run the code, we get something like this:

Screenshot 2021-05-16 at 20.47.49

As you can see, the edges are jagged. People who have used 3d software tools like blender, 3d max etc will know why this happens. This is because we lack detail on the sphere which is necessary for creating a smooth sphere. In fact, everything we create in three JS is made of triangles. So, we want to increase the number of triangles(or segments according to the docs).

It's important that we don't go overboard with this because the amount the GPU has to compute is directly proportional to the number of segments. I'd recommend increasing the value slightly until most of the sharp edges disappear. The controls in most cases are used for debugging and the user will probably have a fixed camera angle.

const geometry = new SphereGeometry(1, 32, 32);
const material = new MeshBasicMaterial({ color: 'cyan' });
const sphere = new Mesh(geometry, material);

scene.add(sphere);
Enter fullscreen mode Exit fullscreen mode

Now, we get a nice and smooth sphere:

Screenshot 2021-05-16 at 21.00.29

Torus

Most of the code is the same, we just need to change the class:

// Params is the radius of the torus, radius of inner circle
const geometry = new TorusGeometry(1, 0.2);
const material = new MeshBasicMaterial({ color: 'cyan' });
const torus = new Mesh(geometry, material);

scene.add(torus);
Enter fullscreen mode Exit fullscreen mode

If we execute the code, we can see that the torus is lacking some detail(same issue we had with the sphere). So, let's add some more segments to this.

Screenshot 2021-05-16 at 22.25.40

const geometry = new TorusGeometry(1, 0.2, 32, 32);
const material = new MeshBasicMaterial({ color: 'cyan' });
const torus = new Mesh(geometry, material);

scene.add(torus);
Enter fullscreen mode Exit fullscreen mode

There, it looks much better:

Screenshot 2021-05-16 at 22.30.41

Cone

// Radius of bottom, height
// I've added segments to give it a smooth texture
const geometry = new ConeGeometry(1, 2);
const material = new MeshBasicMaterial({ color: 'cyan' });
const cone = new Mesh(geometry, material);

scene.add(cone);
Enter fullscreen mode Exit fullscreen mode

Here's how it looks:
Screenshot 2021-05-16 at 22.40.25

As you can see, we have the segment problem again. Let's fix that:

const geometry = new ConeGeometry(1, 2, 32, 32);
const material = new MeshBasicMaterial({ color: 'cyan' });
const cone = new Mesh(geometry, material);

scene.add(cone);
Enter fullscreen mode Exit fullscreen mode

And our nice looking cone:
Screenshot 2021-05-16 at 22.41.44

TorusKnot

This is an interesting shape and I think it's cool and unique.

const geometry = new TorusKnotGeometry(1, 0.2, 128, 128);
const material = new MeshBasicMaterial({ color: 'cyan' });
const torusKnot = new Mesh(geometry, material);

scene.add(torusKnot);
Enter fullscreen mode Exit fullscreen mode

Screenshot 2021-05-16 at 22.45.38

There are many more geometries that are available here in three JS.

Debug Panels

These come real handy when we want to make little changes to test out colors, positions, angles etc. I like using dat.gui for this. Let's install it:

npm i dat.gui
Enter fullscreen mode Exit fullscreen mode

Next, we need to initialize the GUI:

import { GUI } from 'dat.gui';

const gui = new GUI(); 
Enter fullscreen mode Exit fullscreen mode

Now we can see a panel at the top right:

Screenshot 2021-05-16 at 23.00.09

We have a property called wireframe on our object. It removes the color/texture i.e. is the material and exposes the geometry(the outline of the object).

const geometry = new TorusGeometry(1, 0.2, 16, 16);
const material = new MeshBasicMaterial({});
const torus = new Mesh(geometry, material);

material.wireframe = true;

scene.add(torus);
Enter fullscreen mode Exit fullscreen mode

Here's how it looks:

Screenshot 2021-05-17 at 13.05.27

As you can see, there are a lot of criss-crossing lines. These are the segments(the triangles) of the shape.

Let's use this property in our debug GUI.

gui.add(material, 'wireframe');
Enter fullscreen mode Exit fullscreen mode

gui.add() takes in an object and wireframe is the property we want to toggle. The gui.add() method can take in various types of properties(the second argument which needs to be changed). We can use functions, booleans, numbers, ranges etc. One thing to note is that we have decided to change the wireframe property, if we specify something else(like jdfkdfjkd instead of wireframe), it won't work.

Here's how our GUI looks:
Screenshot 2021-05-17 at 13.18.03

As you can see, the label for the property is the property itself(we gave the second argument as wireframe and that is also used as the label).

// Set the label to "Name1"
gui.add(material, 'wireframe').name('Name1');
Enter fullscreen mode Exit fullscreen mode

After changing our label, here's how our GUI looks:
Screenshot 2021-05-17 at 13.25.20

Conclusion

Three JS is super interesting and I think this could be the way to go in the future! Thanks for reading until here. Next, I'll be writing about animating our figures by transformations, rotations etc. Bye for now 🤟

. . . . . . . . . . . . . . . . . . . . . . .