DEV Community

Lavitra
Lavitra

Posted on

Three.js Explained: What You're Actually Building With Scene, Camera, and Mesh

Every Three.js scene, no matter how impressive the final result looks, is built from the same four pieces: a scene to hold things, a camera to look at them from somewhere, a renderer to actually draw the picture, and one or more meshes, the objects themselves. Nothing about a spinning 3D product viewer or a particle-filled hero section changes that foundation, it just adds more of these same four ingredients and animates them over time.

What problem Three.js actually solves

The browser can talk to a GPU directly through low-level graphics APIs, historically WebGL, more recently WebGPU. Both are genuinely low-level, closer to how a graphics driver thinks than how a developer thinks, full of buffers, shaders, and matrix math that has nothing to do with the actual 3D object you are trying to put on screen. Three.js exists to sit on top of that low-level API and give you a much higher-level vocabulary, scenes, cameras, meshes, lights, instead of raw GPU buffers, so building a 3D scene feels closer to assembling objects than programming a graphics pipeline by hand.

The four pieces, and what changed underneath them

Scene, camera, renderer, mesh, the minimum you actually need.

import * as THREE from 'three';

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x2266ff });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(5, 5, 5);
scene.add(light);

camera.position.z = 5;
renderer.render(scene, camera);
Enter fullscreen mode Exit fullscreen mode

Each piece has exactly one job. The Scene is a container, nothing renders unless it has been added here. The PerspectiveCamera defines a viewpoint, its field of view, aspect ratio, and near and far clipping distances, essentially how far away things can be before they stop being drawn at all. A Mesh is not one thing, it is always two things combined, a Geometry, the actual shape, and a Material, how that shape's surface responds to light and color. A box with no light in the scene will render as a flat, unlit silhouette, because a standard material needs a light source to actually shade its surfaces, which is why the DirectionalLight in that example is not optional decoration, it is what makes the cube look three-dimensional instead of a flat colored square.

The renderer is the piece that changed the most, and 2026 is specifically when that change became real. For most of Three.js's history, WebGLRenderer was the only serious option, translating your scene into WebGL draw calls. As of Three.js r171, released in late 2025, WebGPURenderer became a genuinely production-ready alternative, not an experimental side project. The part worth knowing is what did not change, the Scene, Camera, and Mesh API stays identical either way, swapping renderers is close to a one-line change:

import * as THREE from 'three/webgpu';

const renderer = new THREE.WebGPURenderer({ antialias: true });
Enter fullscreen mode Exit fullscreen mode

WebGPURenderer automatically falls back to a WebGL 2 backend if the user's browser does not support WebGPU, so choosing it does not mean abandoning older devices, it means opportunistically getting WebGPU's benefits, lower CPU overhead and access to compute shaders for things like large particle systems, when the browser can provide them.

Shaders got a real upgrade too, through something called TSL. Historically, custom shader code in Three.js meant writing raw GLSL, WebGL's shading language, which simply does not run on a WebGPU backend, which speaks WGSL instead. TSL, the Three.js Shading Language, lets you write shader logic once in a JavaScript-like syntax that gets transpiled automatically to either GLSL or WGSL depending on which renderer actually ends up running. This matters concretely for anyone who has hand-written shaders before, it removes the need to maintain two separate shader codebases just to support both rendering backends.

The honest caveat worth having before reaching for WebGPU

It would be misleading to present this as a strict upgrade with no tradeoffs, so it is worth being direct about the real state of things in 2026. Browser support, while much better than a couple of years ago, is still not universal, several major browser and platform combinations, including Firefox on Linux and on Android, did not have stable WebGPU support as of mid-2026, which is exactly why the automatic WebGL fallback is not a minor detail, it is the thing keeping those users from silently getting a broken scene.

It is also not automatically faster. Independent benchmarks on scenes with a large number of separate, non-instanced meshes have found WebGLRenderer outperforming WebGPURenderer in specific cases, and Three.js's own maintainers are explicit that simply swapping the renderer class, with no other changes, should not be expected to produce a free performance win. WebGPU's real advantages, lower CPU overhead, native compute shader access, better handling of very high draw call counts, show up specifically in compute-heavy or draw-call-heavy scenes, not universally. For an existing, non-trivial scene, migrating fully, including any custom postprocessing, which does not carry over automatically and needs rebuilding on the newer stack, has reportedly taken production teams a few months, not an afternoon.

What this means practically

  • Every Three.js scene reduces to the same four building blocks, regardless of how visually complex the final result is: a scene to hold objects, a camera defining a viewpoint, a renderer that draws the picture, and meshes made of geometry plus material.
  • A mesh with a standard material needs a light in the scene to actually look three-dimensional. An unlit scene is a common first mistake, not a bug.
  • For a new project today, starting with WebGPURenderer is low risk, given the automatic WebGL fallback, but do not assume it hands you a performance improvement without profiling your specific scene.
  • For an existing production scene, only invest in a full WebGPU migration if you are actually hitting a specific performance ceiling WebGL cannot solve, compute-heavy workloads or very high draw call counts, rather than migrating for its own sake.

Conclusion

Three.js's real contribution has never been the renderer choice, it is the higher-level vocabulary, scene, camera, mesh, light, that stays consistent no matter which low-level graphics API is doing the actual drawing underneath. The WebGL-to-WebGPU shift happening through 2025 and 2026 is a genuinely significant change to what is possible, particularly for compute-heavy scenes, but it is an addition to that vocabulary, not a replacement for understanding it, and treating a renderer swap as an automatic win, rather than something to profile and decide on deliberately, is the mistake worth avoiding.

Top comments (0)