graphics-troubleshooting
Debug and fix common issues in Three.js, React Three Fiber, and 3D asset pipelines. Use when encountering visual bugs, performance problems, loading failures, or unexpected behavior. Covers black screens, Z-fighting, memory leaks, shader errors, and physics glitches.
When & Why to Use This Skill
This Claude skill is a comprehensive diagnostic and troubleshooting toolkit for 3D web graphics. It provides expert-level solutions for identifying and fixing common issues in Three.js, React Three Fiber (R3F), and 3D asset pipelines. By focusing on symptoms like rendering failures, performance bottlenecks, and visual artifacts, it helps developers streamline their 3D development workflow and ensure high-performance, bug-free web experiences.
Use Cases
- Fixing 'Black Screen' or empty canvas issues by verifying camera positions, lighting setups, and renderer initialization in both React and Vanilla JS environments.
- Optimizing 3D performance by diagnosing low FPS, reducing draw calls, and identifying memory leaks through renderer info monitoring.
- Resolving visual glitches such as Z-fighting, flickering faces, inverted normals, and incorrect color space or tone mapping configurations.
- Troubleshooting complex asset loading failures, including CORS errors, Draco/KTX2 decoder setup, and GLTF validation issues.
- Debugging physics-based interactions in @react-three/rapier, such as objects falling through floors, jittery movement, or collider mismatches.
| name | graphics-troubleshooting |
|---|---|
| description | Debug and fix common issues in Three.js, React Three Fiber, and 3D asset pipelines. Use when encountering visual bugs, performance problems, loading failures, or unexpected behavior. Covers black screens, Z-fighting, memory leaks, shader errors, and physics glitches. |
Graphics Troubleshooting
Diagnose and fix 3D web graphics issues fast. Organized by symptom (what you see) for rapid diagnosis.
Library Versions (2026)
- Three.js: r171+
- React Three Fiber: v9.5+
- @react-three/drei: v9.116+
- @react-three/rapier: v2+
Quick Diagnosis
Nothing Renders (Black/Empty Screen)
| Check | Command/Action |
|---|---|
| Camera position | Is camera inside object? Move to [0, 0, 5] |
| Camera target | Is camera looking at scene? Check lookAt |
| Object scale | Is object too small/large? Check scale isn't 0 |
| Lights | Are there any lights? Add <ambientLight /> |
| Material | Using MeshStandardMaterial without lights? |
| Render loop | Is animate() being called? |
| Canvas size | Is container height 0? Check CSS |
| WebGPU support | Browser support? Check console for errors |
R3F Minimum Visible Scene:
<Canvas>
<ambientLight intensity={0.5} />
<directionalLight position={[5, 5, 5]} />
<mesh position={[0, 0, 0]}>
<boxGeometry />
<meshStandardMaterial color="red" />
</mesh>
</Canvas>
Vanilla Three.js Minimum Scene:
import * as THREE from 'three/webgpu'
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.position.z = 5
const renderer = new THREE.WebGPURenderer({ antialias: true })
await renderer.init()
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
// Add light - required for Standard materials
scene.add(new THREE.AmbientLight(0xffffff, 0.5))
scene.add(new THREE.DirectionalLight(0xffffff, 1))
// Add visible object
const mesh = new THREE.Mesh(
new THREE.BoxGeometry(),
new THREE.MeshStandardMaterial({ color: 'red' })
)
scene.add(mesh)
function animate() {
renderer.render(scene, camera)
}
renderer.setAnimationLoop(animate)
Object Renders But Looks Wrong
| Symptom | Cause | Fix |
|---|---|---|
| All black | No lights | Add ambient + directional light |
| Flickering faces | Z-fighting | Increase camera near, offset overlapping geometry |
| Inside-out | Inverted normals | material.side = THREE.DoubleSide or fix in Blender |
| Pixelated edges | Low DPR | renderer.setPixelRatio(Math.min(devicePixelRatio, 2)) |
| Washed out colors | No tone mapping | renderer.toneMapping = THREE.ACESFilmicToneMapping |
| Colors look wrong | Color space | renderer.outputColorSpace = THREE.SRGBColorSpace |
| Texture blurry | Filtering | texture.minFilter = THREE.LinearMipmapLinearFilter |
| Texture stretched | Wrong UVs | Check UV mapping in Blender, use texture.repeat |
Performance Issues
| Symptom | Diagnosis | Target | Fix |
|---|---|---|---|
| Low FPS (<30) | Check renderer.info.render.calls |
<100 draw calls | Use InstancedMesh, merge geometries |
| Stuttering/hitching | GC pauses | Avoid allocations | Object pooling, reuse Vector3s |
| Memory growing | Check renderer.info.memory |
Stable | Call .dispose() on removal |
| Slow initial load | Large assets | <5MB total | Compress with Meshopt, resize textures |
| Slow on mobile | Too many triangles | <100K | Simplify geometry, use LOD |
Quick Performance Check:
// Add to console or scene
console.log('Draw calls:', renderer.info.render.calls)
console.log('Triangles:', renderer.info.render.triangles)
console.log('Geometries:', renderer.info.memory.geometries)
console.log('Textures:', renderer.info.memory.textures)
See references/performance.md for detailed profiling.
Loading Failures
| Error | Cause | Fix |
|---|---|---|
| 404 Not Found | Wrong path | Check file exists, use absolute path from public |
| CORS error | Cross-origin | Serve from same origin or configure CORS headers |
| "Unexpected token" | Wrong file format | Ensure file is valid GLTF/GLB |
| Draco decode error | Missing decoder | Set dracoLoader.setDecoderPath('/draco/') |
| KTX2 decode error | Missing transcoder | Set ktx2Loader.setTranscoderPath('/basis/') |
| "Invalid glTF" | Corrupted file | Validate at https://gltf.report |
| Model invisible | Wrong scale | Check scale (Blender default: 1 unit = 1 meter) |
Loader Setup Pattern:
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js'
import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js'
import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js'
const loader = new GLTFLoader()
// Draco (for Draco-compressed models)
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/1.5.6/')
loader.setDRACOLoader(dracoLoader)
// Meshopt (for Meshopt-compressed models)
loader.setMeshoptDecoder(MeshoptDecoder)
// KTX2 (for Basis Universal textures)
const ktx2Loader = new KTX2Loader()
ktx2Loader.setTranscoderPath('https://cdn.jsdelivr.net/npm/three@0.171.0/examples/jsm/libs/basis/')
ktx2Loader.detectSupport(renderer)
loader.setKTX2Loader(ktx2Loader)
See references/loading.md for more patterns.
Physics Issues (@react-three/rapier)
| Symptom | Cause | Fix |
|---|---|---|
| Objects fall through floor | Missing collider | Add <RigidBody type="fixed"> to ground |
| Objects stuck in air | Wrong body type | Use type="dynamic" for moving objects |
| Jittery movement | High velocity + low mass | Increase mass or reduce forces |
| Tunneling (fast objects pass through) | CCD disabled | Enable ccd={true} on fast bodies |
| Collider wrong shape | Auto-collider mismatch | Use explicit <CuboidCollider> etc. |
| Physics not updating | Wrong loop | Check updateLoop prop on <Physics> |
Minimum Physics Setup:
import { Physics, RigidBody } from '@react-three/rapier'
<Physics gravity={[0, -9.81, 0]} debug>
{/* Ground - fixed, doesn't move */}
<RigidBody type="fixed">
<mesh position={[0, -1, 0]}>
<boxGeometry args={[10, 0.5, 10]} />
<meshStandardMaterial />
</mesh>
</RigidBody>
{/* Falling object - dynamic */}
<RigidBody type="dynamic">
<mesh>
<sphereGeometry />
<meshStandardMaterial />
</mesh>
</RigidBody>
</Physics>
Shader/Material Errors
| Error | Cause | Fix |
|---|---|---|
| "X is not defined" in shader | Missing uniform/varying | Declare all variables |
| Black material | Shader compile error | Check console for GLSL/WGSL errors |
| Material not updating | Missing needsUpdate |
Set material.needsUpdate = true |
| TSL error | Wrong import | Use import { x } from 'three/tsl' |
| NodeMaterial black | Missing colorNode | Set material.colorNode = ... |
TSL Debug Pattern:
import { color, uniform } from 'three/tsl'
// Start simple, add complexity
const material = new THREE.MeshBasicNodeMaterial()
material.colorNode = color(0xff0000) // Should be red
// If this works, add your custom logic incrementally
Related Skills
| When you need... | Use skill |
|---|---|
| Optimize assets before loading | asset-pipeline-3d |
| Build scenes with vanilla Three.js | threejs |
| Build scenes with React | react-three-fiber |
Reference Files
- references/visual-bugs.md - Z-fighting, clipping, shadow artifacts, transparency issues
- references/performance.md - Profiling, optimization, memory management
- references/loading.md - Asset loading, decoder setup, CORS, caching