← Back to all projects

LEARN THREEJS FROM SCRATCH

Learn Three.js: From a Blank Canvas to Interactive 3D Worlds

Goal: To master the fundamentals of 3D graphics on the web using Three.js. You will learn to create scenes, manipulate objects, control cameras, add lights and shadows, and build interactive experiences from the ground up.


Why Learn Three.js?

Three.js is the most popular and powerful library for creating 3D graphics in a web browser. It demystifies the complexity of WebGL, giving you a high-level, component-based API to build anything from simple product viewers to complex data visualizations and immersive games. Learning Three.js is not just about graphics; it’s about a new dimension of user interface and experience design.

After completing these projects, you will:

  • Understand the core concepts of a 3D graphics pipeline: scene, camera, renderer, geometry, and materials.
  • Be able to create and animate 3D objects in a browser.
  • Implement realistic lighting, shadows, and textures.
  • Load and display professional 3D models.
  • Build interactive scenes that respond to user input.
  • Integrate physics to create dynamic, living worlds.

Core Concept Analysis

At the heart of every Three.js application lies a simple but powerful structure. Understanding these core components is the key to everything else.

The Three.js “Holy Trinity”

┌─────────────────────────────────────────────────────────────────────────┐
│                                 SCENE                                   │
│  (The container for everything: objects, lights, etc.)                  │
│                                                                         │
│   ┌──────────────────┐   ┌──────────────────┐   ┌──────────────────┐      │
│   │    MESH / OBJECT │   │    MESH / OBJECT │   │      LIGHT       │      │
│   │  (Geometry+Material) │   │  (Geometry+Material) │   │  (Directional, Point)│      │
│   └──────────────────┘   └──────────────────┘   └──────────────────┘      │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
                   │
                   │ (Scene is viewed by the Camera)
                   ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                                 CAMERA                                  │
│  (The user's eye: defines position, perspective, and what's visible)    │
└─────────────────────────────────────────────────────────────────────────┘
                   │
                   │ (The Renderer uses both to draw the final image)
                   ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                                RENDERER                                 │
│  (Draws the scene from the camera's viewpoint onto an HTML <canvas>)    │
└─────────────────────────────────────────────────────────────────────────┘

In short: The Renderer draws the Scene as seen by the Camera.


Project List

These projects are designed to build upon each other, introducing one core concept at a time in a logical and practical way.


Project 1: The Spinning Cube

  • File: LEARN_THREEJS_FROM_SCRATCH.md
  • Main Programming Language: JavaScript
  • Alternative Programming Languages: TypeScript
  • Coolness Level: Level 2: Practical but Forgettable
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 1: Beginner
  • Knowledge Area: 3D Graphics / Rendering
  • Software or Tool: Three.js
  • Main Book: The official Three.js Documentation.

What you’ll build: The “Hello, World!” of 3D graphics: a single webpage displaying a colorful cube that rotates on its own.

Why it teaches Three.js: This project forces you to set up the “Holy Trinity”: the Scene, Camera, and Renderer. It teaches you how to create a 3D object from a Geometry and a Material, add it to the scene, and create a requestAnimationFrame loop to render it and apply animation.

Core challenges you’ll face:

  • Setting up the Three.js boilerplate → maps to creating the scene, camera, and renderer
  • Creating a visible object → maps to combining a geometry and material into a Mesh
  • Positioning the camera → maps to understanding 3D coordinates and making your object visible
  • Creating an animation loop → maps to using requestAnimationFrame to update and render the scene on every frame

Key Concepts:

  • Scene, Camera, Renderer: Three.js Docs - “Creating a scene”
  • Geometries and Materials: Three.js Docs - “Geometries”
  • Animation Loop: MDN Web Docs - requestAnimationFrame()

Difficulty: Beginner Time estimate: A few hours Prerequisites: Basic HTML, CSS, and JavaScript.

Real world outcome: A webpage with a canvas element showing a single, rotating 3D cube. You’ve created life!

Implementation Hints:

  1. HTML: Create a simple HTML file with a <canvas> element.
  2. Scene: const scene = new THREE.Scene();
  3. Geometry: const geometry = new THREE.BoxGeometry(1, 1, 1);
  4. Material: Use a simple material that doesn’t require light: const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
  5. Mesh: Combine them: const cube = new THREE.Mesh(geometry, material); and add it to the scene: scene.add(cube);
  6. Camera: const camera = new THREE.PerspectiveCamera(...). Make sure to move it back from the origin (camera.position.z = 5;) so it can see the cube at (0,0,0).
  7. Renderer: const renderer = new THREE.WebGLRenderer({ canvas: yourCanvas });
  8. Animation Loop: Create a function animate() that calls requestAnimationFrame(animate). Inside this function, update the cube’s rotation (cube.rotation.x += 0.01;) and call renderer.render(scene, camera);. Start it by calling animate() once.

Learning milestones:

  1. A black square appears → Your renderer is working.
  2. A static cube appears → Your camera and object are set up correctly.
  3. The cube rotates → Your animation loop is running successfully.

Project 2: A Solar System Mobile

  • File: LEARN_THREEJS_FROM_SCRATCH.md
  • Main Programming Language: JavaScript
  • Alternative Programming Languages: TypeScript
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: Scene Graphs / Transformations
  • Software or Tool: Three.js
  • Main Book: “Three.js Journey” by Bruno Simon (as a reference for best practices).

What you’ll build: A simple animated solar system. A central “sun” with a planet orbiting it, and a moon orbiting the planet.

Why it teaches Three.js: This project teaches the concept of the scene graph and object parenting. To make a moon orbit a planet while the planet orbits the sun, you can’t just animate them independently. You must make the moon a child of the planet. This is a fundamental concept for organizing complex scenes.

Core challenges you’ll face:

  • Object Parenting → maps to using object.add(child) to create hierarchies
  • Understanding Local vs. World Transformations → maps to realizing that a child’s position is relative to its parent
  • Managing Multiple Animations → maps to updating the rotation of multiple objects in the render loop at different speeds
  • Using Helper Objects → maps to using THREE.Group or Object3D as invisible pivots for orbits

Key Concepts:

  • Scene Graph: Three.js Docs - Object3D (read about the parent/child properties).
  • Groups: Three.js Docs - Group.

Difficulty: Intermediate Time estimate: Weekend Prerequisites: Project 1.

Real world outcome: An animation of a solar system in motion. You’ll see a small “moon” sphere orbiting a larger “planet” sphere, and the entire planet-moon system will be orbiting a central “sun” sphere.

Implementation Hints:

  1. Create your three objects: sun, planet, and moon.
  2. Don’t try to calculate the moon’s world position in code. Instead, use parenting.
  3. Bad way: Animate sun, animate planet around sun, animate moon around planet. This gets very complicated.
  4. Good way:
    • Create an invisible Object3D called planetPivot. Add it to the sun.
    • Add the planet mesh to the planetPivot. Position the planet away from the pivot’s center (e.g., planet.position.x = 5;).
    • Create an invisible Object3D called moonPivot. Add it to the planet.
    • Add the moon mesh to the moonPivot. Position the moon away from its pivot’s center (e.g., moon.position.x = 2;).
  5. In your animation loop, you only need to rotate the pivots! planetPivot.rotation.y += 0.005; and moonPivot.rotation.y += 0.01;. The child objects will follow automatically.

Learning milestones:

  1. A planet orbits the sun → You understand basic object animation relative to the world center.
  2. A moon is stuck to a planet as it orbits → You have successfully parented one object to another.
  3. The moon orbits the planet, which orbits the sun → You have mastered the scene graph and pivot objects for complex animations.

Project 3: Textured Earth and Moon

  • File: LEARN_THREEJS_FROM_SCRATCH.md
  • Main Programming Language: JavaScript
  • Alternative Programming Languages: TypeScript
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: Materials / Texturing
  • Software or Tool: Three.js, TextureLoader
  • Main Book: N/A, use online tutorials and the official documentation.

What you’ll build: A realistic-looking Earth with a textured moon orbiting it. You’ll use real image textures for the Earth’s surface, clouds, and the moon’s craters. You will also introduce lighting.

Why it teaches Three.js: This project dives deep into Materials. You’ll learn how to load images and use them as textures to make your objects look realistic. You will also replace MeshBasicMaterial with MeshStandardMaterial and add lights, which is essential for any realistic scene.

Core challenges you’ll face:

  • Loading Textures → maps to using the TextureLoader class asynchronously
  • Applying Textures to Materials → maps to setting the .map property of a material
  • Introducing Lights → maps to adding AmbientLight and DirectionalLight to the scene
  • Using PBR Materials → maps to switching to MeshStandardMaterial to get realistic light interaction

Resources for key challenges:

  • Find free, public domain textures for the Earth (day map, cloud map) and the moon. NASA’s Visible Earth is a great source.
  • Three.js Docs on TextureLoader

Key Concepts:

  • Textures: Three.js Docs - Texture
  • Standard Material: Three.js Docs - MeshStandardMaterial
  • Lighting: Three.js Docs - “Lights” category

Difficulty: Intermediate Time estimate: Weekend Prerequisites: Project 2.

Real world outcome: A beautiful, animated model of the Earth and Moon, with realistic surfaces that react to a light source. You’ll see the light glint off the Earth’s oceans and the shadows crawl across the moon’s craters as they rotate.

Implementation Hints:

  1. Create a THREE.TextureLoader() instance.
  2. Use textureLoader.load('path/to/earth.jpg') to get an earth texture.
  3. Create a SphereGeometry.
  4. Create a new THREE.MeshStandardMaterial({ map: earthTexture });.
  5. Create the Earth mesh and add it to the scene.
  6. Your scene will be black! You need lights. Add a soft AmbientLight to simulate bounced light, and a DirectionalLight to simulate the sun.
  7. Position the DirectionalLight (e.g., light.position.set(5, 3, 5)) to control where the “sunlight” comes from.
  8. For extra credit, create a second sphere for clouds slightly larger than the Earth, give it a cloud texture with transparency, and animate its rotation at a slightly different speed.

Learning milestones:

  1. An image is wrapped around a sphere → You understand basic texturing.
  2. The scene is no longer black → You have successfully added lights.
  3. The planet’s surface has highlights and shadows → You are using PBR materials and lights correctly.

Project 4: Interactive Museum

  • File: LEARN_THREEJS_FROM_SCRATCH.md
  • Main Programming Language: JavaScript
  • Alternative Programming Languages: TypeScript
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: Interactivity / User Controls / Raycasting
  • Software or Tool: Three.js, OrbitControls, Raycaster
  • Main Book: “Interactive Computer Graphics” by Foley, van Dam, et al. (for theory).

What you’ll build: A virtual “pedestal” with a 3D object on it. The user can use the mouse to rotate, pan, and zoom around the object. When the user clicks on the object, its color changes.

Why it teaches Three.js: This project is the gateway to interactive experiences. You’ll learn how to cede camera control to the user with OrbitControls and how to translate a 2D mouse click into a 3D interaction using the powerful Raycaster object.

Core challenges you’ll face:

  • Implementing Camera Controls → maps to importing and using OrbitControls
  • Detecting Mouse Clicks → maps to listening for DOM events on the canvas
  • Converting 2D Mouse Position to 3D Coordinates → maps to the core concept of raycasting
  • Detecting Intersections → maps to using the Raycaster to find which object is under the mouse

Key Concepts:

  • OrbitControls: Three.js Docs - OrbitControls
  • Raycasting: Three.js Docs - Raycaster
  • Normalized Device Coordinates: Understanding the (-1 to +1) coordinate system for the mouse.

Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Project 1, understanding of DOM events.

Real world outcome: An interactive 3D object viewer. You can tumble the object around with your mouse, and clicking it will make it flash a different color, confirming the interaction worked.

Implementation Hints:

  1. Controls: Import OrbitControls from the examples folder (three/examples/jsm/controls/OrbitControls.js). Instantiate it with your camera and canvas: const controls = new OrbitControls(camera, renderer.domElement);. You’ll need to call controls.update() in your animation loop.
  2. Raycasting Setup:
    • Create const raycaster = new THREE.Raycaster(); and const mouse = new THREE.Vector2();.
    • Add a window.addEventListener('click', onClick);.
  3. Inside onClick:
    • Calculate the mouse position in normalized device coordinates (from -1 to +1 for both components). mouse.x = (event.clientX / window.innerWidth) * 2 - 1; and mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;.
    • Set the raycaster’s origin and direction: raycaster.setFromCamera(mouse, camera);.
    • Get a list of intersected objects: const intersects = raycaster.intersectObjects(scene.children);.
    • If intersects.length > 0, the first object in the array (intersects[0].object) is the one you clicked. You can then change its material’s color.

Learning milestones:

  1. You can rotate the camera around the cube with the mouseOrbitControls is working.
  2. A click event is registered → Your DOM event listener is set up.
  3. The console logs the name of the clicked object → Raycasting is correctly configured.
  4. The object changes color on click → You have a complete, interactive 3D application.

Project 5: GLTF Model Viewer

  • File: LEARN_THREEJS_FROM_SCRATCH.md
  • Main Programming Language: JavaScript
  • Alternative Programming Languages: TypeScript
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: Asset Loading / Scene Composition
  • Software or Tool: Three.js, GLTFLoader, OrbitControls
  • Main Book: N/A, focus on documentation and online model sources.

What you’ll build: A webpage that loads an external, complex 3D model in the .gltf or .glb format and displays it in an interactive viewer with lighting and camera controls.

Why it teaches Three.js: This is an essential, real-world skill. Professional 3D models are built in software like Blender, not coded by hand. This project teaches you how to work with the GLTFLoader to bring rich, detailed assets into your scenes, complete with their own materials and animations.

Core challenges you’ll face:

  • Loading an external asset asynchronously → maps to using loaders with callbacks or promises
  • Handling the loaded scene graph → maps to understanding that a loaded model is a full Scene object itself
  • Adding the model to your scene → maps to scene.add(gltf.scene)
  • Troubleshooting model issues → maps to dealing with scale, rotation, and lighting problems

Resources for key challenges:

Key Concepts:

  • Asset Loaders: The concept of asynchronous loading in Three.js.
  • GLTF Format: Understanding why it’s the standard for 3D on the web.
  • Traversing Scene Graphs: Using model.traverse(...) to modify materials or find specific child objects.

Difficulty: Intermediate Time estimate: Weekend Prerequisites: Projects 1, 3, and 4.

Real world outcome: A professional-looking 3D model (e.g., a car, a character, a piece of furniture) is displayed and interactively explorable in your browser, complete with proper lighting and shadows.

Implementation Hints:

  1. Import GLTFLoader from the examples folder.
  2. Instantiate the loader: const loader = new GLTFLoader();.
  3. Call the load method: loader.load('path/to/model.gltf', (gltf) => { ... });. The second argument is a callback function that executes once the model is ready.
  4. Inside the callback, the gltf object contains the loaded content. The part you want to add to your scene is gltf.scene.
  5. You will likely need to adjust the model’s scale (gltf.scene.scale.set(...)) and position to fit your scene.
  6. Ensure your scene has adequate lighting, as most GLTF models use MeshStandardMaterial and will appear black without lights. An AmbientLight and one or two DirectionalLights are a good start.
  7. For extra credit, play an animation that comes with the model using THREE.AnimationMixer.

Learning milestones:

  1. The browser’s network tab shows the .gltf file being downloaded → You’re calling the loader correctly.
  2. A static model appears in your scene → You’ve successfully added the loaded scene graph.
  3. The model is properly lit and has shadows → You’ve integrated the loaded asset into your own lighting environment.

Project 6: Physics-Based Playground

  • File: LEARN_THREEJS_FROM_SCRATCH.md
  • Main Programming Language: JavaScript
  • Alternative Programming Languages: TypeScript
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 3: Advanced
  • Knowledge Area: Physics Simulation / Game Development
  • Software or Tool: Three.js, Cannon-es.js (or Rapier)
  • Main Book: N/A, physics library documentation is key.

What you’ll build: A simple physics simulation where you can click to drop spheres, boxes, and other shapes from the sky, and watch them realistically collide, bounce, and stack on top of each other on a ground plane.

Why it teaches Three.js: This project teaches the crucial concept of separating the visual world (Three.js) from the physical world (the physics engine). You will learn to create a “physics body” for every visual mesh and synchronize their positions and rotations on every frame, creating dynamic and emergent behavior.

Core challenges you’ll face:

  • Integrating a third-party physics library → maps to setting up a CANNON.World
  • Creating parallel visual and physical objects → maps to for every THREE.Mesh, create a corresponding CANNON.Body
  • Synchronizing worlds in the animation loop → maps to copying the position and quaternion from the physics body to the Three.js mesh
  • Applying forces → maps to using body.applyForce() or setting velocity to make things move

Key Concepts:

  • Physics Engine Integration: Reading the cannon-es documentation.
  • Separation of Concerns: Understanding that Three.js draws, and Cannon-es calculates.
  • Quaternions: Using mesh.quaternion.copy(body.quaternion) for correct rotation, as Euler angles can cause gimbal lock.

Difficulty: Advanced Time estimate: 2-3 weeks Prerequisites: Project 1, understanding of vectors.

Real world outcome: An interactive sandbox. Clicking on the screen spawns a sphere that falls realistically, bounces off the floor, and can knock over a tower of boxes you’ve already created. It feels like a real, dynamic world.

Implementation Hints:

  1. Set up your Three.js scene as usual.
  2. Set up your Cannon-es world: const world = new CANNON.World({ gravity: new CANNON.Vec3(0, -9.82, 0) });.
  3. Create a visual floor (THREE.PlaneGeometry) and a physical floor (CANNON.Plane).
  4. Write a function createSphere(position) that does two things:
    • Creates a THREE.Mesh with SphereGeometry and adds it to the Three.js scene.
    • Creates a CANNON.Body with a CANNON.Sphere shape, sets its position, and adds it to the Cannon world.
    • Store both the mesh and the body in an array of objects.
  5. In your animation loop:
    • Update the physics world: world.step(1 / 60, clock.getDelta());.
    • Loop through your array of objects. For each object, copy the physics body’s position and rotation to the Three.js mesh: mesh.position.copy(body.position); and mesh.quaternion.copy(body.quaternion);.
    • Render the Three.js scene.

Learning milestones:

  1. A sphere falls through the floor → Both worlds are running, but not interacting.
  2. A sphere stops on the floor → You have correctly defined two bodies and they are colliding.
  3. You can spawn multiple objects that stack and tumble → You are managing multiple dynamic bodies and your synchronization loop is correct.

Project 7: Custom Shader Water

  • File: LEARN_THREEJS_FROM_SCRATCH.md
  • Main Programming Language: JavaScript, GLSL
  • Alternative Programming Languages: N/A
  • Coolness Level: Level 5: Pure Magic (Super Cool)
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 4: Expert
  • Knowledge Area: Shaders / GPU Programming
  • Software or Tool: Three.js, ShaderMaterial, GLSL language
  • Main Book: “The Book of Shaders” by Patricio Gonzalez Vivo & Jen Lowe.

What you’ll build: A flat plane that looks like a rippling water surface. The waves will be generated procedurally using noise functions directly on the GPU with a custom shader.

Why it teaches Three.js: This project opens the door to the GPU. You’ll bypass Three.js’s standard materials and write your own vertex and fragment shaders in GLSL. This gives you ultimate control over an object’s geometry and color, allowing for effects that are impossible with standard materials.

Core challenges you’ll face:

  • Writing GLSL code → maps to learning a new C-like language that runs on the GPU
  • Using ShaderMaterial → maps to creating a custom Three.js material from your GLSL code
  • Passing data from JS to the shader (Uniforms) → maps to sending data like time or mouse position to the GPU
  • Manipulating vertices in the Vertex Shader → maps to displacing the plane’s vertices to create waves
  • Calculating color in the Fragment Shader → maps to coloring the pixels based on the wave height or angle

Key Concepts:

  • Vertex Shader: A program that runs for every vertex of a geometry. Its main job is to set gl_Position.
  • Fragment Shader: A program that runs for every pixel on the screen. Its main job is to set gl_FragColor.
  • Uniforms: Variables that are constant for all vertices/fragments in a single draw call (e.g., u_time).
  • Varyings: Variables passed from the vertex shader to the fragment shader.

Difficulty: Expert Time estimate: 2-3 weeks Prerequisites: Project 1, strong vector math understanding.

Real world outcome: A mesmerizing, endlessly moving, and photo-realistic (or stylized) water surface, rendered efficiently in real-time. Moving your mouse over it could create ripples.

Implementation Hints:

  1. Create a PlaneGeometry.
  2. Create a ShaderMaterial and provide it with vertexShader and fragmentShader strings.
  3. Define a uniforms object, which must include a u_time uniform: uniforms: { u_time: { value: 0.0 } }.
  4. In the animation loop, update the time uniform: material.uniforms.u_time.value = clock.getElapsedTime();.
  5. Vertex Shader (GLSL):
    • Get the vertex position (position).
    • Use a noise function (you can find GLSL noise functions online) with the position and u_time to calculate a wave height (elevation).
    • Create a new position vector where the y value is displaced by the elevation.
    • Set gl_Position using this new vector.
  6. Fragment Shader (GLSL):
    • You can receive the elevation from the vertex shader via a varying.
    • Set the pixel color (gl_FragColor) based on the elevation, making peaks white and troughs dark blue.

Learning milestones:

  1. The plane renders with a solid color from your fragment shader → Your ShaderMaterial is working.
  2. The plane’s color changes over time → You are successfully passing a u_time uniform to the GPU.
  3. The plane’s geometry is visibly distorted into waves → Your vertex shader is correctly manipulating vertex positions.
  4. The final water effect is colored and animated → You have mastered the fundamentals of shader programming.

Summary

Project Main Programming Language
Project 1: The Spinning Cube JavaScript
Project 2: A Solar System Mobile JavaScript
Project 3: Textured Earth and Moon JavaScript
Project 4: Interactive Museum JavaScript
Project 5: GLTF Model Viewer JavaScript
Project 6: Physics-Based Playground JavaScript
Project 7: Custom Shader Water JavaScript, GLSL