ThreeJS结合OrbitControl时,如何正确为点击的Mesh着色?
解决Three.js中点击Mesh着色偏差的问题
问题场景
在Three.js中创建了一组球体Mesh,需要实现点击单个球体时为其着色,同时使用OrbitControl控制3D视角。但点击时总是错误选中其他Mesh,仅在模型放大时点击准确率较高,缩小后偏差明显。
核心代码片段
创建球体的逻辑:
lines.forEach(line => { const values = line.split('\t'); // 制表符分隔 if (values.length === 3) { const x = parseFloat(values[0]); const y = parseFloat(values[1]); const z = parseFloat(values[2]); const sphere = new THREE.Mesh(geometry, defaultMaterial.clone()); // 保存原始颜色,用于后续重置 sphere.userData.originalColor = sphere.material.color.clone(); switch (currentAxis) { case 'x': sphere.position.set(x, 0, 0); break; case 'y': sphere.position.set(0, y, 0); break; case 'z': sphere.position.set(0, 0, z); break; default: sphere.position.set(x, y, z); break; } scene.add(sphere); spheres.push(sphere); } }); renderer.domElement.addEventListener('click', onCanvasClick, false);
初始点击事件逻辑:
function onCanvasClick(event) { // 计算归一化设备坐标(NDC) var mouse = new THREE.Vector2(); mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; // 射线检测点击对象 var raycaster = new THREE.Raycaster(); raycaster.setFromCamera(mouse, camera); var intersects = raycaster.intersectObjects(spheres); if (intersects.length > 0) { if (intersects[0].object !== clickedMesh) { if (clickedMesh) { clickedMesh.material.color.set(0xffffff); // 硬编码重置颜色存在问题 } intersects[0].object.material.color.set(0x0000ff); clickedMesh = intersects[0].object; } } }
已尝试的方案
- 初始使用窗口尺寸计算鼠标NDC坐标,点击偏差严重
- 改用容器的
getBoundingClientRect()计算坐标,仅模型放大时点击准确 - 尝试官方框选示例的SelectionBox和SelectionHelper组件,问题未解决
解决方案
1. 修复响应式尺寸更新逻辑
当窗口或容器尺寸变化时,相机的投影矩阵和渲染器尺寸未同步更新,导致射线投射的坐标计算偏差。添加resize事件监听:
const container = document.querySelector('.design'); const width = container.clientWidth; const height = container.clientHeight; const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000); const CAM_DISTANCE = 10; // 监听尺寸变化,同步更新相机和渲染器 window.addEventListener('resize', () => { const newWidth = container.clientWidth; const newHeight = container.clientHeight; // 更新相机宽高比和投影矩阵 camera.aspect = newWidth / newHeight; camera.updateProjectionMatrix(); // 更新渲染器尺寸 renderer.setSize(newWidth, newHeight); });
2. 修正鼠标坐标计算
确保点击坐标基于容器的实际尺寸,且考虑设备像素比(DPR),避免高分辨率屏幕下的偏差:
function onCanvasClick(event) { const containerRect = container.getBoundingClientRect(); const mouse = new THREE.Vector2(); // 计算基于容器的相对坐标,并适配设备像素比 const x = ((event.clientX - containerRect.left) / containerRect.width) * 2 - 1; const y = -((event.clientY - containerRect.top) / containerRect.height) * 2 + 1; mouse.x = x * window.devicePixelRatio; mouse.y = y * window.devicePixelRatio; const raycaster = new THREE.Raycaster(); raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObjects(spheres); if (intersects.length > 0) { const targetMesh = intersects[0].object; if (targetMesh !== clickedMesh) { // 重置上一个选中Mesh的原始颜色 if (clickedMesh) { clickedMesh.material.color.copy(clickedMesh.userData.originalColor); } // 设置当前选中Mesh的颜色 targetMesh.material.color.set(0x0000ff); clickedMesh = targetMesh; } } }
3. 确保射线检测精度
- 保持所有Mesh的
material.depthTest属性为true(默认开启),保证深度排序正确 - 如果球体尺寸过小,可适当增大几何体尺寸,或调整
raycaster.far参数扩大检测范围
内容的提问来源于stack exchange,提问作者sunShine
相关产品推荐
相关产品推荐

