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
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>
Let's select this canvas through javascript.
const canvas = document.querySelector('#threejs-canvas');
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();
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);
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' });
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);
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);
After running that again, there's still no cube:
This is because we need to render our scene.
const renderer = new WebGLRenderer({ canvas });
// Dimensions of the canvas
renderer.setSize(800, 600);
There are various renders but WebGLRenderer
is what we need. Finally, we can see our cube:
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;
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);
If we run the code, we get something like this:
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);
Now, we get a nice and smooth sphere:
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);
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.
const geometry = new TorusGeometry(1, 0.2, 32, 32);
const material = new MeshBasicMaterial({ color: 'cyan' });
const torus = new Mesh(geometry, material);
scene.add(torus);
There, it looks much better:
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);
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);
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);
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
Next, we need to initialize the GUI:
import { GUI } from 'dat.gui';
const gui = new GUI();
Now we can see a panel at the top right:
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);
Here's how it looks:
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');
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.
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');
After changing our label, here's how our GUI looks:
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 🤟