Your first wgpu3d scene
This tutorial builds the smallest interesting wgpu3d scene: a ground, three shapes that move on their own, sunlight with shadows, and a camera you can drag around. It covers the four ideas everything else is built from: the app, objects, meshes and behaviors.
The whole program
import { createApp, SceneObject, MeshBuilder, createGround, material, spin, bob } from "wgpu3d";
const app = await createApp({ canvas: document.querySelector("#start-demo"), environment: "morning" });
app.add(createGround({ size: 60, tiles: 12 }));
const cube = new SceneObject({
mesh: new MeshBuilder().box({ size: [1.6, 1.6, 1.6], material: material("#ff8a3d", { gloss: 0.4 }) }).toMesh(),
position: [-3.2, 1.3, 0]
});
cube.addBehavior(spin(0.8));
const ball = new SceneObject({
mesh: new MeshBuilder().sphere({ radius: 1, sides: 28, rings: 18, material: material("#3d8bff", { gloss: 0.9 }) }).toMesh(),
position: [0, 1.6, 0]
});
ball.addBehavior(bob({ height: 0.5, speed: 2 }));
const beacon = new SceneObject({
mesh: new MeshBuilder()
.cylinder({ center: [0, 0.6, 0], radius: 0.55, height: 1.2, material: material("#e8e2da") })
.cone({ center: [0, 1.9, 0], radius: 0.8, height: 1.4, material: material("#4ade80", { glow: 0.5 }) })
.toMesh(),
position: [3.2, 0, 0]
});
app.add(cube, ball, beacon);
app.orbit({ target: [0, 1, 0], distance: 11, pitch: 0.35, autoRotateSpeed: 0.15 });
app.start();
The app
createApp does the WebGPU setup: it asks the browser for a GPU device, sizes the canvas to its pixels, and creates a scene, a camera and an input object. It is asynchronous because getting a GPU device is, which is why the program uses await at the top level of a module. The environment option picks one of five lighting presets; "morning" has a low sun, so shadows are long.
app.start() begins the frame loop. Each frame runs every behavior, updates positions, moves the camera and draws. When the canvas is scrolled out of view the loop keeps time but skips the work, so a page with several demos stays cool.
Objects and meshes
A SceneObject is anything in the world. It has a position, a rotation (yaw, pitch and roll) and a scale, and optionally a mesh to draw. A mesh is just triangles, and MeshBuilder is how you make them: chain shape calls like box, sphere, cylinder and cone, then finish with toMesh(). The beacon shows that one mesh can hold several shapes, each with its own position inside the object.
Meshes are built once and drawn many times. Moving or rotating an object never rebuilds its mesh; the GPU applies the object's transform as it draws.
Materials
material(color, options) describes a surface. wgpu3d has no textures, so a material is a colour plus two numbers. gloss runs from 0 for chalky to 1 for polished; glossy surfaces get a sharp sun highlight and reflect the sky at grazing angles, which is why the blue sphere picks up a bright rim. glow makes a surface give off its own colour, like the green beacon. Glowing surfaces stay bright in shadow and fade less in fog, but they do not light anything around them.
Behaviors
A behavior is code that runs on an object every frame. spin and bob are two small built-in ones. Your own behavior is just a function that receives the object and a context with the frame time:
cube.addBehavior((object, { deltaSeconds, timeSeconds }) => {
object.yaw += deltaSeconds;
object.scale = [1, 1 + Math.sin(timeSeconds * 3) * 0.2, 1];
});
Behaviors are how the car and character in the next tutorials work, too: VehicleController and CharacterController are behaviors that read the keyboard instead of a clock.
The orbit camera
app.orbit() puts an orbit camera on the canvas: drag to circle the target, scroll or pinch to change distance. autoRotateSpeed turns it slowly on its own, in radians per second. The camera's target is also where the shadow map is centred, so shadows are sharpest where you are looking.
Next
The next tutorial swaps the shapes for a street and a car you drive with the keyboard.