Skip to main content

Command Palette

Search for a command to run...

My First Three.js Scene: Building a 3D World in the Browser

Updated
โ€ข8 min read
My First Three.js Scene: Building a 3D World in the Browser

Hey there! ๐Ÿ‘‹ So I recently started my journey into the world of Three.js, and I wanted to document what I learned while creating my first 3D scene. If you know JavaScript or React but 3D graphics feels like magic, you're in the right place!

What is Three.js Anyway?

Three.js is a JavaScript library that makes it easier to create and display 3D graphics in the browser using WebGL. Without Three.js, you'd have to write complex WebGL code. With Three.js, you can create 3D scenes with just a few lines of JavaScript!

The Movie Set Analogy

Before diving into code, let me share an analogy that helped me understand Three.js concepts:

Think of Three.js like creating a movie:

  • Scene - The movie set where everything happens

  • Camera - What the audience sees (their perspective)

  • Objects - Actors and props on the set

  • Lights - Studio lighting to make things visible

  • Renderer - The camera crew that captures and displays everything

Now let's build our first scene!

Setting Up: The HTML Canvas

First, we need a canvas element in our HTML where Three.js will draw our 3D scene:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My First Three.js Scene</title>
    <style>
        body { margin: 0; overflow: hidden; }
        canvas { display: block; }
    </style>
</head>
<body>
    <canvas id="canvas"></canvas>
    <script type="module" src="script.js"></script>
</body>
</html>

Step 1: Creating the Scene

import * as THREE from "three";

const canvas = document.getElementById("canvas");

// 1. Create the Scene - This is our movie set
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xbbbbbb); // Light gray background

The scene is like an empty room or stage. Right now it's empty, but we'll add objects, lights, and a camera to it. The background color is like painting the walls of your room.

Step 2: Setting Up the Camera

// 2. Create the Camera - This is the audience's viewpoint
const w = window.innerWidth;
const h = window.innerHeight;

// PerspectiveCamera mimics how human eyes see the world
// Parameters: field of view, aspect ratio, near clipping, far clipping
const camera = new THREE.PerspectiveCamera(75, w / h, 0.1, 1000);
camera.position.z = 5; // Move camera back so we can see objects

Imagine you're holding a camera. The field of view (75) is how wide your lens is - a wider angle sees more. The aspect ratio (w/h) ensures things don't look stretched. The near (0.1) and far (1000) values are like how close and how far you can see clearly.

The position.z = 5 moves our camera backward. In Three.js, the default coordinate system has:

  • X-axis: left (-) to right (+)

  • Y-axis: down (-) to up (+)

  • Z-axis: into screen (-) to out toward you (+)

Step 3: Creating Geometries (Shapes)

// 3. Create Geometries - The basic shapes of our objects
const icosahedronGeometry = new THREE.IcosahedronGeometry(1, 0);
// Parameters: radius (1), detail level (0 = low poly)

const boxGeometry = new THREE.BoxGeometry(2, 0.1, 2);
// Parameters: width (2), height (0.1), depth (2)

Geometry is like the skeleton or frame of an object. Think of it as a wireframe structure before you add color or texture. An icosahedron is a 20-sided dice shape, and a box is... well, a box!

Step 4: Creating Materials (Appearance)

// 4. Create Materials - How our objects look (color, shininess, etc.)
const icosahedronMaterial = new THREE.MeshLambertMaterial({ 
    color: 0x00ff00,      // Green color
    emissive: 0x00aa00    // Slight green glow
});

const boxMaterial = new THREE.MeshStandardMaterial({ 
    color: 0xff0000,      // Red color
    emissive: 0xaa0000    // Slight red glow
});

If geometry is the skeleton, material is the skin, paint, and texture. It defines whether something is shiny, matte, colorful, or transparent. The emissive property makes objects glow slightly, like they have their own light.

Note: MeshLambertMaterial and MeshStandardMaterial require lights to be visible. Without lights, they'd appear black!

Step 5: Creating Meshes (Combining Shape + Appearance)

// 5. Create Meshes - Combine geometry + material
const icosahedron = new THREE.Mesh(icosahedronGeometry, icosahedronMaterial);
const box = new THREE.Mesh(boxGeometry, boxMaterial);

// Position the box below the icosahedron
box.position.y = -1.5;

// Add both objects to our scene
scene.add(icosahedron);
scene.add(box);

A mesh is the final "actor" on our set. It's the geometry (skeleton) wearing the material (skin/clothes). Now our objects exist in the scene and are ready to be seen!

Step 6: Let There Be Light!

// 6. Add Lighting - Without this, we'd see nothing!
const spotLight = new THREE.SpotLight(0xffffff, 100);
// Parameters: color (white), intensity (100)

spotLight.position.set(10, 10, 10);
// Position it above and to the side

scene.add(spotLight);

Just like a movie set needs studio lights, our 3D scene needs lights to illuminate objects. Without lights, materials like MeshLambertMaterial would appear completely black because they depend on light to show their colors.

Step 7: The Renderer - Bringing It All Together

// 7. Create the Renderer - This displays everything on screen
const renderer = new THREE.WebGLRenderer({ canvas: canvas });

// Use device pixel ratio for sharper rendering on high-DPI screens
renderer.setPixelRatio(window.devicePixelRatio);

// Set renderer size to fill the window
renderer.setSize(w, h);

The renderer is like the film crew that captures everything and projects it onto the screen. It takes the scene, camera view, and all objects, then draws them on the canvas element.

Step 8: The Animation Loop - Making Things Move!

Here's where the magic happens. We need to continuously render our scene to create animation:

// 8. Animation Loop - Re-render the scene every frame
function animateScene() {
    // Request the browser to call this function before the next repaint
    window.requestAnimationFrame(animateScene);

    // Rotate the icosahedron on X and Y axes
    icosahedron.rotation.x += 0.01;
    icosahedron.rotation.y += 0.01;

    // Rotate the box slower on Y axis only
    box.rotation.y += 0.005;

    // Render the scene from the camera's perspective
    renderer.render(scene, camera);
}

// Start the animation loop
animateScene();

Why do we need an animation loop?

Think about how movies work - they're just a series of still images shown rapidly (typically 24โ€“60 frames per second). Our animation loop does the same thing:

  1. Update - Change object positions, rotations, or properties

  2. Render - Draw the updated scene

  3. Repeat - Do it again for the next frame

requestAnimationFrame is the browser's built-in way to say "call this function right before you redraw the screen" - typically 60 times per second. This creates smooth animations!

Without this loop, we'd just see a single static frame. With it, our objects rotate smoothly.

Step 9: Responsive Design - Handle Window Resizing

// 9. Handle window resize events
window.addEventListener("resize", () => {
    const w = window.innerWidth;
    const h = window.innerHeight;

    // Update camera aspect ratio
    camera.aspect = w / h;
    camera.updateProjectionMatrix(); // Must call this after changing aspect

    // Update renderer size
    renderer.setSize(w, h);
});

Why is this important?

When the window resizes, our canvas and camera need to adjust. Without this, your 3D scene would look stretched or squished. The updateProjectionMatrix() call is crucial - it recalculates how the camera projects 3D space onto 2D screen space.

The Complete Code

Here's everything together:

import * as THREE from "three";

const canvas = document.getElementById("canvas");

// 1. Scene - The movie set
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xbbbbbb);

// 2. Camera - The audience's viewpoint
const w = window.innerWidth;
const h = window.innerHeight;
const camera = new THREE.PerspectiveCamera(75, w / h, 0.1, 1000);
camera.position.z = 5;

// 3. Geometries - Basic shapes
const icosahedronGeometry = new THREE.IcosahedronGeometry(1, 0);
const boxGeometry = new THREE.BoxGeometry(2, 0.1, 2);

// 4. Materials - How objects look
const icosahedronMaterial = new THREE.MeshLambertMaterial({ 
    color: 0x00ff00, 
    emissive: 0x00aa00 
});
const boxMaterial = new THREE.MeshStandardMaterial({ 
    color: 0xff0000, 
    emissive: 0xaa0000 
});

// 5. Meshes - Combine geometry + material
const icosahedron = new THREE.Mesh(icosahedronGeometry, icosahedronMaterial);
const box = new THREE.Mesh(boxGeometry, boxMaterial);
box.position.y = -1.5;

scene.add(icosahedron);
scene.add(box);

// 6. Lighting - Make objects visible
const spotLight = new THREE.SpotLight(0xffffff, 100);
spotLight.position.set(10, 10, 10);
scene.add(spotLight);

// 7. Renderer - Display everything
const renderer = new THREE.WebGLRenderer({ canvas: canvas });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(w, h);

// 8. Animation Loop - Create movement
function animateScene() {
    window.requestAnimationFrame(animateScene);

    icosahedron.rotation.x += 0.01;
    icosahedron.rotation.y += 0.01;
    box.rotation.y += 0.005;

    renderer.render(scene, camera);
}

animateScene();

// 9. Handle window resize
window.addEventListener("resize", () => {
    const w = window.innerWidth;
    const h = window.innerHeight;

    camera.aspect = w / h;
    camera.updateProjectionMatrix();
    renderer.setSize(w, h);
});

What I Learned

1. The Three.js Pattern is Consistent

Every Three.js project follows this pattern:

  • Create a scene

  • Set up a camera

  • Create objects (geometry + material = mesh)

  • Add lights

  • Create a renderer

  • Animate with a render loop

Once you understand this flow, you can build any 3D scene!

2. The Animation Loop is the Heart

The requestAnimationFrame loop is what brings everything to life. Without it, you just have a static image. This is where you:

  • Update object properties (position, rotation, scale)

  • Handle user interactions

  • Render the updated scene

3. Coordinates Take Practice

The 3D coordinate system (X, Y, Z) takes time to internalize. Remember:

  • Positive Z comes toward you (out of screen)

  • Positive Y goes up

  • Positive X goes right

4. Materials Need Lights

This caught me initially! Materials like MeshLambertMaterial and MeshStandardMaterial won't show up without lights. If you want objects visible without lights, use MeshBasicMaterial.

5. Small Values Create Smooth Animation

Notice the rotation values are tiny (0.01, 0.005). That's because the animation loop runs ~60 times per second. Small increments add up to smooth, visible movement!

6. Window Resizing Matters

Always handle resize events to keep your scene looking good on all screen sizes. Don't forget updateProjectionMatrix() after changing the camera's aspect ratio!

If you're starting your Three.js journey too, I'd recommend:

  1. Build this basic scene yourself

  2. Experiment by changing values (colors, positions, rotation speeds)

  3. Add new shapes and lights

  4. Break things and fix them - that's how you learn!

The Three.js documentation is really helpful, so don't hesitate to explore it as you build.

Happy coding! ๐Ÿš€


This is part of my learning journey, documenting my exploration of Three.js and 3D web graphics. Follow along as I build toward creating my own 3D portfolio website!

More from this blog

Dipankar Paul's blog

51 posts