关于 threejs-errors-rendering
面向 Three.js 开发者的渲染故障排查技能,按常见症状整理检查步骤与修复方法,涵盖画面、颜色、阴影和 WebGL 问题。
主要功能
- 01黑屏与不可见对象排查
- 02颜色空间与色调映射检查
- 03Z-Fighting 与阴影问题修复
- 04WebGL 上下文丢失恢复
- 05缓冲区与材质更新检查
适用场景
- 01定位场景无画面的原因
- 02修复物体不可见或颜色异常
- 03处理阴影瑕疵与表面闪烁
- 04排查 WebGL 丢失或更新问题
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ impertio-studio/ three.js-claude-skill-package --skill threejs-errors-rendering安装后在 Claude Code 中输入/threejs-errors-rendering确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
.gitkeepreferences/anti-patterns.mdexamples.mdmethods.md详细介绍
threejs-errors-rendering
调试工作流检查清单
当 Three.js 场景无法正确渲染时,务必按以下顺序检查:
- 打开浏览器控制台——检查是否有 WebGL 错误或 Three.js 警告
- 确认渲染器尺寸非零(
renderer.getSize(new THREE.Vector2())) - 确认画布位于 DOM 中且可见(不是
display: none) - 检查是否调用了
renderer.render(scene, camera)(在循环中或至少调用一次) - 确认摄像机正对场景(位置、目标、近裁剪面/远裁剪面)
- 确认场景中至少有一个光源,供受光材质使用
- 检查材质的
side、visible、opacity和transparent属性 - 检查对象的
visible、layers和frustumCulled属性 - 检查渲染器和纹理的色彩空间设置
症状 1:黑屏(什么都看不见)
原因 A:渲染器尺寸为零
画布的尺寸为 0x0。这通常是因为在容器加入 DOM 之前调用了 setSize(),或者容器没有 CSS 尺寸。
修复:
// 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));
原因 B:摄像机位于对象内部或背对对象
摄像机位于 (0, 0, 0),对象也位于 (0, 0, 0),因此摄像机处于网格内部。或者摄像机指向了错误的方向。
修复:
camera.position.set(0, 2, 5); // ALWAYS move camera away from origin
camera.lookAt(0, 0, 0);
原因 C:近/远裁剪面将对象排除在外
距离近于 near 或远于 far 的对象会被裁剪。默认的 PerspectiveCamera 近裁剪面为 0.1,远裁剪面为 2000。
修复:
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 precision
原因 D:场景中没有光源
MeshStandardMaterial、MeshPhongMaterial 和 MeshLambertMaterial 需要光源。没有光源时,它们会渲染为黑色。MeshBasicMaterial 则不需要光源。
修复:
scene.add(new THREE.AmbientLight(0xffffff, 0.5));
scene.add(new THREE.DirectionalLight(0xffffff, 1));
原因 E:从未调用 render()
动画循环未启动,或者缺少 renderer.render(scene, camera)。
修复:
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate(); // ALWAYS call the function to start the loop
原因 F:场景或摄像机引用错误
向 render() 传入了空场景或未初始化的摄像机。
诊断: 在渲染调用之前记录 scene.children.length 和 camera.type。
症状 2:对象不可见
原因 A:材质侧面设置错误
默认情况下会剔除背面(FrontSide)。如果摄像机看到的是平面或薄几何体的背面,该对象就不可见。
修复:
const material = new THREE.MeshStandardMaterial({
side: THREE.DoubleSide // ALWAYS use for planes, leaves, thin objects
});
原因 B:设置了 opacity,但未设置 transparent
未设置 transparent: true 时,设置 opacity: 0.5 不会生效。
修复:
const material = new THREE.MeshStandardMaterial({
opacity: 0.5,
transparent: true // ALWAYS pair with opacity < 1
});
原因 C:对象位于不同图层
摄像机和对象必须至少共享一个图层。默认情况下,两者都位于图层 0。如果将对象移到其他图层,摄像机也必须启用该图层。
修复:
object.layers.set(1);
camera.layers.enable(1); // camera must see layer 1
原因 D:继承的 visible 为 false
父对象的 visible = false 会使其所有后代对象都不可见。请检查完整的父级链。
诊断:
let node = object;
while (node) {
if (!node.visible) console.log('Hidden ancestor:', node.name || node.type);
node = node.parent;
}
原因 E:frustumCulled 设置不正确
如果包围球有误(例如,手动修改顶点后未调用 computeBoundingSphere()),即使对象可见,也可能会被剔除。
修复:
geometry.computeBoundingSphere(); // ALWAYS call after modifying positions
// Or disable frustum culling for objects that must always render:
mesh.frustumCulled = false;
原因 F:对象位置错误或缩放为 0
对象的位置离摄像机很远,或者设置了 scale.set(0, 0, 0)。
诊断: 记录 object.position、object.scale、object.matrixWorld。
症状 3:颜色错误
原因 A:色彩空间不匹配
这是 Three.js r160+ 中最常见的颜色错误。
规则:
- 颜色/漫反射/自发光纹理:务必设置
texture.colorSpace = THREE.SRGBColorSpace - 数据纹理(法线、粗糙度、金属度、AO、位移):务必保持为
THREE.LinearSRGBColorSpace - 渲染器输出:
renderer.outputColorSpace = THREE.SRGBColorSpace(r160+ 中的默认值)
色彩空间错误的症状:
- 颜色发灰:数据纹理被错误地设置为
SRGBColorSpace(伽马校正两次) - 颜色过饱和:颜色纹理保留为
LinearSRGBColorSpace(未应用伽马校正)
修复:
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 data
原因 B:未配置色调映射
未使用色调映射时,HDR 值会被截断,导致颜色平淡或不正确。
修复:
renderer.toneMapping = THREE.ACESFilmicToneMapping; // or AgXToneMapping
renderer.toneMappingExposure = 1.0;
原因 C:构造后设置材质颜色被忽略
material.color.set() 可以正常工作,但在构造后设置 material.color = new THREE.Color() 也可以正常工作。常见错误是直接将十六进制数字设为颜色:material.color = 0xff0000 不起作用。
修复方法:
material.color.set(0xff0000); // Correct
material.color = new THREE.Color(0xff0000); // Correct
// material.color = 0xff0000; // WRONG -- silently fails
症状 4:Z 冲突(表面闪烁)
当两个表面在几乎相同的深度重叠时,会发生 Z 冲突,导致深度缓冲区在两者之间交替。
修复方法 A:多边形偏移
const material = new THREE.MeshStandardMaterial({
polygonOffset: true,
polygonOffsetFactor: -1,
polygonOffsetUnits: -1
});
修复方法 B:对数深度缓冲区
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 artifacts
修复方法 C:位置偏移
稍微移动一个表面:
decalMesh.position.z += 0.01; // small offset to prevent overlap
修复方法 D:减小远近比
// ALWAYS keep the near plane as large as possible
camera.near = 1; // not 0.001
camera.far = 1000; // not 1000000
camera.updateProjectionMatrix();
症状 5:阴影伪影
有关完整的阴影配置,请参阅 threejs-impl-shadows 技能。常见的快速修复方法:
- 完全没有阴影:
renderer.shadowMap.enabled = true、light.castShadow = true、mesh.castShadow = true、ground.receiveShadow = true - 阴影痤疮(条纹): 增大
light.shadow.bias(例如-0.005) - 彼得潘效应(阴影脱离):
light.shadow.bias过大,应减小 - 阴影分辨率低: 增大
light.shadow.mapSize.set(2048, 2048)
症状 6:WebGL 上下文丢失
浏览器在内存压力过大或 GPU 重置时会回收 WebGL 上下文。
恢复模式
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);
绝不要忽略上下文丢失——画布会永久变黑。务必添加这两个事件监听器。
症状 7:缺少 needsUpdate
BufferAttribute
修改顶点数据后,GPU 缓冲区会过时:
positions.array[0] = newX;
positions.needsUpdate = true; // ALWAYS set after modifying attribute data
材质
更改结构属性(添加或移除贴图、更改 defines)后:
material.map = newTexture;
material.needsUpdate = true; // triggers shader recompilation
// NEVER set needsUpdate = true every frame -- causes constant recompilation
纹理
修改纹理图像数据后:
texture.image = newImage;
texture.needsUpdate = true; // triggers GPU re-upload
InstancedMesh
调用 setMatrixAt() 或 setColorAt() 后:
mesh.instanceMatrix.needsUpdate = true; // ALWAYS after setMatrixAt
mesh.instanceColor.needsUpdate = true; // ALWAYS after setColorAt
症状 8:需要调用 updateProjectionMatrix
更改以下属性后,务必调用 camera.updateProjectionMatrix():
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);
});
症状 9:透明对象渲染问题
透明对象的渲染顺序不正确(从后到前的排序失败)。
修复方法 A:renderOrder
// Force specific rendering order
backgroundMesh.renderOrder = 0;
transparentMesh.renderOrder = 1;
frontMesh.renderOrder = 2;
修复方法 B:depthWrite
const material = new THREE.MeshStandardMaterial({
transparent: true,
opacity: 0.5,
depthWrite: false // prevents transparent objects from writing to depth buffer
});
修复方法 C:使用 alphaTest 代替透明度
对于具有硬边缘 Alpha 的纹理(树叶、栅栏):
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
});
常见控制台警告
| 警告 | 原因 | 修复方法 |
|---|---|---|
THREE.WebGLRenderer: Texture is not power of two | 使用重复包裹模式的 NPOT 纹理 | 使用二次幂纹理或 ClampToEdgeWrapping |
THREE.WebGLProgram: shader error | GLSL 编译失败 | 检查自定义着色器代码是否存在语法错误 |
THREE.PropertyBinding: Can not bind to... | 动画目标缺少属性 | 确保骨架/变形目标与动画剪辑匹配 |
THREE.BufferGeometry: .addAttribute() removed | 使用了已弃用的 API | 改用 setAttribute() |
GL_INVALID_OPERATION: Feedback loop | 从同时用作渲染目标的纹理读取 | 使用独立纹理进行读写 |
参考链接
- references/methods.md -- 与修复相关的 API 签名
- references/examples.md -- 完整的修复模式
- references/anti-patterns.md -- 不该采用的做法