Skip to main content
← 3D on the Web

Lesson 3 of 5

Your First Scene

A lit cube turning in space, in about fifteen lines. Same graphics card, none of the shaders written by hand.

What you will learn

  • import maps
  • Scene
  • PerspectiveCamera
  • WebGLRenderer
  • geometry and material
  • Mesh
  • DirectionalLight
  • delta time

Before you start

The two WebGL lessons. You do not need them to run this page, but the point of the lesson is what is no longer here.

What changed

This page draws a lit cube that turns. It is doing far more than the triangle did, and the code is much shorter. There are no shaders in this file. There are no buffers, no attributes, and no drawArrays.

All of those things are still happening. three.js writes the shaders and fills the buffers for you. That is what a library is. The reason the last two lessons were worth the trouble is that you now know what is under this one.

Where three.js comes from

This is the first page in the whole site that depends on something outside its own folder.

In index.html, before the module runs
<script type="importmap">
{
    "imports": {
        "three": "https://cdn.jsdelivr.net/npm/three@0.185.1/build/three.module.js"
    }
}
</script>

An import map says where a name comes from. Without it, import * as THREE from "three" sends the browser looking for a folder called three next to the page, and it finds nothing. The import map turns the bare name into a real address.

The version number is pinned on purpose. three.js publishes a new release most months, and some of them rename things. A lesson that says latest is a lesson that breaks by itself one morning, with no change from you.

The three things every scene has

What it is
SceneThe list of everything in the world.
CameraWhere you are looking from, and how wide.
RendererThe thing that turns those two into pixels.
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, WIDTH / HEIGHT, 0.1, 100);
var renderer = new THREE.WebGLRenderer({ canvas: canvas });

renderer.render(scene, camera);

Nothing appears until something calls render. A scene full of objects with no render call is a black canvas, and so is a render call with the camera inside the object.

  • 45 is how wide the view is, in degrees. It works like a zoom.
  • WIDTH / HEIGHT is the shape of the picture. Get it wrong and everything is stretched.
  • 0.1 and 100 are the nearest and furthest anything is drawn. Anything closer or further away vanishes.

Move the camera, change the spin

The camera distance is the interesting one. The box never changes size. You move.

4

How far back you stand. The box does not change size. You do.

0.35 turns/s

Geometry plus material

var geometry = new THREE.BoxGeometry(1.4, 1.4, 1.4);
var material = new THREE.MeshStandardMaterial({ color: BOX_COLOUR });
var box = new THREE.Mesh(geometry, material);

scene.add(box);

A mesh is always exactly two things joined: a geometry, which is the shape, and a material, which is what the surface is like. Every visible object in three.js is these two things. Swap either one and you get a different object.

Try swapping BoxGeometry for SphereGeometry, then for TorusKnotGeometry. One word each time, and the material never changes.

Paint it

Move these and watch your page. The code changes too.

#f97316
#12121a

The mistake everybody makes once

Comment out both lights and run it. Sit with the black screen for a moment, because you will see it again. Then put them back.

var lamp = new THREE.DirectionalLight("#ffffff", 3);
lamp.position.set(2, 3, 4);
scene.add(lamp);

scene.add(new THREE.AmbientLight("#ffffff", 0.4));

The directional light comes from one direction, like the sun, and it is what makes one face of the cube brighter than another. The ambient light comes from everywhere at once, and its only job is to stop the dark faces being pure black.

Turning at the same speed everywhere

var seconds = clock.getDelta();
box.rotation.y += SPIN_Y * seconds * Math.PI * 2;

getDelta() is the seconds since the last frame. Turning the box by a fixed amount per frame would spin it twice as fast on a 120 Hz screen as on a 60 Hz one. Multiplying by the time since the last frame makes the speed the same on every machine. The platformer course makes the same point about movement, and it is the same fix.

Try these changes

Make these changes in the files beside this guide. Each one only uses what this lesson has already covered. Tick each one when it works.

Check yourself

Three quick questions on this lesson. Get them all right to finish the lesson and earn a pack.

Loading your answers

Teacher's versionHow to run this lesson, what to check, and any answers. Students do not need this.

How to teach it

  • Put the triangle from lesson 1 on the screen next to this one. Count the lines out loud. That comparison is the lesson.
  • Turn the lights off as a class exercise, not as a warning. The black screen has to be experienced or the sentence does not stick.
  • Do the geometry swaps live. Three swaps, one word each, and the material untouched. It teaches the geometry and material split faster than any explanation.
  • The camera distance slider is worth two minutes on its own. Students expect the box to get bigger. Ask what is actually changing.
  • If the page does not load, check three things in this order: is it being served rather than opened as a file, is the network blocking the CDN, and is type="module" on the script tag.