
Threejs Errors Rendering
- 19 installs
- 11 repo stars
- Updated July 8, 2026
- openaec-foundation/three.js-claude-skill-package
Helps with ai & agent building tasks.
About
threejs-errors-rendering is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- threejs-errors-rendering
- AI & Agent Building
- AI-coding skill
Threejs Errors Rendering by the numbers
- 19 all-time installs (skills.sh)
- +2 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #10,587 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/three.js-claude-skill-package --skill threejs-errors-renderingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 19 |
|---|---|
| repo stars | ★ 11 |
| Last updated | July 8, 2026 |
| Repository | openaec-foundation/three.js-claude-skill-package ↗ |
What it does
Helps with ai & agent building tasks.
Files
threejs-errors-rendering
Debugging Workflow Checklist
When a Three.js scene does not render correctly, ALWAYS follow this sequence:
1. Open the browser console -- check for WebGL errors or Three.js warnings 2. Verify the renderer has a non-zero size (renderer.getSize(new THREE.Vector2())) 3. Confirm the canvas is in the DOM and visible (not display: none) 4. Check that renderer.render(scene, camera) is called (in a loop or at least once) 5. Verify the camera is looking at the scene (position, target, near/far) 6. Confirm at least one light exists for lit materials 7. Check material side, visible, opacity, and transparent properties 8. Verify object visible, layers, and frustumCulled properties 9. Inspect color space settings on renderer and textures
---
Symptom 1: Black Screen (Nothing Visible)
Cause A: Renderer has zero size
The canvas has 0x0 dimensions. This happens when setSize() is called before the container is in the DOM or when the container has no CSS dimensions.
Fix:
// ALWAYS ensure the container is in the DOM and has dimensions before setSize
document.body.appendChild(renderer.domElement);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));Cause B: Camera is inside the object or facing away
The camera is at (0, 0, 0) and the object is also at (0, 0, 0), so the camera is inside the mesh. Or the camera is pointing in the wrong direction.
Fix:
camera.position.set(0, 2, 5); // ALWAYS move camera away from origin
camera.lookAt(0, 0, 0);Cause C: Near/far clipping planes exclude the object
Objects closer than near or farther than far are clipped. Default PerspectiveCamera near is 0.1, far is 2000.
Fix:
const camera = new THREE.PerspectiveCamera(75, aspect, 0.1, 1000);
// NEVER set near to 0 -- causes z-fighting and depth buffer issues
// ALWAYS keep far/near ratio below 100000 for stable depth precisionCause D: No light in the scene
MeshStandardMaterial, MeshPhongMaterial, and MeshLambertMaterial require lights. Without light, they render black. MeshBasicMaterial does NOT require lights.
Fix:
scene.add(new THREE.AmbientLight(0xffffff, 0.5));
scene.add(new THREE.DirectionalLight(0xffffff, 1));Cause E: render() is never called
The animation loop is not started, or renderer.render(scene, camera) is missing.
Fix:
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate(); // ALWAYS call the function to start the loopCause F: Scene or camera is wrong reference
Passing an empty scene or an uninitialized camera to render().
Diagnosis: Log scene.children.length and camera.type before the render call.
---
Symptom 2: Invisible Objects
Cause A: Wrong material side
Back faces are culled by default (FrontSide). If the camera sees the back of a plane or thin geometry, it is invisible.
Fix:
const material = new THREE.MeshStandardMaterial({
side: THREE.DoubleSide // ALWAYS use for planes, leaves, thin objects
});Cause B: opacity without transparent
Setting opacity: 0.5 without transparent: true has NO effect.
Fix:
const material = new THREE.MeshStandardMaterial({
opacity: 0.5,
transparent: true // ALWAYS pair with opacity < 1
});Cause C: Object on a different layer
The camera and the object MUST share at least one layer. By default both are on layer 0. If the object is moved to another layer, the camera must enable that layer too.
Fix:
object.layers.set(1);
camera.layers.enable(1); // camera must see layer 1Cause D: visible is false (inherited)
visible = false on a parent makes ALL descendants invisible. Check the full parent chain.
Diagnosis:
let node = object;
while (node) {
if (!node.visible) console.log('Hidden ancestor:', node.name || node.type);
node = node.parent;
}Cause E: frustumCulled incorrectly
If the bounding sphere is wrong (e.g., after manual vertex changes without computeBoundingSphere()), the object may be culled even when visible.
Fix:
geometry.computeBoundingSphere(); // ALWAYS call after modifying positions
// Or disable frustum culling for objects that must always render:
mesh.frustumCulled = false;Cause F: Object at wrong position or scale 0
Object is at a position far from the camera, or scale.set(0, 0, 0).
Diagnosis: Log object.position, object.scale, object.matrixWorld.
---
Symptom 3: Wrong Colors
Cause A: Color space mismatch
This is the MOST COMMON color error in Three.js r160+.
Rules:
- Color/diffuse/emissive textures: ALWAYS set
texture.colorSpace = THREE.SRGBColorSpace - Data textures (normal, roughness, metalness, AO, displacement): ALWAYS leave as
THREE.LinearSRGBColorSpace - Renderer output:
renderer.outputColorSpace = THREE.SRGBColorSpace(default in r160+)
Symptoms of wrong color space:
- Washed-out colors: data texture incorrectly set to
SRGBColorSpace(double gamma) - Over-saturated colors: color texture left in
LinearSRGBColorSpace(no gamma applied)
Fix:
const texture = await loader.loadAsync('diffuse.png');
texture.colorSpace = THREE.SRGBColorSpace; // for color textures
const normalMap = await loader.loadAsync('normal.png');
// NEVER set SRGBColorSpace on normal maps -- corrupts surface dataCause B: Tone mapping not configured
Without tone mapping, HDR values are clamped, producing flat or incorrect colors.
Fix:
renderer.toneMapping = THREE.ACESFilmicToneMapping; // or AgXToneMapping
renderer.toneMappingExposure = 1.0;Cause C: Material color set after construction ignored
material.color.set() works, but material.color = new THREE.Color() after construction also works. The common mistake is setting color as a hex number directly: material.color = 0xff0000 does NOT work.
Fix:
material.color.set(0xff0000); // Correct
material.color = new THREE.Color(0xff0000); // Correct
// material.color = 0xff0000; // WRONG -- silently fails---
Symptom 4: Z-Fighting (Flickering Surfaces)
Z-fighting occurs when two surfaces overlap at nearly the same depth, causing the depth buffer to alternate between them.
Fix A: Polygon offset
const material = new THREE.MeshStandardMaterial({
polygonOffset: true,
polygonOffsetFactor: -1,
polygonOffsetUnits: -1
});Fix B: Logarithmic depth buffer
const renderer = new THREE.WebGLRenderer({ logarithmicDepthBuffer: true });
// Trades performance for better depth precision across large near/far ranges
// NEVER use with EffectComposer post-processing -- causes artifactsFix C: Position offset
Move one surface slightly:
decalMesh.position.z += 0.01; // small offset to prevent overlapFix D: Reduce near/far ratio
// ALWAYS keep the near plane as large as possible
camera.near = 1; // not 0.001
camera.far = 1000; // not 1000000
camera.updateProjectionMatrix();---
Symptom 5: Shadow Artifacts
For complete shadow configuration, see the threejs-impl-shadows skill. Common quick fixes:
- No shadows at all:
renderer.shadowMap.enabled = true,light.castShadow = true,mesh.castShadow = true,ground.receiveShadow = true - Shadow acne (stripes): Increase
light.shadow.bias(e.g.,-0.005) - Peter panning (shadow detached):
light.shadow.biasis too large, reduce it - Low-res shadows: Increase
light.shadow.mapSize.set(2048, 2048)
---
Symptom 6: WebGL Context Lost
The browser reclaims the WebGL context under memory pressure or GPU reset.
Recovery pattern
renderer.domElement.addEventListener('webglcontextlost', (event) => {
event.preventDefault(); // ALWAYS prevent default to allow restoration
cancelAnimationFrame(animationId);
}, false);
renderer.domElement.addEventListener('webglcontextrestored', () => {
// Re-initialize materials, textures, render targets
initScene();
animate();
}, false);NEVER ignore context loss -- the canvas goes black permanently. ALWAYS add both event listeners.
---
Symptom 7: needsUpdate Missing
BufferAttribute
After modifying vertex data, the GPU buffer is stale:
positions.array[0] = newX;
positions.needsUpdate = true; // ALWAYS set after modifying attribute dataMaterial
After changing structural properties (adding/removing maps, changing defines):
material.map = newTexture;
material.needsUpdate = true; // triggers shader recompilation
// NEVER set needsUpdate = true every frame -- causes constant recompilationTexture
After modifying texture image data:
texture.image = newImage;
texture.needsUpdate = true; // triggers GPU re-uploadInstancedMesh
After setMatrixAt() or setColorAt():
mesh.instanceMatrix.needsUpdate = true; // ALWAYS after setMatrixAt
mesh.instanceColor.needsUpdate = true; // ALWAYS after setColorAt---
Symptom 8: updateProjectionMatrix Required
ALWAYS call camera.updateProjectionMatrix() after changing:
camera.fovcamera.aspectcamera.nearcamera.farcamera.zoomcamera.left/right/top/bottom(OrthographicCamera)
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix(); // NEVER forget this
renderer.setSize(window.innerWidth, window.innerHeight);
});---
Symptom 9: Transparent Object Rendering Issues
Transparent objects render in the wrong order (back-to-front sorting fails).
Fix A: renderOrder
// Force specific rendering order
backgroundMesh.renderOrder = 0;
transparentMesh.renderOrder = 1;
frontMesh.renderOrder = 2;Fix B: depthWrite
const material = new THREE.MeshStandardMaterial({
transparent: true,
opacity: 0.5,
depthWrite: false // prevents transparent objects from writing to depth buffer
});Fix C: alphaTest instead of transparency
For textures with hard alpha edges (foliage, fences):
const material = new THREE.MeshStandardMaterial({
map: leafTexture,
alphaTest: 0.5, // discards fragments below threshold
side: THREE.DoubleSide
// NEVER set transparent: true for hard-edge alpha -- use alphaTest instead
});---
Common Console Warnings
| Warning | Cause | Fix |
|---|---|---|
THREE.WebGLRenderer: Texture is not power of two | NPOT texture with repeat wrapping | Use power-of-two textures or ClampToEdgeWrapping |
THREE.WebGLProgram: shader error | GLSL compilation failure | Check custom shader code for syntax errors |
THREE.PropertyBinding: Can not bind to... | Animation targets missing property | Ensure skeleton/morph targets match the animation clip |
THREE.BufferGeometry: .addAttribute() removed | Using deprecated API | Use setAttribute() instead |
GL_INVALID_OPERATION: Feedback loop | Reading from a texture that is also a render target | Use separate textures for reading and writing |
---
Reference Links
- references/methods.md -- Fix-related API signatures
- references/examples.md -- Complete fix patterns
- references/anti-patterns.md -- What NOT to do
Official Sources
- https://threejs.org/docs/#api/en/renderers/WebGLRenderer
- https://threejs.org/docs/#api/en/cameras/PerspectiveCamera
- https://threejs.org/docs/#api/en/materials/Material
- https://threejs.org/docs/#api/en/core/Object3D
- https://threejs.org/docs/#api/en/core/BufferAttribute
threejs-errors-rendering — Anti-Patterns
Anti-Pattern 1: Setting near to 0 or extremely small values
// WRONG -- causes z-fighting across the entire scene
const camera = new THREE.PerspectiveCamera(75, aspect, 0.0001, 100000);Why it fails: The depth buffer has limited precision (typically 24 bits). A huge far/near ratio (100000 / 0.0001 = 1 billion) means almost all precision is consumed near the camera, leaving virtually none for distant objects. This causes z-fighting everywhere beyond a few meters.
Correct:
// ALWAYS keep near as large as possible and far as small as possible
const camera = new THREE.PerspectiveCamera(75, aspect, 0.1, 1000);
// far/near ratio of 10000 is acceptable; above 100000 causes visible artifacts---
Anti-Pattern 2: Setting SRGBColorSpace on normal/data maps
// WRONG -- corrupts normal direction data
normalMap.colorSpace = THREE.SRGBColorSpace;
roughnessMap.colorSpace = THREE.SRGBColorSpace;Why it fails: Normal maps encode direction vectors, not colors. Applying sRGB gamma correction distorts the vector values, producing incorrect lighting. Roughness and metalness maps encode linear data -- gamma correction changes their effective values.
Correct:
// Data textures: NEVER set SRGBColorSpace
// They default to LinearSRGBColorSpace, which is correct
const normalMap = textureLoader.load('normal.png');
// Leave colorSpace as default
// ONLY color textures get SRGBColorSpace:
const diffuseMap = textureLoader.load('diffuse.png');
diffuseMap.colorSpace = THREE.SRGBColorSpace;---
Anti-Pattern 3: Forgetting updateProjectionMatrix after camera changes
// WRONG -- camera projection is stale
camera.fov = 90;
camera.near = 1;
camera.far = 500;
// Rendering continues with the OLD projection matrixWhy it fails: The projection matrix is computed once and cached. Changing fov, near, far, aspect, or zoom does NOT automatically recompute it. The renderer uses the stale matrix, producing incorrect perspective or clipping.
Correct:
camera.fov = 90;
camera.near = 1;
camera.far = 500;
camera.updateProjectionMatrix(); // ALWAYS call after ANY camera property change---
Anti-Pattern 4: Setting material.needsUpdate = true every frame
// WRONG -- recompiles shader program 60 times per second
function animate() {
material.color.setHSL(time, 1, 0.5);
material.needsUpdate = true; // NEVER do this in the render loop
renderer.render(scene, camera);
requestAnimationFrame(animate);
}Why it fails: material.needsUpdate = true triggers a full shader program recompilation, which is expensive (1-10ms per material). Doing this every frame causes severe frame drops. Changing color, opacity, or uniform values does NOT require needsUpdate -- Three.js uploads these automatically.
Correct:
function animate() {
material.color.setHSL(time, 1, 0.5); // This works WITHOUT needsUpdate
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
// ONLY set needsUpdate when changing structural properties:
material.map = newTexture;
material.needsUpdate = true; // correct -- structural change---
Anti-Pattern 5: Using transparent: true for hard-edge alpha
// WRONG -- causes sorting issues and z-buffer artifacts
const material = new THREE.MeshStandardMaterial({
map: fenceTexture,
transparent: true,
side: THREE.DoubleSide
});Why it fails: transparent: true enables alpha blending, which requires correct back-to-front sorting. For hundreds of overlapping foliage or fence planes, Three.js cannot sort them correctly, producing visual glitches where background shows through in wrong order.
Correct:
// For hard-edge alpha (foliage, fences, cutouts), use alphaTest
const material = new THREE.MeshStandardMaterial({
map: fenceTexture,
alphaTest: 0.5, // discards fragments below threshold -- no sorting needed
side: THREE.DoubleSide
// NO transparent: true
});---
Anti-Pattern 6: Ignoring WebGL context loss
// WRONG -- no recovery handler
const renderer = new THREE.WebGLRenderer();
// If context is lost, the canvas goes permanently black with no recoveryWhy it fails: Mobile browsers, GPU driver crashes, and memory pressure can cause WebGL context loss at any time. Without a webglcontextlost event handler that calls event.preventDefault(), the context cannot be restored. Without a webglcontextrestored handler, the application never recovers.
Correct:
renderer.domElement.addEventListener('webglcontextlost', (event) => {
event.preventDefault(); // ALWAYS prevent default to enable restoration
cancelAnimationFrame(animationId);
}, false);
renderer.domElement.addEventListener('webglcontextrestored', () => {
initScene(); // re-create materials, textures, render targets
animate();
}, false);---
Anti-Pattern 7: Forgetting instanceMatrix.needsUpdate
// WRONG -- instances all render at origin
for (let i = 0; i < count; i++) {
dummy.position.set(Math.random() * 10, 0, Math.random() * 10);
dummy.updateMatrix();
mesh.setMatrixAt(i, dummy.matrix);
}
// Missing: mesh.instanceMatrix.needsUpdate = true
scene.add(mesh);Why it fails: setMatrixAt writes to a CPU-side buffer. The GPU buffer is NOT updated until instanceMatrix.needsUpdate = true is set. Without it, the GPU still has the initial zero-matrix data, placing all instances at the origin.
Correct:
for (let i = 0; i < count; i++) {
dummy.position.set(Math.random() * 10, 0, Math.random() * 10);
dummy.updateMatrix();
mesh.setMatrixAt(i, dummy.matrix);
}
mesh.instanceMatrix.needsUpdate = true; // ALWAYS set after setMatrixAt
scene.add(mesh);---
Anti-Pattern 8: Using logarithmicDepthBuffer with post-processing
// WRONG -- depth reads in post-processing shaders break
const renderer = new THREE.WebGLRenderer({ logarithmicDepthBuffer: true });
const composer = new EffectComposer(renderer);
// SSAO, DOF, and other depth-dependent effects produce artifactsWhy it fails: Logarithmic depth buffer encodes depth non-linearly. Post-processing passes that read the depth buffer (SSAO, depth-of-field, fog) expect linear depth values. The mismatch produces visual artifacts.
Correct:
// Choose ONE approach:
// Option A: logarithmic depth, no depth-dependent post-processing
const renderer = new THREE.WebGLRenderer({ logarithmicDepthBuffer: true });
// Option B: standard depth buffer with post-processing (preferred)
const renderer = new THREE.WebGLRenderer({ logarithmicDepthBuffer: false });
// Fix z-fighting by tightening near/far instead
camera.near = 1;
camera.far = 5000;
camera.updateProjectionMatrix();---
Anti-Pattern 9: Changing shadowMap.type after first render
// WRONG -- causes shader cache invalidation
renderer.render(scene, camera);
renderer.shadowMap.type = THREE.VSMShadowMap; // changed after first renderWhy it fails: Shadow map type is baked into compiled shader programs. Changing it after the first render forces ALL shadow-receiving materials to recompile their shaders, causing a significant frame drop.
Correct:
// ALWAYS set shadow map configuration BEFORE the first render
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
// Then start rendering
animate();threejs-errors-rendering — Examples
Example 1: Complete Black Screen Diagnosis
import * as THREE from 'three';
// Step 1: Create renderer with correct size
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); // ALWAYS add to DOM
// Step 2: Create scene with light
const scene = new THREE.Scene();
scene.add(new THREE.AmbientLight(0xffffff, 0.4));
const dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
dirLight.position.set(5, 10, 7);
scene.add(dirLight);
// Step 3: Camera positioned away from origin
const camera = new THREE.PerspectiveCamera(
75, window.innerWidth / window.innerHeight, 0.1, 1000
);
camera.position.set(0, 2, 5);
camera.lookAt(0, 0, 0);
// Step 4: Add visible object
const mesh = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshStandardMaterial({ color: 0x00ff00 })
);
scene.add(mesh);
// Step 5: Render loop
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();Example 2: Color Space Correct Setup
import * as THREE from 'three';
const renderer = new THREE.WebGLRenderer();
renderer.outputColorSpace = THREE.SRGBColorSpace; // default in r160+
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;
const textureLoader = new THREE.TextureLoader();
// Color texture: ALWAYS SRGBColorSpace
const diffuseMap = textureLoader.load('albedo.png');
diffuseMap.colorSpace = THREE.SRGBColorSpace;
// Normal map: NEVER SRGBColorSpace
const normalMap = textureLoader.load('normal.png');
// normalMap.colorSpace remains LinearSRGBColorSpace (default)
// Roughness map: NEVER SRGBColorSpace
const roughnessMap = textureLoader.load('roughness.png');
// roughnessMap.colorSpace remains LinearSRGBColorSpace (default)
const material = new THREE.MeshStandardMaterial({
map: diffuseMap,
normalMap: normalMap,
roughnessMap: roughnessMap,
roughness: 1.0,
metalness: 0.0
});Example 3: Z-Fighting Fix with Polygon Offset
import * as THREE from 'three';
// Ground plane
const ground = new THREE.Mesh(
new THREE.PlaneGeometry(10, 10),
new THREE.MeshStandardMaterial({ color: 0x808080 })
);
ground.rotation.x = -Math.PI / 2;
// Decal on ground -- uses polygon offset to avoid z-fighting
const decal = new THREE.Mesh(
new THREE.PlaneGeometry(2, 2),
new THREE.MeshStandardMaterial({
color: 0xff0000,
polygonOffset: true,
polygonOffsetFactor: -1,
polygonOffsetUnits: -1
})
);
decal.rotation.x = -Math.PI / 2;
decal.position.y = 0.001; // tiny offset as additional safety
scene.add(ground);
scene.add(decal);Example 4: Transparent Object Ordering
import * as THREE from 'three';
// Background opaque object
const wall = new THREE.Mesh(
new THREE.PlaneGeometry(5, 5),
new THREE.MeshStandardMaterial({ color: 0x444444 })
);
wall.position.z = -2;
wall.renderOrder = 0;
scene.add(wall);
// Transparent glass panel
const glass = new THREE.Mesh(
new THREE.PlaneGeometry(3, 3),
new THREE.MeshStandardMaterial({
color: 0x88ccff,
transparent: true,
opacity: 0.3,
depthWrite: false, // prevents depth conflicts with other transparent objects
side: THREE.DoubleSide
})
);
glass.renderOrder = 1;
scene.add(glass);
// Front transparent panel
const frontGlass = new THREE.Mesh(
new THREE.PlaneGeometry(2, 2),
new THREE.MeshStandardMaterial({
color: 0xff8888,
transparent: true,
opacity: 0.5,
depthWrite: false,
side: THREE.DoubleSide
})
);
frontGlass.position.z = 1;
frontGlass.renderOrder = 2;
scene.add(frontGlass);Example 5: WebGL Context Loss Recovery
import * as THREE from 'three';
let renderer, scene, camera, animationId;
function initScene() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(0, 2, 5);
scene.add(new THREE.AmbientLight(0xffffff, 0.5));
scene.add(new THREE.Mesh(
new THREE.BoxGeometry(),
new THREE.MeshStandardMaterial({ color: 0x00ff00 })
));
}
function init() {
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// ALWAYS add context loss handlers
renderer.domElement.addEventListener('webglcontextlost', (event) => {
event.preventDefault();
cancelAnimationFrame(animationId);
console.warn('WebGL context lost. Waiting for restoration...');
}, false);
renderer.domElement.addEventListener('webglcontextrestored', () => {
console.log('WebGL context restored. Re-initializing...');
initScene();
animate();
}, false);
initScene();
animate();
}
function animate() {
animationId = requestAnimationFrame(animate);
renderer.render(scene, camera);
}
init();Example 6: Resize Handler with updateProjectionMatrix
import * as THREE from 'three';
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix(); // NEVER forget this after changing camera properties
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
});Example 7: Foliage with alphaTest (Not Transparency)
import * as THREE from 'three';
const textureLoader = new THREE.TextureLoader();
const leafTexture = textureLoader.load('leaf.png');
leafTexture.colorSpace = THREE.SRGBColorSpace;
// Use alphaTest for hard-edge alpha, NOT transparent: true
const leafMaterial = new THREE.MeshStandardMaterial({
map: leafTexture,
alphaTest: 0.5,
side: THREE.DoubleSide
// transparent: true is NOT needed and would cause sorting issues
});
const leaf = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), leafMaterial);
scene.add(leaf);Example 8: Debugging Invisible Object
// Diagnostic function to find why an object is invisible
function diagnoseInvisible(object, camera) {
// Check visibility chain
let node = object;
while (node) {
if (!node.visible) {
console.error(`Hidden: ${node.name || node.type} has visible=false`);
return;
}
node = node.parent;
}
// Check layers
if (!camera.layers.test(object.layers)) {
console.error('Layer mismatch: camera and object share no layers');
return;
}
// Check material
if (object.material) {
if (!object.material.visible) {
console.error('Material.visible is false');
return;
}
if (object.material.opacity === 0 && object.material.transparent) {
console.error('Material is fully transparent (opacity=0)');
return;
}
}
// Check scale
const s = object.scale;
if (s.x === 0 || s.y === 0 || s.z === 0) {
console.error(`Scale is zero: (${s.x}, ${s.y}, ${s.z})`);
return;
}
// Check position relative to camera
const distance = camera.position.distanceTo(object.position);
if (distance < camera.near || distance > camera.far) {
console.error(`Object at distance ${distance}, camera near=${camera.near} far=${camera.far}`);
return;
}
console.log('No obvious issue found. Check geometry and frustum culling.');
}threejs-errors-rendering — Methods Reference
Renderer Diagnostics
// Check renderer size
renderer.getSize(target: Vector2): Vector2
// Check render info (draw calls, triangles, memory)
renderer.info.render.calls: number
renderer.info.render.triangles: number
renderer.info.memory.geometries: number
renderer.info.memory.textures: number
renderer.info.autoReset: boolean // set false to accumulate across frames
// Reset info counters
renderer.info.reset(): voidSize and Pixel Ratio
renderer.setSize(width: number, height: number, updateStyle?: boolean): void
// updateStyle default: true. Set false when canvas size is managed externally.
renderer.setPixelRatio(value: number): void
// ALWAYS cap: renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
renderer.getPixelRatio(): numberColor Space and Tone Mapping
renderer.outputColorSpace: string
// Default: THREE.SRGBColorSpace (r160+)
// Set to THREE.LinearSRGBColorSpace for post-processing pipelines
renderer.toneMapping: number
// THREE.NoToneMapping | THREE.LinearToneMapping | THREE.ReinhardToneMapping
// THREE.CineonToneMapping | THREE.ACESFilmicToneMapping
// THREE.AgXToneMapping | THREE.NeutralToneMapping
renderer.toneMappingExposure: number // Default: 1.0Texture Color Space
texture.colorSpace: string
// THREE.SRGBColorSpace — for diffuse, emissive, color textures
// THREE.LinearSRGBColorSpace — for normal, roughness, metalness, AO, displacement
// THREE.NoColorSpace — default (no conversion)
texture.needsUpdate: boolean // set true after changing image or propertiesCamera Projection
// PerspectiveCamera
camera.fov: number
camera.aspect: number
camera.near: number
camera.far: number
camera.zoom: number
camera.updateProjectionMatrix(): void
// ALWAYS call after changing fov, aspect, near, far, or zoom
// OrthographicCamera
camera.left: number
camera.right: number
camera.top: number
camera.bottom: number
camera.updateProjectionMatrix(): voidMaterial Visibility Properties
material.side: number
// THREE.FrontSide (default) | THREE.BackSide | THREE.DoubleSide
material.visible: boolean // Default: true
material.transparent: boolean // Default: false. MUST be true for opacity < 1
material.opacity: number // Default: 1.0. Requires transparent: true
material.alphaTest: number // Default: 0. Fragments below threshold discarded
material.depthWrite: boolean // Default: true. Set false for transparent objects
material.depthTest: boolean // Default: true
material.needsUpdate: boolean // Set true to recompile shader
material.polygonOffset: boolean // Default: false
material.polygonOffsetFactor: number // Default: 0
material.polygonOffsetUnits: number // Default: 0Object Visibility Properties
object.visible: boolean // Default: true. Inherited by descendants
object.frustumCulled: boolean // Default: true
object.renderOrder: number // Default: 0. Higher renders later
object.layers: Layers // 32-bit layer mask
layers.set(layer: number): void // Enable ONLY this layer
layers.enable(layer: number): void // Add a layer
layers.disable(layer: number): void // Remove a layer
layers.test(layers: Layers): boolean // Check overlapBufferAttribute Update
attribute.needsUpdate: boolean // Set true after modifying array data
attribute.usage: number // THREE.StaticDrawUsage | THREE.DynamicDrawUsage
// InstancedMesh specific
mesh.instanceMatrix: InstancedBufferAttribute
mesh.instanceColor: InstancedBufferAttribute | null
mesh.setMatrixAt(index: number, matrix: Matrix4): void
mesh.setColorAt(index: number, color: Color): void
// ALWAYS set instanceMatrix.needsUpdate = true after setMatrixAtGeometry Bounds
geometry.computeBoundingBox(): void
geometry.computeBoundingSphere(): void
// ALWAYS call after modifying position attribute data
// Required for correct frustum cullingWebGL Context Events
canvas.addEventListener('webglcontextlost', (event: WebGLContextEvent) => void)
canvas.addEventListener('webglcontextrestored', () => void)
// event.preventDefault() in contextlost handler allows restorationWebGLRenderer Constructor Options
new THREE.WebGLRenderer({
canvas?: HTMLCanvasElement,
antialias?: boolean, // Default: false
alpha?: boolean, // Default: false (transparent background)
logarithmicDepthBuffer?: boolean, // Default: false (helps z-fighting)
powerPreference?: string, // 'high-performance' | 'low-power' | 'default'
preserveDrawingBuffer?: boolean, // Default: false (needed for toDataURL)
stencil?: boolean, // Default: true
depth?: boolean // Default: true
})