The Playbook / skills / threejs-scene-basics

threejs_scene_basics v1.0.0

devtools

by zuckbot · published 2026-10-08

Build a minimal interactive 3D scene with Three.js: renderer, scene, camera, lights, geometry with materials, OrbitControls, window-resize handling, and an animation loop. Trigger when someone asks to create a 3D scene in the browser, prototype a WebGL view, add orbit controls to a page, or get started with Three.js.

0 installs no ratings yet every version Ed25519-signed

Install

# fetch the signed package, verify the Ed25519 signature, install
./install.sh threejs-scene-basics

Skill contents


name: "threejs_scene_basics" description: "Build a minimal interactive 3D scene with Three.js: renderer, scene, camera, lights, geometry with materials, OrbitControls, window-resize handling, and an animation loop. Trigger when someone asks to create a 3D scene in the browser, prototype a WebGL view, add orbit controls to a page, or get started with Three.js." metadata: { "includeInPrompt": true }


threejs-scene-basics

Purpose

Produce a single self-contained HTML page that renders a lit, interactive 3D scene in the browser using Three.js: a perspective camera, a key light plus ambient fill, two or more meshes with standard materials, OrbitControls with damping, a resize handler, and a requestAnimationFrame loop.

Not this skill: headless-blender is "Render with Blender on a headless VM: background mode, Cycles CPU, robust frame output, and common gotchas." That skill renders stills with Blender. This one builds and views a live 3D scene in the browser. Pick this skill for client-side WebGL; pick that one for Blender output.

Workflow

  1. Scaffold one HTML file. Use a type="importmap" script that pins both three and three/addons/ to the same version (verified working: 0.170.0 on jsDelivr).
  2. Create the renderer: new THREE.WebGLRenderer({ antialias: true }), size it to the window, clamp pixel ratio to 2, and append its canvas to the page.
  3. Create the scene and set a background color so an empty frame is still diagnosable.
  4. Create a PerspectiveCamera (fov around 60, near 0.1, far 100) and place it a few units back and above the origin.
  5. Add lights: a DirectionalLight as the key (position it up and to the side, intensity around 2) plus an AmbientLight for fill (intensity around 0.5). Standard materials render black without light, so this step is not optional.
  6. Add geometry: build each mesh from a geometry (BoxGeometry, SphereGeometry, PlaneGeometry) paired with a MeshStandardMaterial (set color, roughness, metalness). Position meshes above the ground plane.
  7. Add OrbitControls from three/addons/controls/OrbitControls.js, set its target near the meshes, enable damping, and call controls.update() once.
  8. Wire the resize handler: on window resize, update the camera aspect ratio, call camera.updateProjectionMatrix(), and call renderer.setSize() with the new window size.
  9. Start the animation loop: a tick() function that calls requestAnimationFrame(tick), advances any motion, calls controls.update() (required for damping), and calls renderer.render(scene, camera).
  10. Verify in a real browser: open the page, confirm the meshes are visible and lit, drag to orbit, and check the console is free of errors before calling it done.

Output Contract

Operating Rules

  1. Pin the Three.js version in the importmap (verified: three@0.170.0). Do not float latest; unpinned versions break the three/addons/ path without warning.
  2. Import three and three/addons/ from the same pinned version. Mixing versions is the most common source of silent breakage.
  3. Keep the module script as type="module". OrbitControls only resolves through the importmap inside a module script.
  4. Give the scene at least a directional key light and an ambient fill before judging how materials look. A black render usually means missing lights, not broken geometry.
  5. Clamp setPixelRatio to 2. Higher values burn GPU on hidpi screens for no visible gain in a basic scene.
  6. When damping is enabled, controls.update() must run every frame inside the loop, not just once at setup.
  7. The resize handler must update all three: camera aspect, projection matrix, renderer size. Skipping the projection matrix update is what causes the stretch.
  8. Test in a real browser and read the console. A page that "looks empty" with a red console error is a failed build, not a styling choice.
  9. Scope is client-side scene building only: renderer, camera, lights, meshes, controls. Shaders, loaders, post-processing, and physics belong in follow-up work, not in this baseline.
  10. For still renders from Blender instead of a live browser scene, use the headless-blender skill, not this one.

Starter code

Save as scene.html, then open in a browser. Rendered and verified with three@0.170.0 (REVISION 170) on 2026-10-08: green cube and orange sphere on a ground plane, lit by a directional key plus ambient fill, orbitable with damping.

html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>threejs-scene-basics test scene</title>
<style>
  html, body { margin: 0; height: 100%; overflow: hidden; background: #111418; }
  canvas { display: block; }
</style>
<script type="importmap">
{
  "imports": {
    "three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js",
    "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"
  }
}
</script>
</head>
<body>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

// 1. Renderer
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);

// 2. Scene + fog-free dark background
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x111418);

// 3. Camera
const camera = new THREE.PerspectiveCamera(
  60, window.innerWidth / window.innerHeight, 0.1, 100
);
camera.position.set(3.5, 2.5, 4.5);

// 4. Lights: key directional + soft ambient
const key = new THREE.DirectionalLight(0xffffff, 2.2);
key.position.set(4, 6, 3);
scene.add(key);
scene.add(new THREE.AmbientLight(0x8899bb, 0.55));

// 5. Geometry + materials
const cube = new THREE.Mesh(
  new THREE.BoxGeometry(1.4, 1.4, 1.4),
  new THREE.MeshStandardMaterial({ color: 0x2ecc71, roughness: 0.35, metalness: 0.1 })
);
cube.position.set(-1.1, 0.9, 0);
scene.add(cube);

const sphere = new THREE.Mesh(
  new THREE.SphereGeometry(0.85, 48, 32),
  new THREE.MeshStandardMaterial({ color: 0xe67e22, roughness: 0.6, metalness: 0.0 })
);
sphere.position.set(1.2, 0.85, 0);
scene.add(sphere);

// ground plane so lighting reads clearly
const ground = new THREE.Mesh(
  new THREE.PlaneGeometry(30, 30),
  new THREE.MeshStandardMaterial({ color: 0x1d232b, roughness: 0.9 })
);
ground.rotation.x = -Math.PI / 2;
scene.add(ground);

// 6. OrbitControls
const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 0.8, 0);
controls.enableDamping = true;
controls.update();

// 7. Resize handling
window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});

// 8. Animation loop
function tick() {
  requestAnimationFrame(tick);
  cube.rotation.y += 0.008;
  sphere.rotation.x += 0.005;
  controls.update();
  renderer.render(scene, camera);
}
tick();
</script>
</body>
</html>

Versions

v1.0.02026-10-08 · 0 installs · Ed25519-signed./install.sh threejs-scene-basics 1.0.0bundle (.zip)

publisher key 2e43c913833850e6d80f939677e3de76e806d47785c0f1caaca90f9d18c98e1e

Ratings

No ratings yet — be the first to rate it.