Imported from akillness/jeo-skills (
.agent-skills/threejs-fundamentals/SKILL.md). Install upstream withnpx skills add akillness/jeo-skills --skill threejs-fundamentals. Copyright stays with the author (MIT).
Three.js Fundamentals
Use this skill for the rendering foundation of a general Three.js web experience.
For a playable game's system, lifecycle, or release work, use web-game-development;
for a narrowly scoped rendering concern, route to the matching threejs-* skill.
When to use this skill
- Set up or repair a scene, camera, renderer, canvas ownership, or animation loop
- Choose perspective versus orthographic projection or correct world/local transforms
- Make rendering responsive, color-managed, and safe on high-DPI displays
- Diagnose blank scenes, clipped content, wrong camera framing, or leaking GPU resources
Instructions
Step 1: Establish the project contract
- Read the installed
threeversion and existing renderer/canvas ownership before changing imports or initialization. - Keep one owner for the render loop and resize listener. Framework wrappers such as React Three Fiber own those lifecycle concerns; do not add a competing raw loop.
- Confirm the render backend before using backend-specific APIs. This skill defaults to
WebGLRenderer; verify WebGPU patterns against the installed Three.js revision.
Step 2: Build the smallest visible scene
Use an explicit scene, camera, renderer, one lit mesh, and a render loop before layering in loaders, shaders, or post-processing.
import * as THREE from "three";
const width = window.innerWidth;
const height = window.innerHeight;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(50, width / height, 0.1, 100);
camera.position.set(0, 1, 4);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(width, height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.outputColorSpace = THREE.SRGBColorSpace;
const mesh = new THREE.Mesh(
new THREE.BoxGeometry(),
new THREE.MeshStandardMaterial({ color: 0x4f8cff }),
);
scene.add(new THREE.HemisphereLight(0xffffff, 0x334455, 2));
scene.add(mesh);
function render() {
renderer.render(scene, camera);
requestAnimationFrame(render);
}
render();
Use a Group to give a feature one transform root. Change position, quaternion, or
scale intentionally; local coordinates compose through parents, while world-space
queries require updateWorldMatrix when the scene has not rendered yet.
Step 3: Handle resize and disposal explicitly
function resize(width, height) {
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize(width, height, false);
}
function disposeObject(root) {
root.traverse((object) => {
object.geometry?.dispose();
const materials = Array.isArray(object.material)
? object.material
: [object.material];
for (const material of materials) material?.dispose();
});
}
Dispose textures and render targets owned by the feature as well. Do not dispose shared resources until every consumer is gone.
Step 4: Verify observable rendering behavior
- Confirm a visible mesh and stable camera framing at the intended canvas size.
- Resize through narrow, wide, and high-DPI cases; the drawing buffer must not stretch.
- Check the browser console for WebGL warnings and inspect
renderer.infoonly as a diagnostic, not as a test oracle. - Run the repository's build, typecheck, and relevant visual/browser test when present.
Decision guide
| Need | Use |
|---|---|
| Scene graph, camera, renderer, transforms, lifecycle | This skill |
| Custom vertices, instancing, or BufferGeometry | threejs-geometry |
| PBR properties or mesh surface appearance | threejs-materials |
| Lights, shadows, or image-based lighting | threejs-lighting |
| Maps, UVs, HDR backgrounds, or render targets | threejs-textures |
| Model/asset loading and progress | threejs-loaders |
| AnimationMixer, clips, bones, or morphs | threejs-animation |
| Raycasting, controls, picking, or input | threejs-interaction |
| GLSL or material shader extension | threejs-shaders |
| EffectComposer screen-space passes | threejs-postprocessing |
Examples
Perspective product view
Use a PerspectiveCamera for a physically familiar object view. Set a deliberately
small near plane only when needed; an unnecessarily tiny near value wastes depth
precision and causes z-fighting.
Isometric-like board view
Use an OrthographicCamera when scale must remain constant across depth. Recalculate
left/right/top/bottom from aspect ratio on resize, then call updateProjectionMatrix().
Best practices
- Keep one
requestAnimationFrameowner per canvas. - Clamp pixel ratio; unbounded device pixel ratio is a silent GPU-cost multiplier.
- Use
MeshStandardMaterialplus intentional lighting for normal PBR work instead of compensating for an unlit scene with arbitrary color values. - Keep camera clipping planes as tight as the scene permits.
- Pair every feature-owned GPU allocation with a teardown path.