You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 08:56:18