| name | threejs-scenes |
| version | 3.0.0 |
| description | Build production-quality three.js WebGL scenes with vanilla three.js (no R3F). Use this skill whenever the user mentions three.js, WebGL, 3D scenes in the browser, isometric games, voxel worlds, infinite/endless scrollable 3D, procedural geometry, instancing/BatchedMesh, billboards, particle systems, shader effects, glitch effects, bloom, lens flares, god rays, depth of field, chromatic aberration, stereoscopy, anaglyph, film grain, HUD transitions, camera controls (orbit/follow/pan-zoom), touch-friendly 3D controls, three.js performance optimization, draw-call reduction, LOD, post-processing chains, EffectComposer, GLSL ShaderMaterial, or LLM-driven programmatic generation of three.js content. Also trigger when the user wants to build a 3D web experience, mounts a `canvas` for three.js, asks "how do I make this run smooth", needs frame-rate capping, or wants to generate 3D assets from text prompts via Gemini / ai-sdk function tools.
|
three.js Scenes ⚘ Production Skill
Vanilla three.js (WebGL2) skill covering scene composition, programmatic generation,
camera handling, post-processing, voxel worlds, infinite scenes, and LLM-driven
codegen. No WebGPU, no R3F, no Drei — just three from the main entry point.
Reference Index
Load only what the current task needs. Don't read every file by default.
| File | Load when… |
|---|
references/core-principles.md | starting any new three.js work — read first |
references/threejs-docs-lookup.md | you need upstream three.js API/TSL detail — query the live docs (scripts/query-threejs-docs.js) instead of guessing |
references/fundamentals.md | the three.js mental model — primitives, scenegraph, materials, textures, lights, cameras, shadows, fog, render targets, custom BufferGeometry, physics (manual distilled, mapped to modules) |
references/project-architecture.md | scaffolding a project, organizing folders, wiring the frame loop |
references/instancing.md | rendering many copies of geometry; choosing InstancedMesh vs BatchedMesh |
references/textures-and-maps.md | working with diffuse/normal/ORM maps, UV offsets, procedural textures |
references/shaders.md | writing custom ShaderMaterial / RawShaderMaterial; GLSL composition |
references/lighting.md | scene lighting — IBL, sun, shadow tuning, hemisphere fill |
references/particles.md | particle systems — CPU instanced or GPGPU via GPUComputationRenderer |
references/performance.md | optimization, profiling, draw-call reduction, memory/GC discipline |
references/programmatic-generation.md | runtime geometry/texture/instance generation; seeded factories |
references/isometric-and-infinite-scenes.md | iso cameras, chunk managers, infinite worlds, origin rebasing |
references/cinematic-and-streaming.md | named lighting presets, volumetric beams, cinematic LUT, endless segment streams + on-rails camera, path tubes, triplanar + fullscreen-shader materials |
references/voxel-geometry.md | voxel chunks, greedy meshing, smooth voxels |
references/billboards.md | sprites, GLSL-billboarded InstancedMesh, cylindrical vs spherical |
references/camera-handling.md | camera setup, touch-first controls, pointer gestures, resize |
references/post-processing.md | EffectComposer chains — bloom, god rays, DOF+CA, glitch, grain, HUD beam, stereoscopy |
references/prompt-handling-flow.md | how to classify and respond to user prompts; codegen flow |
references/llm-function-definitions.md | Gemini / ai-sdk tool schemas for procedural scene generation |
references/code-style.md | @tuomashatakka/eslint-config + Semantic Nodes style guide rules |
references/anti-patterns.md | quick scan before committing — avoid these |
references/production-lessons.md | architecting beyond a single demo — context injection, disposal ownership, determinism, draw-call budgets, headless capture (distilled from a shipping project) |
references/geometry.md | programmatic mesh generation — shapes, extrusion, lathe, vertex modifiers, merge, layout |
references/materials.md | starting materials — PBR presets, toon, matcap (+ holographic shader) |
references/props-and-factories.md | prop factory — meshes with clips/lights/instancing, registry, composites |
references/animation-system.md | AnimationMixer/AnimationClip — controller + programmatic clip builders |
references/jsx-layer.md | declarative reactive JSX layer over the lib (frame-loop driven) |
references/library.md | importing the published lib from esm.sh via importmap; subpath entry points |
Minimal scene: core-principles.md + project-architecture.md + camera-handling.md.
Isometric game: add isometric-and-infinite-scenes.md + instancing.md + billboards.md.
Voxel world: add voxel-geometry.md + performance.md.
Stylized post-fx: add post-processing.md + shaders.md.
LLM-driven content: add prompt-handling-flow.md + llm-function-definitions.md.
Multi-module / shipping scene: always read production-lessons.md first.
Mesh / material / prop authoring: geometry.md + materials.md + props-and-factories.md.
Animation: animation-system.md. Declarative scenes: jsx-layer.md + library.md.
Querying the three.js docs
Never answer upstream three.js API questions from memory. three.js publishes
LLM-friendly docs — https://threejs.org/docs/llms.txt (guidelines + curated
index), https://threejs.org/docs/llms-full.txt (full TSL reference + page
catalog), and per-class markdown at https://threejs.org/docs/pages/<Name>.html.md.
Use the bundled CLI to fetch exactly what the task needs:
node scripts/query-threejs-docs.js page InstancedMesh
node scripts/query-threejs-docs.js section render pipeline
node scripts/query-threejs-docs.js search compute shader
See references/threejs-docs-lookup.md for the full command set and a
topic → relevant-pages map for every reference file in this skill.
Scripts Index
The scripts/ directory contains production-ready ES-module JavaScript implementations
of every reference example. See scripts/INDEX.md for the full table mapping each
script to its reference section and a one-line summary.
When asked to build something covered by a script, read the script directly rather
than re-deriving the code. Adapt the function signatures and options to the user's
specific request — the scripts are templates, not black boxes.
The 1.1 scripts (extruded-mesh.js, geometry-modifiers.js, material-presets.js,
prop-factory.js, prop-composite.js, animation-controller.js, gltf-prop.js,
jsx-scene.js) import the library (threejs-scenes)
rather than inlining helpers — see Using the Library below.
Runnable Templates
The templates/ directory holds complete, self-contained HTML scenes that run
by just opening the file in a browser — no build step, no dev server, no install.
Each loads three from the esm.sh CDN via an importmap (so it needs internet),
and inlines its own helpers (frame loop, pointer gesture, disposal) so there are
no local module imports to break under file://.
Use a template as the fastest path to a working scene: copy it, then replace the
clearly-marked content section (e.g. buildContent() in minimal-scene.html).
For multi-module projects, graduate to the scripts/ modules + an importmap.
| Template | Demonstrates |
|---|
templates/bootstrap.html | Library starter — createApp: unidirectional store→module→scene flow, fixed-step deterministic clock, seeded particles. Copy this first for library-backed apps. |
templates/minimal-scene.html | Starter scaffold — renderer + orbit camera + lit flat-shaded mesh + ResizeObserver + dispose + __SCENE_READY__. Copy and replace buildContent(). |
templates/instanced-field.html | InstancedMesh — ~5000 objects, 1 draw call, seeded mulberry32 placement, draw-call HUD. |
templates/shader-material.html | Custom GLSL ShaderMaterial — fresnel rim + scanlines + noise; uniforms mutated allocation-free. |
templates/post-processing.html | EffectComposer: RenderPass → UnrealBloom → grade(ShaderPass) → OutputPass; grade in linear HDR, tone-map once. |
templates/isometric.html | Orthographic true-iso camera + instanced height-grid terrain (value-noise) + pan/zoom. |
templates/particles.html | Particles v2 — createEmitter shapes/curves/bursts + 65k GPGPU field toggle (createGpuEmitter); cadence-independent determinism. |
templates/geometry.html | Library demo — extrusion + vertex modifiers + grid layout via the lib (@scenes). |
templates/materials.html | Library demo — PBR / toon / matcap presets. |
templates/props.html | Library demo — defineProp crystal (mesh + light + clips) + instanced forest + composite. |
templates/animation.html | Library demo — createAnimationController + programmatic clips; tap to crossfade. |
templates/jsx-scene.html | Library demo — reactive JSX/hyperscript layer; a signal drives a per-frame rotation. |
The first six templates are self-contained (inlined helpers, esm.sh three). The
five library-backed templates (geometry, materials, props, animation,
jsx-scene) instead import the published package via importmap (@scenes →
esm.sh threejs-scenes, pinned to this skill's version; three
from esm.sh) — see references/library.md. The skill does not bundle a copy of
the library.
These templates encode the patterns in references/production-lessons.md — read
that doc when adapting them into anything multi-module.
Using the Library
For multi-module scenes prefer importing the library over copy-pasting scripts.
Artifacts import the published npm package from esm.sh, version-pinned to this
skill. The ?external=three,… query keeps the library's three imports bare so
the page importmap resolves them — exactly one three.js instance. Import it
through an importmap so artifacts stay self-contained:
<script type="importmap">
{
"imports": {
"three": "https://esm.sh/three@0.184.0",
"three/addons/": "https://esm.sh/three@0.184.0/examples/jsm/",
"@tuomashatakka/canvas-loop-framecapper": "https://esm.sh/@tuomashatakka/canvas-loop-framecapper@1.0.0",
"@scenes": "https://esm.sh/threejs-scenes@3.0.0?external=three,@tuomashatakka/canvas-loop-framecapper",
"@scenes/jsx": "https://esm.sh/threejs-scenes@3.0.0/jsx?external=three,@tuomashatakka/canvas-loop-framecapper"
}
}
</script>
<script type="module">
import { bootstrapScene, createExtrudedMesh, createStandardMaterial } from '@scenes'
import { render, h, signal } from '@scenes/jsx'
</script>
The library is structured into three main, tree-shakeable public entry points:
threejs-scenes (WebGL core, scaffolding, cameras, animations, lighting, materials, geometry, instancing, loaders, and state management)
threejs-scenes/webgpu (Dedicated WebGPU post-processing and TSL effects)
threejs-scenes/jsx (Declarative, reactive JSX layer)
See references/library.md for integration details.
JSX layer (declarative, reactive)
@scenes/jsx is a higher-level layer over the factories — author a scene as
elements and render(tree, { canvas }) mounts it, driving reactivity from the
main frame loop: a function-valued prop is an accessor re-read every frame,
plain values apply once. Use hyperscript h(...) for a no-build artifact, or JSX
with jsxImportSource: "threejs-scenes/jsx". Intrinsics: <scene>,
<camera type=perspective|iso|follow>, <light type=spot|point|directional|ambient| hemisphere>, <group>, <mesh>, <primitive>, <prop src=…> (factory / .ts
module / model file), <instances>, <post>. See references/jsx-layer.md.
Embedding Scripts in Live Artifacts
Three.js scenes ship to the user as Claude artifacts of type text/html. The
artifact runs in a sandboxed iframe with no access to a build tool, so every script
in scripts/ is authored as a self-contained ES module that can be:
- Pasted as a function body into a
<script type="module"> block — simplest
for one-off demos.
- Imported via
esm.sh with importmaps — clean for multi-module artifacts.
Pattern 1 — Inline (smallest artifacts, single-file demos)
Read the relevant script file with view, then copy the exported functions into the
<script type="module"> block of the artifact. Replace the import * as THREE from 'three' line with a CDN import from esm.sh. Inline frame-loop.js first (it's the
backbone), then add the modules the scene needs.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<title>three.js scene</title>
<link rel="stylesheet" href="tokens.css" />
<style>
html, body { margin: 0; height: 100%; background: var(--color-neutral-950); overflow: hidden }
canvas { display: block; width: 100%; height: 100%; touch-action: none }
</style>
</head>
<body>
<main>
<canvas id="scene"></canvas>
</main>
<script type="module">
import * as THREE from 'https://esm.sh/three@0.184.0'
import { EffectComposer } from 'https://esm.sh/three@0.184.0/addons/postprocessing/EffectComposer.js'
import { RenderPass } from 'https://esm.sh/three@0.184.0/addons/postprocessing/RenderPass.js'
import { OutputPass } from 'https://esm.sh/three@0.184.0/addons/postprocessing/OutputPass.js'
const canvas = document.querySelector('#scene')
const renderer = createRenderer({ canvas })
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(50, canvas.clientWidth / canvas.clientHeight, 0.1, 200)
camera.position.set(4, 3, 6)
camera.lookAt(0, 0, 0)
startRenderLoop({ renderer, scene, camera })
</script>
</body>
</html>
Pattern 2 — Importmap (multi-module artifacts, cleaner separation)
For larger scenes, use an importmap so the import paths inside each pasted script
need no rewriting. Three.js addons resolve through the same map.
<script type="importmap">
{
"imports": {
"three": "https://esm.sh/three@0.184.0",
"three/addons/": "https://esm.sh/three@0.184.0/addons/"
}
}
</script>
<script type="module">
import * as THREE from 'three'
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'
</script>
Pattern 3 — <script type="module"> from data: URLs (rare, only when isolation matters)
Only use when an artifact mixes multiple isolated demos in one HTML page. Convert
each script to a data:text/javascript;base64, URL and import it. Most artifacts
should NOT need this.
Embedding Checklist
Before shipping any artifact:
Prompt Handling Flow
Every user request goes through classification + scoped action. See
references/prompt-handling-flow.md for the full taxonomy.
Quick decision tree:
- Is this a one-off question or a scene to build? Question → answer inline.
Scene → continue.
- Read
core-principles.md if not already loaded this session.
- Classify the request (geometry / shader / camera / effect-pass / instancing /
particle / lighting / scene-composition / optimization / debug).
- Load the matching reference file(s) from the table above.
- Reuse scripts — check
scripts/INDEX.md before writing anything from scratch.
- Generate via LLM tools if the task is procedural and config-driven (palette,
placement, voxel level layout, shader recipe). See
references/llm-function-definitions.md.
- Emit code as a complete, working module with a
// perf: annotation.
- Wire into artifact following the embedding patterns above.
Style Guarantees
Every artifact and code module produced under this skill must:
- Follow
@tuomashatakka/eslint-config (no semis, single quotes, two-space indent, no any).
- Apply Semantic Nodes style to all non-3D UI (HTML5 landmarks, design-token CSS,
tag selectors over class selectors, no Tailwind / Shadcn / Radix / CSS Modules).
- Use
pointer* events exclusively — never mouse* or touch*.
- Disable native gestures over the canvas with
touch-action: none.
- Cap pixel ratio at 2.
- Annotate non-trivial outputs with expected draw-call class and memory footprint.
When Things Go Wrong
- Black canvas → camera position likely inside geometry, or scene has no lights AND material isn't
MeshBasicMaterial. Add a HemisphereLight for fast diagnosis.
- Shader compile error → wrap GLSL in
/* glsl */ template literals; check uniform names match exactly between JS and shader.
- Frame stalls on first render → call
renderer.compile(scene, camera) before first render().
- GC spikes mid-scene → audit the render loop for
new Vector3(), closures capturing fresh objects, or Array.from() calls.
- Touch drag breaks when finger leaves canvas → missing
el.setPointerCapture(e.pointerId) in pointerdown.
- Sharp aliasing on mobile →
antialias: true is often ignored on iOS; use FXAA pass instead.
- Z-fighting on co-planar surfaces → tighten the camera frustum (
near / far) or use logarithmic depth buffer.