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.
<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>
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.
// 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);
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.
// 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);
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.
// 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);
Wireframe
Any material can be rendered as a wireframe — showing the underlying triangle mesh. Great for debugging geometry or for a stylized, digital look.
// 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);
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.
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();
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.
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.
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.
// 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);
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 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();
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.
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;