The contents of this article/summary are based off of the excelent course Threejs-Journey by Bruno Simon.
Author: Daniel Einars
Date Published: 25.10.2022
Date Edited: 25.10.2022
This part summarises my notes on all the basics regarding the use of threejs such as creating a scene, transforming objects, animations etc.
Before we do anything, we need to create a scene. This is essentially the container for everything else and you create it like this
const scene = new THREE.Scene()
Objects are things you render to the scene. They can be anything from simple shapes (pyramids, cubes, spheres etc.) to imported models, particles, lights etc. In order to create a simple box we need the geometry (shape) and the mesh (what the surface looks like)
const geometry = new THREE.BoxGeometry(1, 1, 1) // the 1, 1, 1 are the width, height and depth.
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 })
We then combine the geometry and material to create a mesh, which we then add to the scene
const mesh = new THREE.Mesh(geometry, material)
scene.add(mesh)
In order to actually see anything, we need a camera to view things. There are a bunch of different cameras for different purposes, but they all inherit from the base camera class (don't use this one, but one of the specifics). From the docs:
For now, we'll be using the perspective camera like this
const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 100)
The arguments are 1. Field of View 2. Aspect Ratio 3. Near (how close you can move with the camera before objects start disappearing) 4. Far (opposite of near)
Be mindful to use sensible defaults for near and far because if you choose values which are too large you'll run into performance issues and if you choose values too small you won't see rendered objects. It's ok to start between 0.1 and 100, and if you notice you need more "space", simply increase the far argument
Also, don't forget to add the camera to the scene and to move the camera back from the object, otherwise you'll render the object and the camera into the same coordinates and you won't be able to see anything
camera.position.z = 3
scene.add(camera)
We have four properties which we can use to transform objects. Those are
We can change an object's position in two ways. You can either set the x
, y
and z
coordinate separately or you can call the mesh.position.set(x, y, z)
function.
Since the position property is a Vector3 class, it also has other functions such as position.length()
which will return the vector's length. You can use it to calculate the distance to the camera by using mesh.position.distanceTo(camera.position)
and you can also normalize the vector by calling the mesh.position.normalize()
function.
Sometimes it's useful to know which axis is whereas you might have rotated the camera as well as the object. In order to have this appear, use the following code snippit
const axesHelper = new THREE.AxesHelper(2) // takes size as an argument
scene.add(axesHelper)
Scaling objects is pretty straightforward. Do this by setting the scale value of the appropriate axis like this
mesh.scale.x = 2
mesh.scale.y = 0.25
mesh.scale.z = 0.5
Rotating is only a tad trickier. If you want to rotate an object, imagine you're putting a rod through the center of one of the axis and then rotate it by degrees or radians. Bruno gives three good examples of this
- If you spin on the y axis, you can picture it like a carousel.
- If you spin on the x axis, you can imagine that you are rotating the wheels of a car you'd be in.
- And if you rotate on the z axis, you can imagine that you are rotating the propellers in front of an aircraft you'd be in.
Rotations are applied as follows
mesh.rotation.x = Math.PI * 0.25
mesh.rotation.y = Math.PI * 0.25
Depending on the order in which rotations are applied, you might end up with something called "gimbal lock". Wikipedia gives a decent explanation of it
Gimbal lock is the loss of one degree of freedom in a three-dimensional, three-gimbal mechanism that occurs when the axes of two of the three gimbals are driven into a parallel configuration, "locking" the system into rotation in a degenerate two-dimensional space.
In order to avoid this you simply have to change the order in which rotations are applied like this
object.rotation.reorder('yxz')
Sometimes you'll have spent a large amount of time developing a scene, only to figure out that a part of it is too small, or needs to be repositioned. Because you don't want to move every item individually, you can add them to a group and apply all transformations as a group. The code for this is fairly simple:
const group = new THREE.Group() // create new group
group.scale.y = 2 // no change since nothing has been added to the group yet
group.rotation.y = 0.2
scene.add(group) // don't forget to add the group to the scene
const cube1 = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshBasicMaterial({ color: 0xff0000 })
)
cube1.position.x = - 1.5
group.add(cube1) // add item to group
const cube2 = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshBasicMaterial({ color: 0xff0000 })
)
cube2.position.x = 0
group.add(cube2) // add another item to the group
const cube3 = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshBasicMaterial({ color: 0xff0000 })
)
cube3.position.x = 1.5 // transformation is applied to all items in the group
group.add(cube3)
As with any animation in javascript we need to make use of requestAnimationFrame
. This function accepts a function, which is called when the next frame is available. Any code you need to run on every frame should be placed inside this. Because some mashines are faster than others and you don't want to waste resources, you should aim for animation at 60fps. Some libraries provied functions for that (such as gsap), but threejs also provides a solution. Animating simple things is very similar to animating anything else using javascript.
// get the threejs clock
const clock = new THREE.Clock()
const tick = () =>
{
// get the elapsed time
const elapsedTime = clock.getElapsedTime()
// Update objects with the elapsed time
camera.position.x = Math.cos(elapsedTime)
camera.position.y = Math.sin(elapsedTime)
camera.lookAt(mesh.position)
// ...
}
// call animation function
tick()
Note that you can also use the js native way and get the current time using Date.now()
, calculate the delta within the tick()
function and then apply delta to the rotation. Do not do this when using the THREE.Clock()
function as it breaks things
This chapter largly deals with moving the camera around. There are a number of different controls provided by threejs (look at the documentation for more information). I'm largly copy&pasting these descriptions
For some weird reason you have to import the controls from the examples directory like this
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
const controls = new OrbitControls(camera, canvas) // attach it to the canvas and the camera
... to be continued ...