Skip to main content

Basic setup

THREE.js docs:

Running a local server​

Download Node.js and then run

npx serve .

in the directory you've set up for this course. You should see something like this:

   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ β”‚
β”‚ Serving! β”‚
β”‚ β”‚
β”‚ - Local: http://localhost:3000 β”‚
β”‚ - Network: http://192.168.0.33:3000 β”‚
β”‚ β”‚
β”‚ Copied local address to clipboard! β”‚
β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Copy that address into your browser.

Minimal scene​

Here is an absolutely minimal scene.

Let's walk through what's going on here.

index.html​

index.html
    <script src="./scene.js" type="module"></script>

This marks the file scene.js as an ES module. ES modules are the modern way to break JavaScript code into reusable chunks and share them. Specifically, this is what lets us use the import syntax in bases.js.

This is also what necessitates running a local server: for security reasons, ES modules can't be run in file:// mode.

index.html
    <script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.182.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.182.0/examples/jsm/"
}
}
</script>

This is an import map. This tells the browser how to resolve "three" to an actual JS file. Without import maps, we would have to write

scene.js
import * as THREE from "https://cdn.jsdelivr.net/npm/three@0.182.0/build/three.module.js";

which is harder to read and maintain.

scene.js​

Now let's take a look at the JavaScript.

import syntax​

scene.js
import * as THREE from "three";

This is a modern import statement. It loads all of THREE.js into a variable called THREE. Unlike older module-loading solutions, the variable THREE is not available outside of scene.jsβ€”this is one advantage of the new syntax.

Another advantage of the new import syntax is that it allows tooling to statically determine which imports you actually use. This enables bundlers (covered in the Framework part of the course) to perform tree shaking, meaning they get rid of code that you don't actually use. If you use a bundler, then it is more efficient (in terms of file size) to only import what you need:

import {
BoxGeometry,
Mesh,
MeshBasicMaterial,
PerspectiveCamera,
Scene,
WebGLRenderer
} from "three";

However, in plain JavaScript it doesn't make a difference. (Although browsers can do the equivalent of tree-shaking, most libraries are not structured in a way that would enable this, since downloading one big file is usually faster than making roundtrips for several small files.)

Boilerplate​

Next we have some boilerplate that should be understandable from the comments. Here are the relevant documentation links:

I don't really know how to do proper camera work, I just copy the same settings between every project.

Adding objects​

The most interesting part is:

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

This is the typical pattern for adding objects to a THREE.js scene. Most objects in THREE.js are Meshes. Every Mesh has an associated BufferGeometry defining its shape and a Material defining its color/appearance. The specific ones we used here are:

warning

If you change the MeshBasicMaterial to another material, such as a THREE.MeshPhongMaterial, the cube will disappear! This is because we haven't yet added any lights to our scene.

Refactored code​

Here is a refactored scene with boilerplate extracted and various niceties added. We will use this setup as the base for everything going forward.