【问题标题】:Detect clicked object in THREE.js在 THREE.js 中检测被点击的对象
【发布时间】:2026-02-10 07:55:02
【问题描述】:

我有一个 THREE.js 场景,其中出现了很多元素,我需要检测用户点击的是什么对象。

到目前为止,我所做的如下。相机不会移动太多 - 它只会改变有限的垂直位置,始终朝向同一点。我的大致方法如下:

  • 如果点击相对于画布,我会获取坐标
  • 我通过简单的重新缩放将它们转换为 webGL 场景中的水平和垂直坐标,并添加一个足够远的 Z 坐标。
  • 我从上面的点开始拍摄水平射线,由 THREE.Ray() 构造
  • 我使用 ray.intersectObjects() 来查找沿射线的第一个元素。

这种方法大致可行,但有时距离实际点有几个像素。

是否有更可靠的技术来找出用户点击的对象?

【问题讨论】:

  • 边距和填充可能会导致您的坐标有点偏移。你解释了吗?
  • 目前在演示中没有边距和内边距,但我描述的技术并不准确。
  • 谢谢,这正是我需要的。我不知道projector.unprojectVector()。在后来的版本中,我尝试自己取消投影矢量,考虑到它与相机形成的角度,我可以找到它,因为我知道 fov,但它没有按预期工作。顺便说一句,在将向量传递给 Ray 构造函数之前对向量进行规范化是有原因的吗?归一化后方向不会改变。

标签: javascript three.js


【解决方案1】:

取决于您使用的是哪种相机。

1) PerspectiveCameraMr.doob 提供的链接是可以的。
2) OrthographicCamera:完全不同:

var init = function() {
  camera = new THREE.OrthographicCamera( SCREEN_WIDTH / - 2, SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, SCREEN_HEIGHT / - 2, NEAR, FAR);
  document.addEventListener( 'mousedown', onDocumentMouseDown, false );
}

function onDocumentMouseDown( e ) {
  e.preventDefault();
  var mouseVector = new THREE.Vector3();
  mouseVector.x = 2 * (e.clientX / SCREEN_WIDTH) - 1;
  mouseVector.y = 1 - 2 * ( e.clientY / SCREEN_HEIGHT );
  var raycaster = projector.pickingRay( mouseVector.clone(), camera );
  var intersects = raycaster.intersectObject( TARGET );
  for( var i = 0; i < intersects.length; i++ ) {
    var intersection = intersects[ i ],
    obj = intersection.object;
    console.log("Intersected object", obj);
  }
}

【讨论】:

  • “projector.pickingRay”被移除了吗?
  • 但是它给出了一个包含大量元素的数组,但我只想要一个被用户点击的精确元素??
  • 目标是什么?
  • 这让我很紧张,因为调整窗口大小可能会影响 SCREEN_WIDTHSCREEN_HEIGHT 的值
  • 谁是Mr.doob
【解决方案2】:

看看这个:

var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 5000);
var object; //your object

document.addEventListener('mousedown', onMouseDown, false);

function onMouseDown(e) {
    var vectorMouse = new THREE.Vector3( //vector from camera to mouse
        -(window.innerWidth/2-e.clientX)*2/window.innerWidth,
        (window.innerHeight/2-e.clientY)*2/window.innerHeight,
        -1/Math.tan(22.5*Math.PI/180)); //22.5 is half of camera frustum angle 45 degree
    vectorMouse.applyQuaternion(camera.quaternion);
    vectorMouse.normalize();        

    var vectorObject = new THREE.Vector3(); //vector from camera to object
    vectorObject.set(object.x - camera.position.x,
                     object.y - camera.position.y,
                     object.z - camera.position.z);
    vectorObject.normalize();
    if (vectorMouse.angleTo(vectorObject)*180/Math.PI < 1) {
        //mouse's position is near object's position

    }
}

【讨论】:

  • 这是一个非常好的解决方案,可以防止 cpu 密集型光线投射,但只查看鼠标是否在对象的枢轴附近单击。优点是没有网格的对象也会以这种方式工作。缺点是圆形“热点”。
【解决方案3】:

检查鼠标和 3d 空间中的任何立方体的交集并改变它的颜色。也许this 能帮到你。

【讨论】:

    【解决方案4】:

    我在尝试为不占用屏幕整个宽度和高度的画布实现此功能时遇到了问题。这是我发现效果很好的解决方案。

    初始化现有画布上的所有内容:

    var init = function() {
      var canvas_model = document.getElementById('model')
      var viewSize = 50 // Depending on object size, canvas size etc.
      var camera = new THREE.OrthographicCamera(-canvas_model.clientWidth/viewSize, canvas_model.clientWidth/viewSize, canvas_model.clientHeight/viewSize, -canvas_model.clientHeight/viewSize, 0.01, 2000),
    }
    

    向画布添加事件监听器:

    canvas_model.addEventListener('click', function(event){
      var bounds = canvas_model.getBoundingClientRect()
      mouse.x = ( (event.clientX - bounds.left) / canvas_model.clientWidth ) * 2 - 1;
      mouse.y = - ( (event.clientY - bounds.top) / canvas_model.clientHeight ) * 2 + 1;
      raycaster.setFromCamera( mouse, camera );
      var intersects = raycaster.intersectObjects(scene.children, true);
      if (intersects.length > 0) {
         // Do stuff
      }
    }, false)
    

    或者对于 'touchstart' 事件,将计算 mouse.x 和 mouse.y 的行更改为:

    mouse.x = ( (event.touches[0].clientX - bounds.left) / canvas_model.clientWidth ) * 2 - 1;
    mouse.y = - ( (event.touches[0].clientY - bounds.top) / canvas_model.clientHeight ) * 2 + 1;
    

    【讨论】: