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.
./install.sh threejs-scene-basicsname: "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 }
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.
type="importmap" script that pins both three and three/addons/ to the same version (verified working: 0.170.0 on jsDelivr).new THREE.WebGLRenderer({ antialias: true }), size it to the window, clamp pixel ratio to 2, and append its canvas to the page.PerspectiveCamera (fov around 60, near 0.1, far 100) and place it a few units back and above the origin.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.BoxGeometry, SphereGeometry, PlaneGeometry) paired with a MeshStandardMaterial (set color, roughness, metalness). Position meshes above the ground plane.OrbitControls from three/addons/controls/OrbitControls.js, set its target near the meshes, enable damping, and call controls.update() once.window resize, update the camera aspect ratio, call camera.updateProjectionMatrix(), and call renderer.setSize() with the new window size.tick() function that calls requestAnimationFrame(tick), advances any motion, calls controls.update() (required for damping), and calls renderer.render(scene, camera).scene.html, with no build step and no dependencies beyond the pinned CDN importmap.three@0.170.0). Do not float latest; unpinned versions break the three/addons/ path without warning.three and three/addons/ from the same pinned version. Mixing versions is the most common source of silent breakage.type="module". OrbitControls only resolves through the importmap inside a module script.setPixelRatio to 2. Higher values burn GPU on hidpi screens for no visible gain in a basic scene.controls.update() must run every frame inside the loop, not just once at setup.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>
publisher key 2e43c913833850e6d80f939677e3de76e806d47785c0f1caaca90f9d18c98e1e
No ratings yet — be the first to rate it.