const scene = new THREE.Scene();
// draw a cube
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0xffffff });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
// camera
const camera = new THREE.PerspectiveCamera(75, width / height);
// move camera in front of cube by moving camera along z access
camera.position.z = 4;
scene.add(camera);
// renderer
const renderer = new THREE.WebGLRenderer({
canvas,
});
renderer.setSize(width, height);
renderer.render(scene, camera);
Threejs
Draw a Cube
Transform Cubes
Rotate & Reposition Single Cube
// draw single cube (same as prev example)
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0xffffff });
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(2, 1, 0);
mesh.scale.set(1, 0.5, 0.5);
// reorder before rotation to avoid axis lock / confusion
mesh.rotation.reorder("YXZ");
// Math.PI = half rotation on y axis
mesh.rotation.y = Math.PI;
mesh.rotation.x = Math.PI;
scene.add(mesh);
// axis helper
const axisHelper = new THREE.AxesHelper(4);
scene.add(axisHelper);
Group Three Cubes & Rotate on y Axis
This example uses a helper function to draw cubes refer to previous examples for how to draw a cube.
// transformations
const group = new THREE.Group();
scene.add(group);
const firstCube = drawCube(0x98b9f2);
const secondCube = drawCube(0x8cd790);
const thirdCube = drawCube(0x918ef4);
// move two of the cubes so they do not overlap
secondCube.position.x = -2;
thirdCube.position.x = 2;
group.add(firstCube);
group.add(secondCube);
group.add(thirdCube);
group.rotation.y = 1; // rotate group on y axis
Animate
Ensure animation is the same speed regardless of viewer’s computer FPS.
const clock = new THREE.Clock();
animate() {
const elapsedTime = clock.getElapsedTime();
cube.rotation.y = elapsedTime;
renderer.render(scene, camera);
window.requestAnimationFrame(animate);
}
animate();
Debug GUI
On page control panel. We are using lil-gui for this example.
I had to dynamically load ‘lil-gui’ to avoid a self not defined error. By default the UI will be mounted in the top right corner of the screen. In order to move the UI next to our canvas, I changed the container to be a sibling of our canvas.
const guiLib = (await import("lil-gui")).default;
const gui = new guiLib({
container: document.getElementById("gui") ?? undefined,
autoPlace: false,
});
Then we draw a box and add properties we want to see in the UI.
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({
color,
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
gui.add(mesh.position, "y").min(-3).max(3).step(0.01);
gui.add(mesh.position, "x").min(-3).max(3).step(0.01);
gui.add(mesh.position, "z").min(-3).max(3).step(0.01);
3D Text
import { Font } from "three/examples/jsm/loaders/FontLoader";
import { TextGeometry } from "three/examples/jsm/geometries/TextGeometry";
import matcap from "../assets/matcap.png";
import typeface from "../assets/your_typeface.json";
// ...
const textGeometry = new TextGeometry("Kayla's Portfolio", {
bevelEnabled: true,
bevelOffset: 0,
bevelSegments: 4,
bevelSize: 0.04,
bevelThickness: 0.04,
curveSegments: 24,
font: new Font(typeface),
height: 0.3, // extrusion depth
size: 0.8,
});
textGeometry.center();
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load(matcap);
const textMaterial = new THREE.MeshMatcapMaterial({
matcap: texture,
});
const mesh = new THREE.Mesh(textGeometry, textMaterial);
scene.add(mesh);
Lights
Lights are performance hogs, try to use only what you need. Adjust intensity in control panel to see how different lights effect the cube and plane, or how these lights can be combined. Intensity is set to zero for all lights by default. Most lights come with helper functions (not included in live preview),
const directionalLightHelper = new THREE.DirectionalLightHelper(
directionalLight,
);
scene.add(directionalLightHelper);
Note that RectAreaLight
, only works with MeshStandardMaterial
or MeshPhysicalMaterial
.
When using Spot Light we have to add the target to the scene
scene.add(spotLight.target);
Shadows
Avoid shadows a much as possible. Use shadow baking for multiple shadows / better performance. Adjust intensity for each light in the control panel to see how the shadows change.
Shadows Animated Sphere
Instead of baking the shadow into the model directly using software, an alternative is loading a shadow texture as an alphaMap material and animating the resulting mesh relative to the sphere. The shadow jpg is loaded using the texture loader and the shadow mesh positioned slightly above the base plane. “The alpha map is a grayscale texture that controls the opacity across the surface (black: fully transparent; white: fully opaque)”.
import simpleShadow from "assets/textures/simpleShadow.jpg";
// plane mesh
const planeGeometry = new THREE.PlaneGeometry(8, 8);
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load(simpleShadow);
const material = new THREE.MeshBasicMaterial({
alphaMap: texture,
transparent: true,
color: 0x000000,
});
const sphereShadow = new THREE.Mesh(new THREE.PlaneGeometry(3, 3), material);
sphereShadow.rotation.x = Math.PI * 1.5;
sphereShadow.position.z = 0.5;
sphereShadow.position.y = -0.5;
scene.add(sphereShadow);
The following code that animates the sphere and it’s shadow in this example was taken directly from ThreeJS Journey.
const clock = new THREE.Clock();
function animate() {
controls.update();
const elapsedTime = clock.getElapsedTime();
// Update the sphere
sphere.position.x = Math.cos(elapsedTime) * 1.5;
sphere.position.z = Math.sin(elapsedTime) * 1.5;
sphere.position.y = Math.abs(Math.sin(elapsedTime * 3));
// Update the shadow
sphereShadow.position.x = sphere.position.x;
sphereShadow.position.z = sphere.position.z;
sphereShadow.material.opacity = (1 - sphere.position.y) * 0.3;
renderer.render(scene, camera);
window.requestAnimationFrame(animate);
}
animate();
Quirks
Z-Fighting Planes
Z-Fighting planes, if two planes have x,y,z values, the renderer can have trouble determining which plane should be on top. The result is a glitch like effect where one plane flashes through the other.
references & inspiration 1. https://threejs.org/examples 2. https://threejs-journey.com