Tutorial
⏱️ ~40 minutes 🎓 Beginner 🛠️ HTML · JavaScript · Three.js

Three.js Basics — Scene, Camera, Renderer

Three.js abstracts WebGL into a JS-friendly scene graph. This tutorial walks through every core concept from scratch: renderer setup, camera types, geometry, materials, lighting and the animation loop. By the end you'll have a 3D spinning mesh you understand completely.

Prerequisites

Create the WebGLRenderer

The renderer owns the <canvas> element and the WebGL context. Create it, size it, and append it to the DOM:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <style> body { margin: 0; overflow: hidden; background: #000; } </style>
</head>
<body>
<script type="module">
import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.168/build/three.module.js';

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.setSize(innerWidth, innerHeight);
renderer.outputColorSpace = THREE.SRGBColorSpace;
document.body.appendChild(renderer.domElement);
</script>
</body>
</html>

Why setPixelRatio(Math.min(..., 2))? Retina displays have devicePixelRatio of 2–3. Rendering at 3× costs 9× the pixels for barely visible quality gain. Capping at 2 is a good default.

Scene and PerspectiveCamera

Every Three.js project needs exactly one Scene (the root container) and at least one camera.

const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a0a0f); // dark background

// PerspectiveCamera(fov, aspect, near, far)
const camera = new THREE.PerspectiveCamera(
  60,                      // vertical field of view in degrees
  innerWidth / innerHeight, // aspect ratio
  0.01,                    // near clipping plane
  1000                     // far clipping plane
);
camera.position.set(0, 1, 5); // move camera back so we can see the origin

The near/far planes define the visible depth range. Objects closer than near or further than far are clipped (invisible). Avoid near = 0 — it causes z-fighting artefacts.

Add Geometry and Material

Three.js separates geometry (the shape: vertices, faces) from material (the appearance: colour, shininess, texture). Combine them in a Mesh:

// Geometry: built-in primitives
const geo = new THREE.BoxGeometry(1, 1, 1); // width, height, depth

// Material: how it looks
const mat = new THREE.MeshStandardMaterial({
  color: 0x4488ff,
  metalness: 0.3,
  roughness: 0.6,
});

// Mesh = geometry + material
const cube = new THREE.Mesh(geo, mat);
scene.add(cube);

Other useful geometry primitives: SphereGeometry(radius, widthSeg, heightSeg), CylinderGeometry, PlaneGeometry(w, h), TorusGeometry.

Material types to know:

Add Lighting

MeshStandardMaterial requires lights — without them the mesh is completely black:

// Ambient light: uniform low-level illumination everywhere
const ambient = new THREE.AmbientLight(0xffffff, 0.4); // colour, intensity
scene.add(ambient);

// Directional light: parallel rays (simulates the sun)
const dirLight = new THREE.DirectionalLight(0xffffff, 1.2);
dirLight.position.set(5, 8, 5);
scene.add(dirLight);

// Point light: omnidirectional, falls off with distance
const point = new THREE.PointLight(0x4488ff, 2, 20); // colour, intensity, distance
point.position.set(-3, 2, 3);
scene.add(point);

A common setup: one AmbientLight (low intensity) + one DirectionalLight (strong) is enough for most scenes.

Animate with requestAnimationFrame

Three.js doesn't have a built-in loop — call renderer.render(scene, camera) yourself each frame using requestAnimationFrame:

let prevTime = performance.now();

function animate(time) {
  requestAnimationFrame(animate);
  const dt = Math.min((time - prevTime) / 1000, 0.05); // seconds
  prevTime = time;

  // Rotate cube over time
  cube.rotation.x += dt * 0.8;
  cube.rotation.y += dt * 1.2;

  renderer.render(scene, camera);
}

requestAnimationFrame(animate);

Always multiply movement by dt (delta time). This makes the animation frame-rate independent — it runs identically at 30fps, 60fps and 120fps.

Handle Window Resize

Without this, resizing the browser window stretches/squishes the canvas:

window.addEventListener('resize', () => {
  camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix(); // required after changing aspect!
  renderer.setSize(innerWidth, innerHeight);
});

Complete Example

All steps combined into one copy-pasteable file:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <style> * { margin:0; } body { overflow: hidden; background: #000; } </style>
</head>
<body>
<script type="module">
import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.168/build/three.module.js';

// Renderer
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.setSize(innerWidth, innerHeight);
renderer.outputColorSpace = THREE.SRGBColorSpace;
document.body.appendChild(renderer.domElement);

// Scene + Camera
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a0a0f);
const camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.01, 1000);
camera.position.set(0, 1, 5);

// Mesh
const cube = new THREE.Mesh(
  new THREE.BoxGeometry(1, 1, 1),
  new THREE.MeshStandardMaterial({ color: 0x4488ff, metalness: 0.3, roughness: 0.6 })
);
scene.add(cube);

// Lighting
scene.add(new THREE.AmbientLight(0xffffff, 0.4));
const dirLight = new THREE.DirectionalLight(0xffffff, 1.2);
dirLight.position.set(5, 8, 5);
scene.add(dirLight);

// Resize
window.addEventListener('resize', () => {
  camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(innerWidth, innerHeight);
});

// Animate
let prevTime = performance.now();
(function animate(time) {
  requestAnimationFrame(animate);
  const dt = Math.min((time - prevTime) / 1000, 0.05);
  prevTime = time;
  cube.rotation.x += dt * 0.8;
  cube.rotation.y += dt * 1.2;
  renderer.render(scene, camera);
})(performance.now());
</script>
</body>
</html>

✅ What you'll see

A blue metallic cube spinning slowly on a dark background. The rotation speed is frame-rate independent. Resize the window — the aspect ratio stays correct.

Continue Learning

Frequently Asked Questions

What will I learn in this tutorial?

Learn the core Three.js concepts: WebGLRenderer, scene graph, cameras, meshes, lighting and the animation loop. Hands-on from scratch in 40 minutes.

What topics are covered in this tutorial?

This tutorial covers: Create the WebGLRenderer, Scene and PerspectiveCamera, Add Geometry and Material, Add Lighting, Animate with requestAnimationFrame, Handle window resize, Complete example.

What tools and technologies does this tutorial use?

This tutorial uses HTML, JavaScript, Three.js.

How long does this tutorial take?

This tutorial takes approximately 40 minutes to complete.

What prerequisites do I need before starting?

Before starting, you should have: Basic HTML & JavaScript (variables, functions, const / let ); Familiarity with Cartesian coordinates (x, y, z); No prior Three.js or WebGL knowledge needed.

🛠

Experiment in Playground

Write, run and tweak Three.js code directly in your browser — no setup required.

Open Playground → View Simulation ↗