How to easily get started with ThreeJS - Part 2

How to easily get started with ThreeJS - Part 2

ยท

6 min read

Hi guys, hope you are fine! :)

I'm back after posting the first part of this series about how to get started on Three.js without pain. If you haven't done it yet, you can read the first part here ๐Ÿ‘‡๐Ÿผ

Small recap

Last time we finished with our canvas containing a red 3D cube rotated by a little bit in order to be able to see some angles.

Cube at last step of part one

This is the full script.js file that you should have if you have followed the steps in the part one:

// script.js
import * as THREE from "three";

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight);
camera.position.z = 3;
scene.add(camera);

const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const mesh = new THREE.Mesh(geometry, material);
mesh.rotation.y = 0.5;
scene.add(mesh);

const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.render( scene, camera );

document.body.appendChild(renderer.domElement);

Today we will see how to animate our cube, how to change its geometry and its material (with a special technique). At the end of the series you will have a demo that should look just like this: https://th3wall-threejs.netlify.app

Animating the cube

In order to animate stuff, what we need to do is take a picture of each frame. But, how can we do that? ๐Ÿค”

We will use the requestAnimationFrame (also called RAF), which lets you trigger a function on each frame. This has to do with plain JS and it's not specific to ThreeJS.

First of all, inside the same script.js file, we need to create a function and call it once:

const animate = () => {
  // empty function
};
animate();

The second step is to move the render inside this function:

const animate = () => {
  renderer.render( scene, camera );
};
animate();

Now it's time to insert the requestAnimationFrame inside the animate function, passing the same animate function to it as a parameter.

RAF method takes a callback function as a parameter, that will be called on the next frame (once the screen is ready to accept the next screen repaint), and on the next, and on the next.....

const animate = () => {
  window.requestAnimationFrame(animate);

  renderer.render( scene, camera );
};

โš ๏ธ You may be wondering why not use a setTimeout/setInterval called repeatedly: when we call requestAnimationFrame() to create an animation, we are assured that our animation code is called when the user's computer is actually ready to make changes to the screen each time, resulting in a smoother, more efficient animation than if we used a setTimeout or a setInterval repeatedly.


How can we make sure that everything is working? Let's rotate our object!
To do that, we will increment the rotation of the mesh by a very small amount, let's try with 0.01:

const animate = () => {
  window.requestAnimationFrame(animate); 

  mesh.rotation.y += 0.01;

  renderer.render( scene, camera );
};

And here we go: our cube is now rotating! Cube rotating for the first time

Change the geometry

Now that our object is animated, we can change its geometry. On the ThreeJS documentation, you can find a lot of available geometries with which you can play and experiment with a lot of different things. One of my favorites (same as Bruno ๐Ÿ˜) is the TorusKnowGeometry, which consists of a sort of "infinite" knot with a lot of rounded surfaces.

We can create a torus knot by replacing the geometry declaration line from BoxGeometry to TorusKnotGeometry. Obviously, by changing the shape of our object, we need to adjust its parameters accordingly.

For the Torus Knot, the parameters we will need to remember for our purposes are:

  • radius - Defines the radius of the torus [default is 1];
  • tube โ€” Defines the radius of the tube [default is 0.4];
  • tubularSegments โ€” Defines the number of tubular vertices [default is 64];
  • radialSegments โ€” Defines the number of radial vertices [default is 8]

Here are some values we throw in

// replace this line
const geometry = new THREE.BoxGeometry(1, 1, 1);

// with this one
const geometry = new THREE.TorusKnotGeometry(0.5, 0.2, 200, 30);

and the result will be this one: First Torus Knot

Much better than the cube I would say, isn't it? I personally love this geometry!

Change the material

The geometry looks much better and evolved than before, but honestly, with this flat red color it's a little messy and the curves of the geometry aren't really clear to see. So we need to change the material now.

There are so many materials included in ThreeJS and you can also create your own material with something called shaders.
Shaders are a BIG and quite hard topic to learn so we are not gonna use them for our purpose, but Bruno has hours of lessons also on this topic with his ThreeJS Course, a must for every creative developer.

What we are gonna use is a special technique called matcaps. Matcaps are a kind of material that is used to simulate the appearance of a material on a surface.

Firstly, we need to access to a matcap, and Bruno provided a link to one of yours in the video tutorial. I will use this one: Bruno's Matcap #3 Link: https://bruno-simon.com/prismic/matcaps/3.png

It's basically a picture of a sphere (you can create one yourself with programs like Photoshop) and you will use this picture as a texture for the geometry. Every pixel of this image will be applied to the geometry and will take the colors from every pixel of the image.

To use a matcap, we need to load what I've called texture and in order to do this, in ThreeJS we will use something called TextureLoader.

const textureLoader = new THREE.TextureLoader();

Then, we load our matcap image inside the loader providing it the URL and we assign it to a variable:

const matcapTexture = textureLoader.load("https://bruno-simon.com/prismic/matcaps/3.png");

Now that our texture is loaded, we need to apply it to the material as last step. The MeshBasicMaterial method is unable to use a matcap as a texture, so we need to use a different method called MeshMatcapMaterial (ref: MeshMatcapMaterial Docs) and then we can provide it our texture contained in the variable matcapTexture.

// Replace this line
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });

// With this one
const material = new THREE.MeshMatcapMaterial({ matcap: matcapTexture });

And finally, if you now preview the scene you will see this beauty MeshMatcapMaterial applied

Want to add a little bit of movement?
Let's add also a vertical rotation to our torus knot. Inside the animate function, together with the horizontal movement, we add this line:

mesh.rotation.x += 0.01;

Now you can enjoy this beautiful 3D animation along with this stunning matcap! Final Torus Knot effect

Final recap

In this part we have seen how to animate our object, how to change its geometry and how to change its material by using a special technique.

In the third and last part, we will apply some little smooth animations to our scene, we will make it responsive and we will style the page to look like this: https://th3wall-threejs.netlify.app.

As I did for the first part, I leave down here the full script.js code block so you can have the updated one:

// script.js

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight);
camera.position.z = 3;
scene.add(camera);

const textureLoader = new THREE.TextureLoader(); 
const matcapTexture = textureLoader.load("https://bruno-simon.com/prismic/matcaps/3.png");

const geometry = new THREE.TorusKnotGeometry(0.5, 0.2, 200, 30);
const material = new THREE.MeshMatcapMaterial({ matcap: matcapTexture });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);

const animate = function () {
  window.requestAnimationFrame(animate); 

  mesh.rotation.x += 0.01; 
  mesh.rotation.y += 0.01;

  renderer.render( scene, camera );
};
animate();

document.body.appendChild(renderer.domElement);

I hope you are learning something and I also hope that you find this article interesting.

Please follow me on Twitter, GitHub & DEV.to and let me know if you liked it!

Thanks.
Th3Wall

Did you find this article valuable?

Support Th3Wall's Blog by becoming a sponsor. Any amount is appreciated!

ย