Threejs



Draw a Cube

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);

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