跳到主要内容

常见问题与解决方案

在 Three.js 开发过程中,经常会遇到各种问题。本章收集了最常见的错误和解决方案,帮助你快速定位和解决问题。

场景渲染问题

场景显示黑屏

问题:场景加载后显示黑屏,没有任何内容。

可能原因和解决方案

1. 没有光源

如果使用 MeshStandardMaterialMeshPhongMaterial,场景中必须有光源:

// 添加环境光
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 开发中的常见问题主要集中在以下几个方面:

  • 渲染问题:黑屏、颜色错误、透明度问题
  • 几何体和材质:变形、纹理不显示
  • 光照和阴影:阴影不显示、阴影瑕疵
  • 性能:卡顿、内存泄漏
  • 模型加载:路径错误、格式问题
  • 着色器:编译错误、变量冲突
  • 交互:射线检测、控制器问题
  • 响应式:窗口调整、移动端触摸

遇到问题时,建议使用辅助工具可视化调试,检查控制台错误信息,逐步排查可能的原因。大多数问题都有明确的解决方案,关键是准确定位问题所在。