🪐 Three.js in 3 ideas

Three.js renders real 3D into a <canvas> using WebGL. Unlike CSS 3D (which transforms flat HTML rectangles), Three.js draws actual meshes — geometry covered in material, lit by lights, viewed through a camera.

// The 3-line skeleton every Three.js app starts from:
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
const renderer = new THREE.WebGLRenderer({ canvas });

// Then: add meshes, call renderer.render(scene, camera).

Load it from a CDN with <script type="module"> and import * as THREE from '...'. Every demo below is a self-contained function that builds a mini scene and renders into its own canvas.

01 — Basics

Hello, Cube

The "hello world" of Three.js. A rotating cube, a camera, a renderer. Everything else in this lab builds on these three objects.

Scene + PerspectiveCamera + WebGLRenderer BASICS
<canvas id="c-hello"></canvas>

<script type="module">
  import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.164.0/build/three.module.js';
  /* see JS tab */
</script>
02 — Basics

Primitive Geometries

Three.js ships with every basic shape you need out of the box. BoxGeometry, SphereGeometry, TorusGeometry, ConeGeometry, CylinderGeometry, DodecahedronGeometry. Here they are all on one stage.

6 primitives, rotating in sync BASICS
// Six different geometries — same material for clarity
const geos = [
  new THREE.BoxGeometry(1, 1, 1),
  new THREE.SphereGeometry(0.6, 32, 32),
  new THREE.TorusGeometry(0.5, 0.2, 16, 48),
  new THREE.ConeGeometry(0.6, 1.2, 32),
  new THREE.CylinderGeometry(0.4, 0.4, 1.2, 32),
  new THREE.DodecahedronGeometry(0.7),
];

const material = new THREE.MeshNormalMaterial();

const meshes = geos.map((g, i) => {
  const mesh = new THREE.Mesh(g, material);
  mesh.position.x = (i - 2.5) * 1.8;  // spread them across x
  scene.add(mesh);
  return mesh;
});

function tick(t) {
  meshes.forEach(m => {
    m.rotation.x = t * 0.0006;
    m.rotation.y = t * 0.001;
  });
  renderer.render(scene, camera);
  requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
03 — Basics

Materials

Same sphere, four different materials. MeshBasicMaterial ignores light (always fully lit), MeshLambertMaterial does simple diffuse lighting, MeshPhongMaterial adds specular highlights, MeshStandardMaterial is the modern physically-based material.

Basic · Lambert · Phong · Standard BASICS
// The geometry is shared — materials make the difference
const geo = new THREE.SphereGeometry(0.7, 32, 32);

const materials = [
  new THREE.MeshBasicMaterial   ({ color: 0x84cc16 }),
  new THREE.MeshLambertMaterial ({ color: 0x84cc16 }),
  new THREE.MeshPhongMaterial   ({ color: 0x84cc16, shininess: 80 }),
  new THREE.MeshStandardMaterial({ color: 0x84cc16, roughness: 0.3, metalness: 0.4 }),
];

materials.forEach((mat, i) => {
  const sphere = new THREE.Mesh(geo, mat);
  sphere.position.x = (i - 1.5) * 1.8;
  scene.add(sphere);
});

// All except "Basic" need lights
scene.add(new THREE.AmbientLight(0xffffff, 0.5));
const dir = new THREE.DirectionalLight(0xffffff, 1);
dir.position.set(2, 3, 4);
scene.add(dir);
04 — Lighting

Lights

Three.js gives you a handful of light types. This scene uses AmbientLight (fills everything evenly), DirectionalLight (parallel rays — think sun), and a colored PointLight that orbits to show moving illumination.

Ambient + Directional + orbiting Point LIGHTS
// A matte object so the lighting really shows
const mesh = new THREE.Mesh(
  new THREE.TorusKnotGeometry(0.8, 0.3, 128, 16),
  new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: .6 })
);
scene.add(mesh);

// Three lights in one scene:
// 1) Ambient — lifts the shadows so nothing is pitch-black
scene.add(new THREE.AmbientLight(0xffffff, 0.3));

// 2) Directional — the "sun", casts parallel rays
const sun = new THREE.DirectionalLight(0xffffff, 0.9);
sun.position.set(-3, 4, 2);
scene.add(sun);

// 3) Colored point light that orbits the mesh
const point = new THREE.PointLight(0xd946ef, 2, 10);
scene.add(point);

function tick(t) {
  mesh.rotation.x = t * 0.0004;
  mesh.rotation.y = t * 0.0006;
  // move the point light in a circle
  point.position.set(Math.sin(t*0.001)*2, 1, Math.cos(t*0.001)*2);
  renderer.render(scene, camera);
  requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
05 — Lighting

Wireframe

Any material can be rendered as a wireframe — showing the underlying triangle mesh. Great for debugging geometry or for a stylized, digital look.

material.wireframe = true STYLE
// Higher segment counts reveal the wireframe structure better
const ico = new THREE.Mesh(
  new THREE.IcosahedronGeometry(1.2, 1),  // detail level 1
  new THREE.MeshBasicMaterial({
    color: 0x84cc16,
    wireframe: true
  })
);
scene.add(ico);

// Everything outside the wireframe mesh can still use regular
// lighting / materials — wireframe is a per-material flag.

function tick(t) {
  ico.rotation.x = t * 0.0004;
  ico.rotation.y = t * 0.0007;
  renderer.render(scene, camera);
  requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
06 — Lighting

Textures

Wrap an image around geometry with TextureLoader. Here a sphere is covered in an earth-style map, with a point light giving it shape. The same technique wraps labels around bottles, billboards, or cube faces.

TextureLoader + material.map TEXTURES
const loader = new THREE.TextureLoader();
const earthTex = loader.load(
  'https://unpkg.com/three-globe@2.27.2/example/img/earth-blue-marble.jpg'
);

const planet = new THREE.Mesh(
  new THREE.SphereGeometry(1.2, 64, 64),
  new THREE.MeshStandardMaterial({
    map: earthTex,              // ← the color ("diffuse") texture
    roughness: .8
  })
);
scene.add(planet);

scene.add(new THREE.AmbientLight(0xffffff, 0.25));
const sun = new THREE.DirectionalLight(0xffffff, 1.3);
sun.position.set(3, 1, 2);
scene.add(sun);

function tick() {
  planet.rotation.y += 0.003;
  renderer.render(scene, camera);
  requestAnimationFrame(tick);
}
tick();
07 — Motion

Animated Torus Knot

A torus knot is just a fancier torus — twisted through itself p times while going around q times. It makes great hero-section decoration. Here it spins on two axes and changes color with a gradient material.

TorusKnotGeometry + MeshNormalMaterial MOTION
const knot = new THREE.Mesh(
  new THREE.TorusKnotGeometry(
    0.9,    // radius of the overall ring
    0.28,   // radius of the tube
    128,    // tubular segments
    16,     // radial segments
    2,      // p — how many times around the axis
    3       // q — how many times through the hole
  ),
  new THREE.MeshNormalMaterial()
);
scene.add(knot);

function tick(t) {
  knot.rotation.x = t * 0.0004;
  knot.rotation.y = t * 0.0006;
  renderer.render(scene, camera);
  requestAnimationFrame(tick);
}
requestAnimationFrame(tick);

// Try changing p and q to different integers for wildly
// different knot shapes. p=2 q=3 is the classic trefoil.
08 — Motion

Particle Field

Thousands of dots in 3D space, rendered by a single Points object instead of thousands of meshes. Great for starfields, snow, dust, or any ambient background.

BufferGeometry + Points + PointsMaterial MOTION
// Build an array of xyz positions for 3,000 points
const count = 3000;
const positions = new Float32Array(count * 3);
for (let i = 0; i < count; i++) {
  positions[i * 3 + 0] = (Math.random() - .5) * 10;  // x
  positions[i * 3 + 1] = (Math.random() - .5) * 10;  // y
  positions[i * 3 + 2] = (Math.random() - .5) * 10;  // z
}

// BufferGeometry holds raw position data
const geo = new THREE.BufferGeometry();
geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));

const material = new THREE.PointsMaterial({
  color: 0x84cc16,
  size: 0.03,
  sizeAttenuation: true,        // far particles look smaller
});

const cloud = new THREE.Points(geo, material);
scene.add(cloud);

function tick(t) {
  cloud.rotation.y = t * 0.0001;
  renderer.render(scene, camera);
  requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
09 — Input

Orbit Controls

Let users drag to rotate and scroll to zoom. OrbitControls is an optional addon — not in the main Three.js core, so you import it separately. Try dragging the object below.

OrbitControls addon + controls.update() INPUT
drag · scroll to zoom
// OrbitControls lives in the "addons" / "examples/jsm" path
import { OrbitControls } from 'https://cdn.jsdelivr.net/npm/three@0.164.0/examples/jsm/controls/OrbitControls.js';

// ...scene, camera, renderer setup as usual...

const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;        // smoothed pan/zoom
controls.dampingFactor = 0.08;
controls.minDistance   = 2;           // don't zoom in past this
controls.maxDistance   = 12;

function tick() {
  controls.update();                  // required each frame for damping
  renderer.render(scene, camera);
  requestAnimationFrame(tick);
}
tick();
10 — Input

Loaded GLTF Model

Real projects use real 3D models, typically in .gltf or .glb format (like JPG for 3D). You load them with GLTFLoader and add the resulting scene to your own. This demo fetches a free model — you can swap the URL for any GLB on the web.

GLTFLoader + loader.load() MODEL
drag to inspect
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GLTFLoader    } from 'three/addons/loaders/GLTFLoader.js';

const scene    = new THREE.Scene();
const camera   = new THREE.PerspectiveCamera(35, aspect, 0.1, 100);
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });

// Bright, simple lighting so the model reads
scene.add(new THREE.AmbientLight(0xffffff, 0.8));
const dir = new THREE.DirectionalLight(0xffffff, 1);
dir.position.set(2, 3, 4);
scene.add(dir);

const loader = new GLTFLoader();
loader.load(
  'https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/main/2.0/Duck/glTF-Binary/Duck.glb',
  (gltf) => {                         // SUCCESS
    scene.add(gltf.scene);
  },
  undefined,
  (err) => console.error(err)         // ERROR
);

const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;