常见问题与解决方案
在 Three.js 开发过程中,经常会遇到各种问题。本章收集了最常见的错误和解决方案,帮助你快速定位和解决问题。
场景渲染问题
场景显示黑屏
问题:场景加载后显示黑屏,没有任何内容。
可能原因和解决方案:
1. 没有光源
如果使用 MeshStandardMaterial 或 MeshPhongMaterial,场景中必须有光源:
// 添加环境光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
// 或添加方向光
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 10, 5);
scene.add(directionalLight);
// 或使用不受光照影响的材质
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
2. 相机位置不对
相机可能在物体内部或太远:
// 检查相机位置
console.log(camera.position);
// 设置合适的相机位置
camera.position.set(0, 0, 5);
// 让相机看向原点
camera.lookAt(0, 0, 0);
3. 物体太小或太远
物体可能太小看不见,或在相机视野之外:
// 确保物体大小合适
const geometry = new THREE.BoxGeometry(1, 1, 1);
// 检查物体位置
console.log(mesh.position);
// 调整物体大小
mesh.scale.set(2, 2, 2);
4. 没有调用渲染
忘记在动画循环中渲染场景:
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera); // 确保调用
}
animate();
5. Canvas 没有添加到页面
// 确保将 canvas 添加到 DOM
document.body.appendChild(renderer.domElement);
物体显示为黑色
问题:物体存在但显示为纯黑色。
解决方案:
// 1. 检查是否有光源
scene.add(new THREE.AmbientLight(0xffffff, 0.5));
// 2. 检查材质是否需要光照
// MeshBasicMaterial 不受光照影响
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
// 3. 检查物体是否在阴影中
// 移动物体或调整光源位置
// 4. 检查法线是否存在
geometry.computeVertexNormals();
物体从背面看是透明的
问题:物体的背面不可见,像单面纸一样。
解决方案:
// 设置材质为双面渲染
const material = new THREE.MeshStandardMaterial({
color: 0x00ff00,
side: THREE.DoubleSide // 渲染两面
});
// 或仅渲染背面
material.side = THREE.BackSide;
// 默认是正面
material.side = THREE.FrontSide;
几何体和材质问题
几何体变形或扭曲
问题:几何体显示不正确,出现奇怪的形状。
解决方案:
// 1. 检查几何体参数
const geometry = new THREE.SphereGeometry(1, 32, 32); // 确保分段数合理
// 2. 重新计算法线
geometry.computeVertexNormals();
// 3. 检查 UV 坐标
console.log(geometry.attributes.uv);
// 4. 计算包围盒
geometry.computeBoundingBox();
geometry.computeBoundingSphere();
材质颜色不对
问题:材质显示的颜色与设置的不一致。
解决方案:
// 1. 检查颜色格式
material.color.setHex(0xff0000); // 正确
material.color.setHex('#ff0000'); // 错误:应该是数字不是字符串
// 2. 检查是否有纹理覆盖
material.map = null; // 清除纹理
// 3. 检查光照影响
// 使用 MeshBasicMaterial 测试原始颜色
const testMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
// 4. 检查色调映射
renderer.toneMapping = THREE.NoToneMapping;
// 5. 检查颜色空间
renderer.outputColorSpace = THREE.SRGBColorSpace;
纹理不显示
问题:纹理加载了但不显示在物体上。
解决方案:
// 1. 检查纹理是否加载成功
const texture = textureLoader.load('texture.jpg',
() => console.log('纹理加载成功'),
undefined,
(error) => console.error('纹理加载失败:', error)
);
// 2. 检查 UV 坐标是否存在
console.log(geometry.attributes.uv);
// 3. 确保纹理赋给了正确的属性
material.map = texture;
material.needsUpdate = true;
// 4. 检查纹理颜色空间
texture.colorSpace = THREE.SRGBColorSpace;
// 5. 检查路径是否正确
// 确保纹理文件路径相对于 HTML 文件正确
纹理显示模糊
问题:纹理在高分辨率屏幕上显示模糊。
解决方案:
// 1. 设置设备像素比
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
// 2. 使用更高分辨率的纹理
// 3. 设置各向异性过滤
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
// 4. 检查纹理过滤设置
texture.minFilter = THREE.LinearMipmapLinearFilter;
texture.magFilter = THREE.LinearFilter;
光照和阴影问题
阴影不显示
问题:启用了阴影但场景中没有阴影。
解决方案:
// 1. 启用渲染器阴影
renderer.shadowMap.enabled = true;
// 2. 光源必须投射阴影
light.castShadow = true;
// 3. 物体必须投射阴影
mesh.castShadow = true;
// 4. 接收阴影的物体必须启用
ground.receiveShadow = true;
// 5. 检查阴影相机范围
light.shadow.camera.near = 0.5;
light.shadow.camera.far = 500;
light.shadow.camera.left = -100;
light.shadow.camera.right = 100;
light.shadow.camera.top = 100;
light.shadow.camera.bottom = -100;
// 6. 使用阴影助手调试
const helper = new THREE.CameraHelper(light.shadow.camera);
scene.add(helper);
阴影有条纹(阴影痤疮)
问题:阴影表面出现奇怪的条纹或斑点。
解决方案:
// 1. 调整阴影偏移
light.shadow.bias = -0.0001;
// 2. 增加阴影贴图分辨率
light.shadow.mapSize.width = 2048;
light.shadow.mapSize.height = 2048;
// 3. 使用更柔和的阴影类型
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
阴影被截断
问题:阴影只显示一部分,其余被截断。
解决方案:
// 扩大阴影相机范围
light.shadow.camera.left = -50;
light.shadow.camera.right = 50;
light.shadow.camera.top = 50;
light.shadow.camera.bottom = -50;
// 或使用阴影助手可视化范围
const helper = new THREE.DirectionalLightHelper(light, 5);
scene.add(helper);
性能问题
场景卡顿
问题:渲染帧率低,场景卡顿。
解决方案:
// 1. 检查渲染统计信息
console.log(renderer.info);
// 2. 减少几何体复杂度
const geometry = new THREE.SphereGeometry(1, 16, 16); // 减少分段
// 3. 使用实例化渲染
const mesh = new THREE.InstancedMesh(geometry, material, count);
// 4. 合并静态几何体
import { mergeGeometries } from 'three/addons/utils/BufferGeometryUtils.js';
const merged = mergeGeometries([geo1, geo2]);
// 5. 减少光源数量
// 尽量使用方向光和环境光
// 6. 降低渲染分辨率
renderer.setPixelRatio(1);
// 7. 禁用抗锯齿
// 创建渲染器时不启用 antialias
// 8. 使用 LOD
const lod = new THREE.LOD();
内存占用过高
问题:应用内存占用持续增长。
解决方案:
// 1. 及时释放不再使用的资源
geometry.dispose();
material.dispose();
texture.dispose();
// 2. 完整释放物体
function disposeObject(obj) {
obj.traverse((child) => {
if (child.geometry) child.geometry.dispose();
if (child.material) {
const materials = Array.isArray(child.material) ? child.material : [child.material];
materials.forEach(m => {
Object.values(m).forEach(v => {
if (v && typeof v.dispose === 'function') v.dispose();
});
m.dispose();
});
}
});
}
// 3. 清理事件监听器
window.removeEventListener('resize', onResize);
// 4. 限制纹理尺寸
// 使用适当大小的纹理
模型加载问题
模型加载失败
问题:模型无法加载,控制台报错。
解决方案:
// 1. 检查文件路径
loader.load(
'model.glb',
(gltf) => console.log('加载成功'),
undefined,
(error) => console.error('加载失败:', error)
);
// 2. 检查 CORS 问题
// 确保服务器正确设置 CORS 头
// 或使用本地服务器开发
// 3. 检查文件格式
// 确保使用支持的格式
// 4. 检查 MIME 类型
// 服务器可能需要配置正确的 MIME 类型
// .glb -> model/gltf-binary
// .gltf -> model/gltf+json
模型显示异常
问题:模型加载了但显示不正确。
解决方案:
// 1. 检查模型比例
const box = new THREE.Box3().setFromObject(model);
const size = box.getSize(new THREE.Vector3());
console.log('模型尺寸:', size);
// 调整比例
const maxDim = Math.max(size.x, size.y, size.z);
const scale = 2 / maxDim;
model.scale.setScalar(scale);
// 2. 检查模型朝向
// 某些软件导出的模型可能有不同的坐标系
model.rotation.x = -Math.PI / 2; // 旋转校正
// 3. 检查材质
model.traverse((child) => {
if (child.isMesh) {
console.log('材质:', child.material);
// 可能需要重新设置材质
child.material.side = THREE.DoubleSide;
}
});
// 4. 检查动画
console.log('动画:', gltf.animations);
着色器问题
着色器编译错误
问题:自定义着色器无法编译。
解决方案:
// 1. 检查着色器语法
const vertexShader = `
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;
// 2. 使用正确的内置变量
// position, normal, uv 是属性
// projectionMatrix, modelViewMatrix 是 uniform
// 3. 检查版本兼容性
// Three.js 默认使用 GLSL ES 3.0
// 确保不使用旧语法
// 4. 捕获错误
const material = new THREE.ShaderMaterial({
vertexShader,
fragmentShader,
uniforms: {}
});
// 强制编译
renderer.compile(scene, camera);
// 5. 检查控制台输出
// Three.js 会在控制台输出编译错误
着色器变量冲突
问题:自定义属性名与内置名冲突。
解决方案:
// 避免使用内置名称
// 错误:使用内置名称
// attribute vec3 color;
// 正确:使用自定义名称
attribute vec3 aColor;
// 常见的内置名称:
// position, normal, uv, color, tangent
// 建议的自定义命名方式:
// a 前缀表示 attribute:aPosition, aColor
// v 前缀表示 varying:vUv, vNormal
// u 前缀表示 uniform:uTime, uColor
交互问题
射线检测不到物体
问题:raycaster 无法检测到场景中的物体。
解决方案:
// 1. 确保传入正确的物体列表
const intersects = raycaster.intersectObjects(scene.children);
// 2. 如果有嵌套,使用递归
const intersects = raycaster.intersectObjects(scene.children, true);
// 3. 检查物体是否可见
console.log(mesh.visible);
// 4. 检查物体是否有几何体
// 空的 Group 或辅助对象可能没有几何体
// 5. 检查射线方向
raycaster.setFromCamera(mouse, camera);
console.log(raycaster.ray);
控制器不工作
问题:OrbitControls 无法旋转或缩放。
解决方案:
// 1. 检查是否在动画循环中更新
function animate() {
requestAnimationFrame(animate);
controls.update(); // 必须调用
renderer.render(scene, camera);
}
// 2. 检查是否传入了正确的 DOM 元素
const controls = new OrbitControls(camera, renderer.domElement);
// 3. 检查是否被其他事件阻止
// 检查 CSS pointer-events 设置
// 4. 检查相机位置
// 相机太近或太远可能导致问题
响应式问题
窗口大小变化后显示异常
问题:调整窗口大小后,场景显示拉伸或变形。
解决方案:
window.addEventListener('resize', () => {
// 更新相机宽高比
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
// 更新渲染器大小
renderer.setSize(window.innerWidth, window.innerHeight);
});
// 如果使用后处理
window.addEventListener('resize', () => {
composer.setSize(window.innerWidth, window.innerHeight);
});
移动端触摸不响应
问题:在移动设备上触摸操作无效。
解决方案:
// 1. 添加 viewport meta 标签
// <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
// 2. 阻止默认触摸行为
renderer.domElement.addEventListener('touchstart', (e) => {
e.preventDefault();
}, { passive: false });
// 3. 检查控制器触摸配置
controls.touches = {
ONE: THREE.TOUCH.ROTATE,
TWO: THREE.TOUCH.DOLLY_PAN
};
调试技巧
使用辅助工具
// 坐标轴辅助
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);
// 网格辅助
const gridHelper = new THREE.GridHelper(10, 10);
scene.add(gridHelper);
// 相机辅助
const cameraHelper = new THREE.CameraHelper(light.shadow.camera);
scene.add(cameraHelper);
// 光源辅助
const lightHelper = new THREE.DirectionalLightHelper(light, 1);
scene.add(lightHelper);
// 骨骼辅助
const skeletonHelper = new THREE.SkeletonHelper(mesh);
scene.add(skeletonHelper);
输出调试信息
// 输出物体信息
function debugObject(obj) {
console.log('位置:', obj.position);
console.log('旋转:', obj.rotation);
console.log('缩放:', obj.scale);
console.log('可见:', obj.visible);
}
// 输出场景树
function dumpObject(obj, indent = 0) {
console.log(' '.repeat(indent) + obj.type + ': ' + obj.name);
obj.children.forEach(child => dumpObject(child, indent + 2));
}
dumpObject(scene);
强制刷新材质
// 更新材质后可能需要标记
material.needsUpdate = true;
// 更新几何体属性后
geometry.attributes.position.needsUpdate = true;
小结
Three.js 开发中的常见问题主要集中在以下几个方面:
- 渲染问题:黑屏、颜色错误、透明度问题
- 几何体和材质:变形、纹理不显示
- 光照和阴影:阴影不显示、阴影瑕疵
- 性能:卡顿、内存泄漏
- 模型加载:路径错误、格式问题
- 着色器:编译错误、变量冲突
- 交互:射线检测、控制器问题
- 响应式:窗口调整、移动端触摸
遇到问题时,建议使用辅助工具可视化调试,检查控制台错误信息,逐步排查可能的原因。大多数问题都有明确的解决方案,关键是准确定位问题所在。