threejs-scene-setup
Stand up a three.js scene: import maps and the three/addons path, the Scene/PerspectiveCamera/WebGLRenderer trio, the setAnimationLoop render loop, responsive resize, and OrbitControls. Use when starting or debugging a three.js app — when the user mentions three.js, THREE.Scene, WebGLRenderer, Persp
By gamedev-skills · 2,162 installs
npx skills add gamedev-skills/awesome-gamedev-agent-skills --skill threejs-scene-setup
Source repository · Upstream listing
three.js Scene Setup
Create the foundation of a three.js app: module loading, the
scene/camera/renderer trio, the render loop, responsive resizing, and camera
controls. Patterns target r186 . Read the installed three version before
changing an existing project because examples and addons move across releases.
When to use
Use when bootstrapping a three.js scene, fixing a blank/black canvas, making the
canvas responsive, setting up the animation loop, or adding OrbitControls .
Use when package.json depends on three and code does import as THREE from
'three' .
When not to use: loading .gltf / .glb models or skinned animation →
threejs gltf loading . Materials, lights, shadows, environment maps →
threejs materials lighting . 2D rendering → pixijs rendering .
Core workflow
1. Load three.js as an ES module with an import map. Since r137 the bare
specifier 'three' must be resolved by an import map (in HTML) or a bundler;
map 'three/addons/' too. Addons (controls, loaders) live under
three/addons/... .
2. Create the trio. A Scene (root of the graph), a PerspectiveCamera(fov,
aspect, near, far) moved back from the origin, and a WebGLRenderer whose
domElement is in the DOM. Set size and pixelRatio .
3. Add a mesh. new Mesh(geometry, material) and scene.add(mesh) . With a
lit material you also need a light (see threejs materials lighting ).
4. Drive a render loop with renderer.setAnimationLoop(fn) . It's the modern,
WebXR /WebGPU safe replacement for hand rolled requestAnimationFrame . Use a
Timer for delta time ( Clock is deprecated since r183).
5. Handle resize so the camera aspect and renderer match the canvas; update
camera.aspect , call updateProjectionMatrix() , and renderer.setSize(...) .
6. Add OrbitControls for orbit/pan/zoom while developing. Confirm something
actually renders (a lit cube, the controls responding) before assuming success.
Patterns
1. HTML import map + module entry (no bundler)
With a bundler (Vite/webpack), skip the import map and just
npm i three ; the same import statements resolve.
2. Scene + camera + renderer
3. The render loop (setAnimationLoop + Timer)
In r186, visibility reset samples performance.now() . A supplied animation frame
timestamp can precede that reset and produce a negative delta. Using update()
keeps the clock source consistent; the lower clamp also protects a fixed step
accumulator. Feed the bounded dt into simulation, not raw getDelta() .
The 0.1 second upper bound deliberately drops excess time after a stall; tune it
to the game's catch up policy rather than using it for real world deadlines.
4. Responsive resize
5. OrbitControls (orbit / pan / zoom)
6. Opting into WebGPURenderer (only when you need it)
WebGLRenderer stays the default choice for compatibility. Switch to
WebGPURenderer when the project needs TSL node materials or compute shaders; it
falls back to a WebGL 2 backend where WebGPU is unavailable.
Scenes, cameras, built in Mesh Material s, and most addons carry over; custom
GLSL ( ShaderMaterial , onBeforeCompile ) does not.
7. Resolution is a performance budget
A ratio of 2 draws four times as many pixels as 1 at the same CSS size. A cap
of 2 does nothing on a 2× display. Start games around 1.25–1.5, expose a resolution
setting, and profile the target device; these are tuning defaults, not an FPS guarantee.
Lower resolution helps fill rate bottlenecks, not CPU bound simulation.
For adaptive resolution, measure several seconds of frame time after assets and
shader warm up. Ignore hidden tab intervals and loading stalls. If sustained
GPU/fill rate pressure exceeds the budget, lower the ratio in 0.25 steps to a
chosen floor (for example 1), never above the device ratio or user setting.
Use a cooldown and a separate recovery threshold before raising it again to avoid
oscillation. Reapply the chosen ratio on display/DPR changes and resize render
targets or postprocessing buffers consistently with the renderer.
Pitfalls
Failed to resolve module specifier "three" → missing import map (or bundler
config). Map both "three" and "three/addons/" ; addon paths must end with / .
Black canvas, no errors → camera is at the origin (inside/behind the object),
or you used a lit material ( MeshStandardMaterial ) with no light. Move the camera
back; use MeshNormalMaterial / MeshBasicMaterial to verify geometry first.
Nothing animates → you never called renderer.render inside the loop, or you
call setAnimationLoop but render outside it.
Stretched / squashed view on resize → you resized the renderer but didn't
update camera.aspect + updateProjectionMatrix() .
Blurry or slow on HiDPI → balance sharpness against fill rate with a resolution
setting. Start around 1.25–1.5; a cap of 2 still renders 4× the pixels of 1×.
Simulation freezes after restoring a tab → r186 can mix a frame timestamp
with visibility reset time. Use timer.update() and clamp simulation delta to
[0, 0.1] as above so a negative delta cannot drain the fixed step accumulator.
OrbitControls feel dead → with enableDamping = true you must call
controls.update() every frame.
Old tutorials use <script src="three.min.js" or examples/js/... →
examples/js was removed in r148 and build/three.min.js in r161; use
type="module" + import maps.
.render() called before the backend is initialized (WebGPURenderer) → call
await renderer.init() before any direct render() / compute() , or render only
from setAnimationLoop , which initializes first.
Material "ShaderMaterial" is not compatible , or an onBeforeCompile patch is
ignored (WebGPURenderer) → it renders node materials only; port the effect to
TSL, or stay on WebGLRenderer .
References
Primary sources: [r186 Timer implementation](https://github.com/mrdoob/three.js/blob/r186/src/core/Timer.js)
and [r186 responsive rendering manual](https://github.com/mrdoob/three.js/blob/r186/manual/pages/responsive.html).
For coordinate conventions, the scene graph ( Group , parent/child transforms,
Object3D add/remove), OrthographicCamera for 2.5D, and disposing of
geometries/materials/textures to avoid leaks, read references/scene graph.md .
Related skills
threejs materials lighting — give surfaces a lit look (lights, shadows, PBR).
threejs gltf loading — load 3D models and play their animations.
pixijs rendering — 2D rendering in the browser.
fps shooter — a 3D genre template that composes three.js skills.