threejs-animation
Keyframe, skeletal and morph-target animation, procedural motion and clip blending, on WebGL or with WebGPU and TSL. Use when…
Measuring and optimizing three.js speed: render statistics, memory counters, fewer draw calls via merging, instancing, batching and LOD, fill rate, shadows, CPU work per frame, shader stalls and GPU compute for particles, WebGL or WebGPU with TSL. Use when a scene is slow, janks
$ npx -y skills add cloudai-x/threejs-skills --skill threejs-performance --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/threejs-performanceContext preview
The summary Claude sees to decide when to auto-load this skill.
Measuring and optimizing three.js speed: render statistics, memory counters, fewer draw calls via merging, instancing, batching and LOD, fill rate, shadows, CPU work per frame, shader stalls and GPU compute for particles, WebGL or WebGPU with TSL. Use when a scene is slow, janks
name: threejs-performance description: "Measuring and optimizing three.js speed: render statistics, memory counters, fewer draw calls via merging, instancing, batching and LOD, fill rate, shadows, CPU work per frame, shader stalls and GPU compute for particles, WebGL or WebGPU with TSL. Use when a scene is slow, janks or exceeds memory."
Targets three.js r186. Examples use WebGLRenderer; the WebGPU and TSL section uses WebGPURenderer.
import * as THREE from "three";
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
scene.add(new THREE.HemisphereLight(0xffffff, 0x445566, 1));
const sun = new THREE.DirectionalLight(0xffffff, 2);
sun.position.set(3, 6, 4);
scene.add(sun);
const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(0, 2, 8);
const mesh = new THREE.Mesh(new THREE.SphereGeometry(1, 32, 16), new THREE.MeshStandardMaterial({ color: 0x4488ff }));
scene.add(mesh);
function animate() {
mesh.rotation.y += 0.01;
renderer.render(scene, camera);
// the counters reset at the start of every render() call, so these cover the frame just drawn
console.log(`${renderer.info.render.calls} calls, ${renderer.info.render.triangles} triangles, ${renderer.info.memory.textures} textures`);
}
renderer.setAnimationLoop(animate);Optimize what the numbers name, not what a hunch suspects. WebGLRenderer's `renderer.info` object is read after `render()`:
function animate() {
renderer.render(scene, camera);
console.table({
calls: renderer.info.render.calls, // draw calls submitted by this render
triangles: renderer.info.render.triangles,
lines: renderer.info.render.lines,
points: renderer.info.render.points,
geometries: renderer.info.memory.geometries, // geometry buffers live on the GPU
textures: renderer.info.memory.textures,
programs: renderer.info.programs.length, // compiled shader programs in the cache
});
}
renderer.setAnimationLoop(animate);Details worth knowing when reading these:
When a frame really has several `render()` calls, accumulate over the whole frame yourself:
renderer.info.autoReset = false; // render() stops clearing the counters
function animate() {
renderer.render(scene, camera);
console.log(renderer.info.render.calls); // every render of this frame, summed
renderer.info.reset(); // once per frame, after the last render
}
renderer.setAnimationLoop(animate);`Timer` gives the frame interval; average it over a second of frames, single frames jitter too much to compare. The interval never drops below one display refresh (16.7 ms at 60 Hz), so it shows dropped frames but not headroom; time the CPU side of the frame with `performance.now()` around the work:
const timer = new THREE.Timer();
timer.connect(document); // pauses while the tab is hidden
let interval = 0;
let cpu = 0;
let frames = 0;
function animate() {
timer.update();
interval += timer.getDelta(); // seconds since the previous frame, never less than one display refresh
const start = performance.now();
renderer.render(scene, camera);
cpu += performance.now() - start; // CPU time spent in render(); the GPU executes the commands later
frames++;
if (frames === 60) {
console.log(`${((interval / frames) * 1000).toFixed(2)} ms per frame, ${(cpu / frames).toFixed(2)} ms CPU in render()`);
interval = 0;
cpu = 0;
frames = 0;
}
}
renderer.setAnimationLoop(animate);For anything deeper than frame time, use the browser profiler — the Chrome DevTools Performance panel shows long main-thread tasks, scripting versus rendering time, and GPU work on its GPU track. The `stats-gl` package shows a live per-frame statistics overlay when you want the numbers always visible.
Each visible mesh queues one draw call, or one per geometry group when the material is an array, and two settings multiply that: a `transparent` material with `side: THREE.DoubleSide` draws each mesh twice, back faces then front faces (`forceSinglePass: true` makes it one), and while any MeshPhysicalMaterial with `transmission` above 0 is in view, every opaque object is drawn a second time into the transmission buffer. Without those two, a thousand single-material meshes is a thousand draw calls; `info.render.calls` is the number to watch. The classic reducers:
**Merge static geometry.** Many meshes that never move and share one material become one mesh and one call:
import * as BufferGeometryUtils from "three/addons/utils/BufferGeometryUtils.js";
const rockGeometry = new THREE.DodecahedronGeometry(1, 0);
const transform = new THREE.Matrix4();
const parts = [];
for (let i = 0; i < 200; i++) {
const baked = rockGeometry.clone();
transform.setPosition((Math.random() - 0.5) * 40, 0, (Math.random() - 0.5) * 40);
baked.applyMatrix4(transform); // bake the placement into the vertices
parts.push(baked);
}
const merged = BufferGeoSkill files that give Claude Code current, verified knowledge of three.js, so the code it writes runs on the release you actually install. The skills target three.js r186 (three@0.186), React Three Fiber 9.8 with drei 10.7, and gltf-transform 4.5.
Keyframe, skeletal and morph-target animation, procedural motion and clip blending, on WebGL or with WebGPU and TSL. Use when…
Shrinking glTF assets for three.js with gltf-transform: inspect, validate and optimize commands, Draco, meshopt and quantization,…
Three.js scene setup: scenes and fog, cameras, the renderer and tone mapping, the animation loop, object hierarchies, transforms,…
Creating and shaping three.js geometry: built-in shapes, 3D text, vertex buffers and attributes, merging, instancing, batching,…
Picking with raycasting, camera controls, transform gizmos, drag, hover and box selection for three.js, identical under WebGL and…
Light types and physical units, shadow maps, HDR environment lighting, light probes, and lighting performance, on WebGL or with…