Install
$ agentstack add skill-impertio-studio-three-js-claude-skill-package-threejs-errors-performance ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
threejs-errors-performance
Performance Diagnosis Flowchart
Scene is slow / low FPS
│
├─ Check renderer.info.render.calls
│ ├─ > 200 draw calls ──────────────── Go to: Draw Call Optimization
│ └─ 2M triangles ────────────────── Go to: Geometry Optimization (LOD, merge)
│ └─ 500 |
| Triangles | 3M |
| Textures (GPU) | 200 |
| Shader programs | 50 |
| Frame time | 33ms |
---
## Memory Management: Disposal Rules
### What MUST Be Disposed
Three.js allocates GPU resources that are NOT automatically garbage collected by JavaScript. ALWAYS dispose these manually:
| Object Type | Method | GPU Resource Released |
|-------------|--------|---------------------|
| `BufferGeometry` | `geometry.dispose()` | Vertex/index buffers |
| `Material` (all types) | `material.dispose()` | Shader programs, uniforms |
| `Texture` (all types) | `texture.dispose()` | GPU texture memory |
| `WebGLRenderTarget` | `renderTarget.dispose()` | Framebuffer + textures |
| `WebGLRenderer` | `renderer.dispose()` | Entire WebGL context |
| `PMREMGenerator` | `pmremGenerator.dispose()` | Prefiltered env maps |
| Controls (all types) | `controls.dispose()` | DOM event listeners |
### Complete Material Disposal
```javascript
function disposeMaterial(material) {
const textureProps = [
'map', 'lightMap', 'bumpMap', 'normalMap', 'specularMap',
'envMap', 'alphaMap', 'aoMap', 'displacementMap',
'emissiveMap', 'gradientMap', 'metalnessMap', 'roughnessMap',
'clearcoatMap', 'clearcoatNormalMap', 'clearcoatRoughnessMap',
'transmissionMap', 'thicknessMap', 'sheenColorMap', 'sheenRoughnessMap'
];
for (const prop of textureProps) {
if (material[prop]) material[prop].dispose();
}
material.dispose();
}
Full Scene Disposal
function disposeScene(scene) {
scene.traverse((object) => {
if (object.geometry) {
object.geometry.dispose();
}
if (object.material) {
if (Array.isArray(object.material)) {
object.material.forEach(disposeMaterial);
} else {
disposeMaterial(object.material);
}
}
});
scene.clear();
}
When to Dispose vs. When to Reuse
ALWAYS dispose when:
- Removing objects permanently from the scene
- Switching between completely different scenes
- Unloading loaded models (GLTF, FBX, OBJ)
- Component unmount (React, Vue, Angular)
NEVER dispose when:
- Temporarily hiding objects (use
visible = falseinstead) - Objects will be re-added to the scene later
- Multiple meshes share the same geometry/material (dispose ONLY when ALL users are done)
Draw Call Optimization
InstancedMesh: Render Thousands in One Call
ALWAYS use InstancedMesh when rendering more than ~100 copies of the same geometry+material combination.
import * as THREE from 'three';
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const count = 5000;
const mesh = new THREE.InstancedMesh(geometry, material, count);
const dummy = new THREE.Object3D();
for (let i = 0; i 10,000 | `InstancedMesh` with spatial subdivision or `BatchedMesh` (r160+) |
### BatchedMesh (r160+): Multiple Geometries in One Call
`BatchedMesh` extends instancing to support different geometries and materials in a single draw call. Use for heterogeneous repeated objects.
### Geometry Merging: Static Objects
For static objects that NEVER move independently, merge them into a single geometry:
```javascript
import { mergeGeometries } from 'three/addons/utils/BufferGeometryUtils.js';
const geometries = meshArray.map((m) => {
const geo = m.geometry.clone();
geo.applyMatrix4(m.matrixWorld);
return geo;
});
const merged = mergeGeometries(geometries, false);
const mergedMesh = new THREE.Mesh(merged, sharedMaterial);
// Dispose originals after merge
meshArray.forEach((m) => {
m.geometry.dispose();
scene.remove(m);
});
Rule: NEVER merge geometries that need independent transforms, materials, or raycasting targets. Merging makes individual object interaction impossible.
LOD (Level of Detail)
import * as THREE from 'three';
const lod = new THREE.LOD();
lod.addLevel(highDetailMesh, 0); // visible 0-50 units
lod.addLevel(mediumDetailMesh, 50); // visible 50-200 units
lod.addLevel(lowDetailMesh, 200); // visible 200+ units
scene.add(lod);
// ALWAYS call in animation loop for distance-based switching
lod.update(camera);
Rule: ALWAYS provide at least 3 LOD levels for objects visible across a wide distance range. Triangle counts MUST decrease by at least 50% between each level.
Texture Optimization
| Technique | Impact | When to Use | |-----------|--------|-------------| | Resize textures | High | ALWAYS use the smallest resolution that looks acceptable | | Power-of-two dimensions | Medium | Required for mipmaps; ALWAYS use (256, 512, 1024, 2048) | | Compressed formats (KTX2/Basis) | High | ALWAYS for production; 4-6x smaller GPU footprint | | Texture atlases | High | Combine multiple small textures into one to reduce draw calls | | texture.dispose() on swap | Critical | ALWAYS dispose old texture before assigning new one | | generateMipmaps: false | Low | Use for UI textures or textures that NEVER need filtering at distance |
KTX2 Compressed Textures
import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
const ktx2Loader = new KTX2Loader()
.setTranscoderPath('three/addons/libs/basis/')
.detectSupport(renderer);
ktx2Loader.load('texture.ktx2', (texture) => {
material.map = texture;
material.needsUpdate = true;
});
Frustum Culling
Frustum culling is enabled by default (object.frustumCulled = true). The renderer skips objects outside the camera view.
NEVER disable frustum culling globally. Only set frustumCulled = false on specific objects that MUST render regardless of camera (skyboxes, large particle systems, full-screen post-processing quads).
Rule: For InstancedMesh, frustum culling operates on the entire instance group as one bounding sphere. If instances are spread across a large area, split them into spatial groups for effective culling.
Object Pooling
NEVER create and destroy objects every frame. Use object pools for frequently spawned/despawned objects:
class MeshPool {
constructor(geometry, material, poolSize) {
this.pool = [];
for (let i = 0; i = this.pool.length) return null;
const mesh = this.pool[this.activeIndex++];
mesh.visible = true;
return mesh;
}
release(mesh) {
mesh.visible = false;
const idx = this.pool.indexOf(mesh);
if (idx !== -1 && idx < this.activeIndex) {
[this.pool[idx], this.pool[this.activeIndex - 1]] =
[this.pool[this.activeIndex - 1], this.pool[idx]];
this.activeIndex--;
}
}
}
CPU-Side Optimization
Static Object Matrix Optimization
For objects that NEVER move after initial placement:
object.matrixAutoUpdate = false;
object.updateMatrix(); // compute once
This prevents the renderer from recalculating the local matrix every frame for static objects.
Avoid Allocations in the Render Loop
// WRONG: creates new Vector3 every frame — GC pressure
function animate() {
const pos = new THREE.Vector3(1, 2, 3); // NEVER allocate in loop
mesh.position.copy(pos);
}
// CORRECT: reuse pre-allocated objects
const _tempVec = new THREE.Vector3();
function animate() {
_tempVec.set(1, 2, 3);
mesh.position.copy(_tempVec);
}
Rule: ALWAYS declare temporary math objects (Vector3, Matrix4, Quaternion, Color, Box3) outside the animation loop. Prefix with _ to indicate they are reusable scratch variables.
Shader and Material Optimization
| Action | Impact | |--------|--------| | Use MeshStandardMaterial instead of MeshPhysicalMaterial | Fewer shader instructions unless you need clearcoat/transmission/sheen | | Minimize unique material count | Fewer shader compilations; ALWAYS share materials across identical meshes | | Set material.precision = 'mediump' on mobile | Faster fragment shading on mobile GPUs | | Avoid onBeforeCompile unless necessary | Each unique modification creates a new shader variant |
Chrome DevTools Profiling
- Performance tab: Record a few seconds, look for long "GPU" tasks and JS frame duration
- Memory tab: Take heap snapshots before and after scene changes to find unreleased objects
renderer.infologging: Add a periodicconsole.log(renderer.info.memory)to detect leaks- WebGL Inspector (browser extension): Inspect draw calls, textures, and shader programs
Critical Warnings
NEVER create BufferGeometry, Material, or Texture objects inside the render/animation loop. This causes memory to grow without bound.
NEVER call renderer.render() after renderer.dispose(). The WebGL context is destroyed.
NEVER dispose shared geometry/material/texture while other meshes still reference it. Track reference counts or dispose only when ALL consumers are removed.
ALWAYS dispose old textures before replacing: if (material.map) material.map.dispose(); material.map = newTexture;
ALWAYS call controls.dispose() when removing OrbitControls or other control instances. Failing to do so leaks DOM event listeners.
ALWAYS set instanceMatrix.needsUpdate = true after calling setMatrixAt() on an InstancedMesh. Without this, instances render at the origin.
Reference Links
- [references/methods.md](references/methods.md) -- Disposal and profiling API signatures
- [references/examples.md](references/examples.md) -- Performance optimization code examples
- [references/anti-patterns.md](references/anti-patterns.md) -- Common performance mistakes and fixes
Official Sources
- Disposal guide: https://threejs.org/docs/#manual/en/introduction/How-to-dispose-of-objects
- WebGLRenderer.info: https://threejs.org/docs/#api/en/renderers/WebGLRenderer
- InstancedMesh: https://threejs.org/docs/#api/en/objects/InstancedMesh
- LOD: https://threejs.org/docs/#api/en/objects/LOD
- BufferGeometryUtils: https://threejs.org/docs/#examples/en/utils/BufferGeometryUtils
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: Impertio-Studio
- Source: Impertio-Studio/Three.js-Claude-Skill-Package
- License: MIT
- Homepage: https://threejs.org/
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.