Build your first Yawn scene
This guide starts from an empty HTML file and ends with a WebGPU triangle you can move, shade, and inspect. You do not need engine experience. Every new term is explained where it first appears.
Type the examples exactly as shown. Words in
code style are names the computer expects; the
prose around them explains why.
Load Handles from Yawn’s CDN
You do not build or host Yawn yourself. An
import map gives the CDN module a short name that the
rest of this tutorial can use. Put this inside your page’s
<head>:
<script type="importmap">
{
"imports": {
"@yawn/core": "https://yawn.heaust.org/pkg/core.js",
"@yawn/handles": "https://yawn.heaust.org/pkg/handles.js"
}
}
</script>
Core and Handles stay separate: Handles uses the mapped Core module instead of containing its own copy. Their render worker, import worker, picking worker, and WebAssembly module all continue loading from Yawn’s CDN automatically.
Make a canvas
A canvas is the rectangle where WebGPU will draw. Add
one to the page body:
<canvas id="view" width="1280" height="720"></canvas>
<script type="module" src="/app.js"></script>
Your own web host must send COOP: same-origin and
COEP: require-corp so SharedArrayBuffer is
available. The CDN already sends the matching CORS and
resource-policy headers for Yawn’s files.
Scene → material → mesh
A scene owns the shared data and render graph. A material describes the surface. A mesh supplies points and tells Yawn which order to connect them.
import "@yawn/core";
import { Mesh, PBRMaterial, Scene } from "@yawn/handles";
const canvas = document.querySelector("#view");
const scene = new Scene(canvas, { hdr: true, fps: 60 });
await scene.ready;
const sky = new PBRMaterial(scene, {
baseColor: [0.12, 0.58, 1, 1],
metallic: 0.15,
roughness: 0.35,
});
await sky.ready;
const triangle = new Mesh(scene, {
material: sky,
vertexData: {
positions: [-0.7, -0.6, 0, 0.7, -0.6, 0, 0, 0.72, 0],
indices: [0, 1, 2],
},
});
await triangle.ready;
new
Creates one object. Here that is a scene, material, or mesh handle.
await …ready
Waits for setup to finish before the next object depends on it.
[x, y, z]
One point in 3D space: horizontal, vertical, and depth.
Add a camera you can orbit
The starter triangle is already in clip space, so it is visible
without a camera. For a 3D world, add an
ArcRotateCamera. Drag to orbit and use the wheel to
zoom.
import { ArcRotateCamera } from "@yawn/handles";
const camera = new ArcRotateCamera(scene, {
target: triangle,
alpha: 0,
beta: Math.PI / 2,
radius: 3,
controls: { element: canvas, pointer: true },
});
await camera.ready;
Clone geometry, not work
Every Mesh is an instance. Calling
clone() shares its geometry and creates only a new
transform and mesh slot. If one clone later changes its vertex
data, Yawn makes that geometry unique automatically.
for (let x = -4; x <= 4; x++) {
const copy = triangle.clone({ position: [x * 0.35, 0, 0] });
await copy.ready;
}
Materials and lights are ordinary handles
Change a material after it is ready and Yawn writes the new value directly into its shared row. Lights use the same pattern.
import { AmbientLight, PointLight } from "@yawn/handles";
const key = new PointLight(scene, {
position: [0, 1, 1],
color: [1, 0.72, 0.5],
intensity: 12,
range: 8,
});
const fill = new AmbientLight(scene, {
color: [0.08, 0.2, 0.5],
intensity: 0.3,
});
await Promise.all([key.ready, fill.ready]);
sky.roughness = 0.5; // one shared-memory write
Bring in a glTF model
importGltf fetches and parses .gltf or
.glb data in a worker, then creates ordinary Yawn
meshes and materials.
import { importGltf } from "@yawn/handles";
const meshes = await importGltf(scene, "/models/robot.glb");
meshes[0].position.y = 0.5;
Add effects without leaving the scene API
Effect handles add passes to the same render graph. Batch related additions to rebuild that graph once.
import { ColorGrading, FXAA } from "@yawn/handles";
await scene.batchGraphUpdates(async () => {
const grade = new ColorGrading(scene, { toneMap: "aces" });
const fxaa = new FXAA(scene);
await Promise.all([grade.ready, fxaa.ready]);
});
Setup is messages. Motion is memory.
Expensive structural changes—creating rows, allocating IDs, or replacing a render graph—go to the render worker as messages. Values that already exist—positions, colors, camera matrices, light strengths—change in shared memory.
Measure the passes the GPU actually ran
Open Profile in the playground to enable timestamp queries. Yawn reports the physical pass names and GPU milliseconds without serializing the render queue. Support depends on the browser and adapter.
The playground’s profiler uses
core.onProfile() and
core.setProfiler(true)—the same public APIs
available to your app.
Outgrow handles without outgrowing Yawn
@yawn/core has no scene, mesh, material, camera, or
built-in shader. It owns a shared arena and a render-graph
runtime. Handles are one replaceable frontend that builds on those
two primitives.
import { YawnCore } from "https://yawn.heaust.org/pkg/core.js";
const core = new YawnCore(canvas, {
arenaBytes: 64 * 1024 * 1024,
});
await core.ready;
const particles = await core.createRows({
name: "particles",
rows: 100_000,
stride: 16,
format: "f32",
});
From here, your frontend supplies WGSL, resources, pipelines, and a pass DAG. Core compiles that description into an up-front loadout, aliases compatible transient textures, and records render work.
Craft an API for your problem
A custom handle can be as small as an ID plus getters and setters into shared rows. Keep domain policy in your code and send only structural changes to core.
class Particle {
constructor(
readonly id: number,
readonly positions: SharedRows,
) {}
set x(value: number) {
this.positions.row(this.id)[0] = value;
}
}
Deployment checklist
-
1
Serve over HTTPS
WebGPU and cross-origin isolation require a secure browser context outside local development.
-
2
Keep the isolation headers
Send
Cross-Origin-Opener-Policy: same-originandCross-Origin-Embedder-Policy: require-corp. -
3
Allow Yawn’s CDN
The imported module keeps every worker and WASM request on
yawn.heaust.org. If you use a Content Security Policy, allow that origin andblob:workers.