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.
HTML
<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.
JavaScript
// To run standalone, load THREE first:
// <script type="module">
// import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.164.0/build/three.module.js';
// /* your code below */
// </script>
// 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.
JavaScript
// To run standalone, load THREE first:
// <script type="module">
// import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.164.0/build/three.module.js';
// /* your code below */
// </script>
// 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.
JavaScript
// To run standalone, load THREE first:
// <script type="module">
// import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.164.0/build/three.module.js';
// /* your code below */
// </script>
// 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.
JavaScript
// To run standalone, load THREE first:
// <script type="module">
// import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.164.0/build/three.module.js';
// /* your code below */
// </script>
// 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.
JavaScript
// To run standalone, load THREE first:
// <script type="module">
// import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.164.0/build/three.module.js';
// /* your code below */
// </script>
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.
JavaScript
// To run standalone, load THREE first:
// <script type="module">
// import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.164.0/build/three.module.js';
// /* your code below */
// </script>
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.
JavaScript
// To run standalone, load THREE first:
// <script type="module">
// import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.164.0/build/three.module.js';
// /* your code below */
// </script>
// 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.
JavaScript
// 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.
JavaScript
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;
The whole scene as one file
Everything from this lab — scene, camera, renderer, lights, a spinning torus knot, a starfield, and drag-to-orbit controls — combined into one complete HTML file. Save it as scene.html, open it in a browser, and it just works. The live preview below is rendered from the exact code in the box.
Live preview
Complete HTML — copy this whole file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Three.js Scene</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { height: 100%; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: #0a0a12;
color: #e2e8f0;
overflow: hidden;
}
/* The canvas fills the whole window */
#scene {
display: block;
width: 100%;
height: 100%;
cursor: grab;
}
#scene:active { cursor: grabbing; }
/* A little caption floating over the 3D scene */
.overlay {
position: fixed;
left: 24px;
bottom: 24px;
max-width: 320px;
pointer-events: none;
}
.overlay h1 {
font-size: 1.6rem;
font-weight: 700;
color: #fff;
margin-bottom: 6px;
}
.overlay h1 span { color: #84cc16; }
.overlay p {
font-size: .85rem;
line-height: 1.5;
color: rgba(255,255,255,.6);
}
.hint {
position: fixed;
right: 24px;
top: 24px;
font-size: .72rem;
letter-spacing: .06em;
text-transform: uppercase;
color: rgba(255,255,255,.45);
background: rgba(0,0,0,.4);
border: 1px solid rgba(255,255,255,.1);
border-radius: 6px;
padding: 6px 10px;
pointer-events: none;
}
</style>
</head>
<body>
<!-- Three.js renders into this single canvas -->
<canvas id="scene"></canvas>
<div class="overlay">
<h1>Three<span>.js</span> Scene</h1>
<p>A torus knot, a starfield, and three lights — drag to orbit, scroll to zoom.</p>
</div>
<div class="hint">drag · scroll to zoom</div>
<!-- Import map lets the addons resolve the bare "three" import -->
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.164.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.164.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
const canvas = document.getElementById('scene');
// 1. SCENE — the container for everything 3D
const scene = new THREE.Scene();
scene.background = new THREE.Color('#0a0a12');
// 2. CAMERA — the point of view
const camera = new THREE.PerspectiveCamera(
45, window.innerWidth / window.innerHeight, 0.1, 100
);
camera.position.set(0, 1, 6);
// 3. RENDERER — draws the scene into the canvas
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(window.innerWidth, window.innerHeight);
// 4. MESH — a torus knot with a shiny standard material
const knot = new THREE.Mesh(
new THREE.TorusKnotGeometry(0.9, 0.28, 160, 24, 2, 3),
new THREE.MeshStandardMaterial({ color: 0x84cc16, roughness: 0.25, metalness: 0.5 })
);
scene.add(knot);
// 5. LIGHTS — ambient fill, a "sun", and an orbiting point light
scene.add(new THREE.AmbientLight(0xffffff, 0.35));
const sun = new THREE.DirectionalLight(0xffffff, 1);
sun.position.set(-3, 4, 2);
scene.add(sun);
const point = new THREE.PointLight(0xd946ef, 2.5, 12);
scene.add(point);
// 6. PARTICLE FIELD — 1,500 dots as a single Points object
const count = 1500;
const positions = new Float32Array(count * 3);
for (let i = 0; i < count; i++) {
positions[i*3+0] = (Math.random() - 0.5) * 14;
positions[i*3+1] = (Math.random() - 0.5) * 14;
positions[i*3+2] = (Math.random() - 0.5) * 14;
}
const starGeo = new THREE.BufferGeometry();
starGeo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
const stars = new THREE.Points(
starGeo,
new THREE.PointsMaterial({ color: 0xaaaaaa, size: 0.04, sizeAttenuation: true })
);
scene.add(stars);
// 7. ORBIT CONTROLS — drag to rotate, scroll to zoom
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.08;
controls.minDistance = 3;
controls.maxDistance = 12;
// 8. ANIMATION LOOP — runs once per browser paint (~60 fps)
function tick(t) {
knot.rotation.x = t * 0.0003;
knot.rotation.y = t * 0.0005;
point.position.set(Math.sin(t*0.001)*3, 1, Math.cos(t*0.001)*3);
stars.rotation.y = t * 0.00008;
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
// Keep the scene sized to the window
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body>
</html>