Three.js Basics Explained — Complete Beginner's Guide to Scene, Camera & Renderer
Three.js is a cross-browser JavaScript library that makes 3D graphics in the browser accessible by abstracting the low-level WebGL API into a clean, object-oriented scene graph.
What You’ll Learn
By the end of this guide, you’ll understand how Three.js works under the hood, set up your first 3D scene with a cube, configure cameras and renderers, handle window resizing, add a stats panel, and build a mini solar system — all while learning why each piece exists.
Why Three.js Matters
Three.js powers everything from product configurators to data visualization dashboards. At DodaTech, we use Three.js in Durga Antivirus Pro to render real-time 3D threat visualization dashboards, showing malware propagation patterns across network nodes in an interactive 3D space. This is the foundation you need to build experiences like that.
flowchart LR
A[HTML + JS Setup] --> B[Scene = Container]
B --> C[Camera = Viewpoint]
C --> D[Renderer = Painter]
D --> E[Objects = Meshes]
E --> F[Lights]
F --> G[Animation Loop]
G --> H[Your 3D App!]
style G fill:#44aa88,color:#fff,stroke:none
Three.js Architecture — The Stage Analogy
Think of a Three.js scene like a theatre production:
- Scene = the stage. Everything lives here — actors, props, lights.
- Camera = your eyes. Where you stand and what you see.
- Renderer = the painter. It takes what the camera sees and draws it on the canvas (the screen).
- Mesh (Geometry + Material) = an actor. The geometry is their body shape; the material is their costume.
- Light = stage lighting. Without it, you can’t see the actors.
Every Three.js app follows this same pattern: create a scene, add a camera, create objects, add lights, then loop and render.
Installation — Three Ways
CDN (Simplest for Learning)
Add this to your HTML <head>:
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>The THREE global is now available.
Import Map (Modern — Recommended)
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js"
}
}
</script>
<script type="module">
import * as THREE from "three";
</script>Why import maps? They let you use import statements without a bundler. The browser resolves "three" to the CDN URL automatically. This is how all our examples work.
npm (For Real Projects)
npm install threeimport * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";Use this when you’re building with Webpack, Vite, or other bundlers.
Hello Cube — Your First 3D Scene Explained Line by Line
Let’s walk through every line of a complete Three.js scene. Don’t skim — each line has a purpose.
<!DOCTYPE html>
<html>
<head>
<title>Hello Cube — Your First Three.js Scene</title>
<style>
/* Remove margin and hide scrollbars so the canvas fills the screen */
body { margin: 0; overflow: hidden; }
</style>
</head>
<body>
<!-- Import map tells the browser where to find the "three" module -->
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js"
}
}
</script>
<script type="module">
import * as THREE from "three";
// 1. SCENE — the container that holds everything
var scene = new THREE.Scene();
scene.background = new THREE.Color(0x222222); // Dark gray background
// 2. CAMERA — the viewpoint. PerspectiveCamera mimics human eyes:
// things farther away appear smaller.
var camera = new THREE.PerspectiveCamera(
75, // Field of view (degrees). 50-75 is typical.
window.innerWidth / window.innerHeight, // Aspect ratio (width/height). Must match canvas.
0.1, // Near clipping plane. Closer than this is not rendered.
1000 // Far clipping plane. Farther than this is culled.
);
camera.position.set(2, 2, 5); // Move camera back and to the right
camera.lookAt(0, 0, 0); // Point the camera at the origin
// 3. RENDERER — the painter that turns 3D into 2D pixels on screen
var renderer = new THREE.WebGLRenderer({ antialias: true }); // antialias smooths edges
renderer.setSize(window.innerWidth, window.innerHeight); // Match canvas to window
renderer.setPixelRatio(window.devicePixelRatio); // Sharp on retina displays
document.body.appendChild(renderer.domElement); // Add canvas to the page
// 4. OBJECT — a mesh is geometry (shape) + material (appearance)
var geometry = new THREE.BoxGeometry(1, 1, 1); // A 1x1x1 cube
var material = new THREE.MeshStandardMaterial({ // Physically-based material (needs light!)
color: 0x44aa88, // Teal color
roughness: 0.3, // How rough the surface is (0 = mirror, 1 = matte)
metalness: 0.1 // How metallic (0 = non-metal, 1 = metal)
});
var cube = new THREE.Mesh(geometry, material); // Combine shape + appearance
scene.add(cube); // Place the cube on the stage
// 5. LIGHTS — MeshStandardMaterial is dark without light
var ambientLight = new THREE.AmbientLight(0xffffff, 0.5); // Soft fill light
scene.add(ambientLight);
var directionalLight = new THREE.DirectionalLight(0xffffff, 1); // Sun-like light
directionalLight.position.set(5, 10, 7); // Position the light source
scene.add(directionalLight);
// 6. ANIMATION LOOP — runs ~60 times per second
function animate() {
requestAnimationFrame(animate); // Schedule the next frame
// Rotate the cube a tiny amount each frame
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera); // Draw the scene from the camera's perspective
}
animate(); // Start the loop
// 7. RESIZE HANDLER — keeps the canvas looking right when the window changes size
window.addEventListener("resize", function() {
camera.aspect = window.innerWidth / window.innerHeight; // Update aspect ratio
camera.updateProjectionMatrix(); // Tell the camera to recalculate
renderer.setSize(window.innerWidth, window.innerHeight); // Resize the canvas
});
</script>
</body>
</html>Expected output: A teal cube rotating in space against a dark gray background.
Scene Deep Dive
var scene = new THREE.Scene();
scene.background = new THREE.Color(0x888888); // Set background color
scene.fog = new THREE.Fog(0x888888, 10, 50); // Fog starts at distance 10, fully opaque at 50
scene.fog = new THREE.FogExp2(0x888888, 0.01); // Exponential fog (denser, more natural)
scene.add(object); // Add any object to the scene
scene.remove(object); // Remove it
scene.children; // Array of everything in the scene
Why fog? Fog hides the edge of your world. If objects suddenly pop out of existence at distance 50, that’s jarring. Fog makes them fade out gradually.
Camera Deep Dive
PerspectiveCamera (Realistic — Use This Most Often)
var camera = new THREE.PerspectiveCamera(
fov, // Field of view in degrees (default 75). Higher = wider = fish-eye effect.
aspect, // Width / height. Must match canvas or objects look stretched.
near, // Near clipping plane (default 0.1). Don't set this too small.
far // Far clipping plane (default 1000). Don't set this too large.
);
camera.position.set(x, y, z); // Move the camera in 3D space
camera.lookAt(x, y, z); // Point the camera at a specific spot
camera.zoom = 1; // Zoom factor (1 = normal, 2 = 2x zoom)
camera.fov = 75; // Change FOV at runtime
camera.updateProjectionMatrix(); // ALWAYS call this after changing fov, aspect, near, or far
OrthographicCamera (Isometric / 2D)
Objects stay the same size regardless of distance. Great for 2D games, CAD tools, and isometric views.
var frustumSize = 10;
var aspect = window.innerWidth / window.innerHeight;
var camera = new THREE.OrthographicCamera(
-frustumSize * aspect / 2, // left
frustumSize * aspect / 2, // right
frustumSize / 2, // top
-frustumSize / 2, // bottom
0.1, // near
1000 // far
);When to use which? Use PerspectiveCamera for anything realistic — games, product viewers, architectural walkthroughs. Use OrthographicCamera for UI, 2D games, or when you need precise measurements (CAD).
Renderer Deep Dive
var renderer = new THREE.WebGLRenderer({
antialias: true, // Smooth jagged edges (slight performance cost)
alpha: true, // Transparent background (for overlays)
powerPreference: "high-performance" // Prefer dedicated GPU
});
renderer.setSize(width, height); // Canvas resolution
renderer.setPixelRatio(window.devicePixelRatio); // Sharp on retina
renderer.setClearColor(0x222222, 1); // Background clear color
renderer.shadowMap.enabled = true; // Enable shadows
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // Soft shadow quality
renderer.toneMapping = THREE.ACESFilmicToneMapping; // Film-like color response
renderer.toneMappingExposure = 1.0; // Brightness adjustment
document.body.appendChild(renderer.domElement); // Add <canvas> to page
The Animation Loop — Why requestAnimationFrame?
function animate() {
requestAnimationFrame(animate); // Browser calls this before next paint (~60fps)
controls.update(); // Update user controls (if any)
renderer.render(scene, camera); // Draw the frame
}
animate();Why not setInterval? requestAnimationFrame pauses when the tab is hidden (saving battery/CPU), syncs with the monitor’s refresh rate, and gives smoother animation. setInterval keeps firing even when nobody’s watching.
Clock for Time-Based Animation
var clock = new THREE.Clock();
function animate() {
var delta = clock.getDelta(); // Seconds since last frame (~0.016 at 60fps)
var elapsed = clock.getElapsedTime(); // Total seconds since clock started
cube.position.y = Math.sin(elapsed) * 2; // Bob up and down (2 units amplitude)
cube.rotation.x = elapsed * 0.5; // Rotate at 0.5 radians per second
renderer.render(scene, camera);
requestAnimationFrame(animate);
}Why delta time matters: Without it, a fast computer spins the cube faster than a slow one. Multiplying movement by delta makes speed consistent across all devices. Always use delta.
Debug & Stats
import Stats from "three/addons/libs/stats.module.js";
var stats = new Stats();
stats.showPanel(0); // 0: FPS, 1: MS (frame time), 2: MB (memory)
document.body.appendChild(stats.dom);
function animate() {
stats.begin();
// ... your render code ...
stats.end();
requestAnimationFrame(animate);
}Dat.GUI for Runtime Controls
import { GUI } from "three/addons/libs/lil-gui.module.min.js";
var gui = new GUI({ width: 300 });
gui.add(cube.position, "x", -5, 5).name("Position X");
gui.add(cube.material, "roughness", 0, 1).name("Roughness");
gui.add(cube.material, "metalness", 0, 1).name("Metalness");
gui.addColor(cube.material, "color").name("Color");Common Mistakes
1. Camera at origin (inside the cube)
The camera defaults to (0, 0, 0) — the same position as your cube. You’re inside it. Always set camera.position.set() before expecting to see anything.
2. MeshStandardMaterial with no lights
MeshStandardMaterial and MeshPhysicalMaterial require lights to be visible. Without them, objects render pure black. Use MeshBasicMaterial (unlit) for testing, or always add at least an AmbientLight.
3. Forgetting updateProjectionMatrix() on resize
If you change camera.aspect and don’t call camera.updateProjectionMatrix(), the rendered image stays distorted. This is the #1 resize bug.
4. Using setInterval instead of requestAnimationFrame
setInterval doesn’t pause when the tab is hidden, doesn’t sync with refresh rate, and can queue overlapping callbacks. Always use requestAnimationFrame.
5. Not disposing materials and geometries
Every new THREE.Mesh() allocates GPU memory. When removing objects, call geometry.dispose() and material.dispose() to avoid memory leaks — especially in dynamic scenes.
6. Ignoring setPixelRatio
Without renderer.setPixelRatio(), the canvas renders at the CSS size’s pixel resolution. On retina displays (2x, 3x), the image looks blurry. Always set it — but cap it with Math.min(devicePixelRatio, 2) for performance.
7. Forgetting that Three.js uses a right-handed coordinate system
X = right, Y = up, Z = toward the viewer. If your objects appear mirrored or flipped, double-check your coordinate axes.
Practice Questions
Q1: What are the three essential components of every Three.js scene? A: Scene (container), Camera (viewpoint), and Renderer (draws to canvas).
Q2: Why does MeshStandardMaterial render black without lights? A: It uses physically-based rendering (PBR) which simulates how light interacts with surfaces. No light = no illumination = black. Add an AmbientLight or use MeshBasicMaterial for unlit objects.
Q3: What happens if you don’t call camera.updateProjectionMatrix() after changing the aspect ratio?
A: The rendered image stays distorted because the camera’s internal projection matrix still uses the old aspect ratio. The view will look stretched or squashed.
Q4: Why should you use requestAnimationFrame instead of setInterval for animation?
A: rAF pauses when the tab is hidden, syncs with the display refresh rate, and prevents overlapping callbacks — giving smoother, more efficient animation.
Q5: What is delta time and why is it important? A: Delta time is the seconds elapsed between frames. Multiplying movement by delta ensures consistent speed across different frame rates (a 30fps machine moves objects the same distance per second as a 120fps machine).
Challenge: Modify the Hello Cube example to create a scene with 3 cubes arranged in a triangle, each rotating at a different speed. One should use BasicMaterial (no light needed), one StandardMaterial with a light, and one PhysicalMaterial with transparency. Hint: use transparent: true and opacity: 0.5 on the physical material.
FAQ
Try It Yourself
Copy this complete HTML file, save it as hello-threejs.html, and open it in a browser. No server needed.
<!DOCTYPE html>
<html>
<head>
<title>Hello Three.js — Your First 3D Scene</title>
<style>
body { margin: 0; overflow: hidden; background: #000; }
#info { position: absolute; top: 10px; left: 10px; color: white;
font-family: sans-serif; font-size: 14px; }
</style>
</head>
<body>
<div id="info">🎯 Your First Three.js Scene</div>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js"
}
}
</script>
<script type="module">
import * as THREE from "three";
var scene = new THREE.Scene();
scene.background = new THREE.Color(0x111122);
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 5, 15);
camera.lookAt(0, 0, 0);
var renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
// Sun
var sunGeo = new THREE.SphereGeometry(1.5, 32, 32);
var sunMat = new THREE.MeshStandardMaterial({
color: 0xffaa00, emissive: 0xff5500, emissiveIntensity: 0.5
});
var sun = new THREE.Mesh(sunGeo, sunMat);
scene.add(sun);
// Earth
var earthGeo = new THREE.SphereGeometry(0.6, 32, 32);
var earthMat = new THREE.MeshStandardMaterial({ color: 0x4488ff, roughness: 0.5 });
var earth = new THREE.Mesh(earthGeo, earthMat);
scene.add(earth);
// Moon
var moonGeo = new THREE.SphereGeometry(0.2, 16, 16);
var moonMat = new THREE.MeshStandardMaterial({ color: 0xcccccc });
var moon = new THREE.Mesh(moonGeo, moonMat);
scene.add(moon);
// Lights
var ambient = new THREE.AmbientLight(0x222244);
scene.add(ambient);
var sunLight = new THREE.PointLight(0xffffff, 2, 30);
sunLight.position.set(0, 0, 0);
scene.add(sunLight);
// Star field
var starsGeo = new THREE.BufferGeometry();
var starPositions = new Float32Array(3000);
for (var i = 0; i < 3000; i++) {
starPositions[i] = (Math.random() - 0.5) * 200;
}
starsGeo.setAttribute("position", new THREE.BufferAttribute(starPositions, 3));
var starsMat = new THREE.PointsMaterial({ color: 0xffffff, size: 0.2 });
var stars = new THREE.Points(starsGeo, starsMat);
scene.add(stars);
// Orbit path
var orbitPoints = [];
for (var i = 0; i <= 64; i++) {
var angle = (i / 64) * Math.PI * 2;
orbitPoints.push(new THREE.Vector3(Math.cos(angle) * 4, 0, Math.sin(angle) * 4));
}
var orbitGeo = new THREE.BufferGeometry().setFromPoints(orbitPoints);
var orbitMat = new THREE.LineBasicMaterial({ color: 0x444444 });
var orbit = new THREE.Line(orbitGeo, orbitMat);
scene.add(orbit);
var clock = new THREE.Clock();
function animate() {
var elapsed = clock.getElapsedTime();
earth.position.x = Math.cos(elapsed * 0.5) * 4;
earth.position.z = Math.sin(elapsed * 0.5) * 4;
moon.position.x = earth.position.x + Math.cos(elapsed * 2) * 1;
moon.position.z = earth.position.z + Math.sin(elapsed * 2) * 1;
sun.rotation.y += 0.002;
earth.rotation.y += 0.01;
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
animate();
window.addEventListener("resize", function() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body>
</html>You should see a glowing sun at the center, an orbiting Earth with its moon, and a field of distant stars.
What’s Next
| Topic | Description | Link |
|---|---|---|
| Cameras & Controls | Master Perspective/Orthographic cameras and OrbitControls | https://tutorials.dodatech.com/frontend/libraries/threejs/threejs-cameras-controls/ |
| Geometries & Materials | Deep dive into shapes, PBR materials, and textures | https://tutorials.dodatech.com/frontend/libraries/threejs/threejs-geometries-materials/ |
| Lights & Shadows | Light types, shadow maps, and 3-point lighting | https://tutorials.dodatech.com/frontend/libraries/threejs/threejs-lights-shadows/ |
| Animation | Clocks, easing, TWEEN.js, and GSAP integration | https://tutorials.dodatech.com/frontend/libraries/threejs/threejs-animation/ |
| WebGL Fundamentals | The low-level API that Three.js builds on | WebGL |
This Three.js basics tutorial is your starting point. You now understand the scene-camera-renderer trinity, how to create and animate objects, and why each piece matters. This same architecture powers the 3D visualization dashboard in Durga Antivirus Pro, where threat data is rendered as interactive 3D node graphs. Master these fundamentals, and you can build anything in Three.js.
What’s Next
Congratulations on completing this Threejs Basics tutorial! Here’s where to go from here:
- Practice daily — Consistency is more important than long study sessions
- Build a project — Apply what you learned by building something real
- Explore related topics — Check out other tutorials in the same category
- Join the community — Discuss with other learners and share your progress
Remember: every expert was once a beginner. Keep coding!
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro