如何从鼠标点击坐标中获取WebGL 3D空间中的对象

20
我正在使用WebGL构建一个桌面游戏。可以旋转/缩放游戏板。我需要一种方式将画布元素(x,y)上的点击转换为3D空间中相关点(x,y,z)。最终结果是我想知道包含与用户最接近的对象碰触点的(x,y,z)坐标。例如,用户单击一个棋子,你就可以想象一条穿过3D空间的射线,穿过棋子和游戏板,但我想要的是在被触摸到的地方的棋子的(x,y,z)坐标。
我觉得这肯定是个很常见的问题,但我似乎找不到谷歌上的解决方案。必须有一种将3D空间的当前视图投影到2D空间的方法,以便可以将2D空间中的每个点映射到3D空间中的相关点。我希望用户能够在游戏板上悬停鼠标,然后该处点变色。

你不是在谈论这个吧?http://www.3dkingdoms.com/selection.html#ray 只需从摄像机镜头向平面投射一个法线,然后计算出交点。 - Incognito
我认为这篇文章大致描述了我想做的事情,但是从他的描述中我无法得出算法。 - seibelj
必须从将视口想象为位于原点(0,0,0)处的平面,并且矩形中心也在原点进行某种计算。我会再考虑一下,相信会有一个优雅的解决方案。 - seibelj
4个回答

30
您正在寻找一个未投影函数,该函数将屏幕坐标转换为从相机位置到三维世界中的光线投射。然后,您必须执行射线/三角形交集测试,以找到最接近相机的三角形,该三角形也与该光线相交。 我有一个未投影的示例可在jax/camera.js#L568中找到 - 但您仍需要实现射线/三角形交集。我在jax/triangle.js#L113中有一个实现。
然而,还有一种更简单(通常更快)的选择,称为“拾取”。如果您想选择整个对象(例如国际象棋棋子),并且不关心鼠标实际单击的位置,则使用此选项。在WebGL中执行此操作的方法是将整个场景呈现为各种蓝色(蓝色是关键字,而红色和绿色用于场景中对象的唯一ID),然后从该纹理中读取一个像素。将RGB解码为对象的ID将给出被单击的对象。同样,我已经实现了这个功能,可以在jax/world.js#L82中找到(也请参见第146、162、175行)。

这两种方法各有优缺点(在这里和一些评论中讨论过),你需要决定哪种方法最适合你的需求。对于大场景选择Picking速度较慢,但使用纯JS进行Unproject操作极其缓慢(因为JS本身并不是很快),所以我的最佳建议是尝试使用两种方法进行实验。

另外,你也可以查看GLU项目和Unproject代码,我自由地参考了这些代码:http://www.opengl.org/wiki/GluProject_and_gluUnProject_code


澄清一下:在WebGL中进行拾取通常比仅进行反投影更快,因为JS很慢,并且拾取利用GPU进行硬件加速,从而绕过JS瓶颈。在传统的C实现中,如果您只需要检查非常少量的三角形,则可能需要使用反投影。 - sinisterchipmunk
哇,非常感谢,我会仔细研究这些内容。这完美地回答了我的问题。 - seibelj
那么,如果我需要知道确切的三角形/顶点,仅使用拾取是不足够的吗? - Drew
没错。您可以通过使用不同的选取颜色绕开这个问题,但这样做会非常慢(我怀疑在JavaScript中行不通,但更奇怪的事情已经发生过了),因为每个三角形都需要新的遍历。对于精确的三角形/顶点信息,最好的选择是反向投影。 - sinisterchipmunk
仅供澄清,术语“picking”与任何特定的技术无关。此外,当屏幕上有几千个对象可见时,根据代码结构,通过颜色ID进行拾取可能会变得非常缓慢。这就是为什么我建议使用合理的空间细分应用射线拾取(对场景和网格三角形都适用)。 - LJᛃ

2

我正在处理这个问题 - 我采用的方法是

  1. 将每个对象渲染到选择缓冲区,每个对象都有唯一的颜色
  2. 读取缓冲区像素,映射回选中的对象
  3. 将选中的对象渲染到缓冲区,每个像素颜色都是Z深度的函数
  4. 读取缓冲区像素,映射回Z深度
  5. 我们已经选择了对象并近似确定了选择坐标的Z深度

1
这是工作演示。
function onMouseUp(event) {

    event.preventDefault();        
    x_pos = (event.clientX / window.innerWidth) * 2 - 1;
    y_pos = -(event.clientY / window.innerHeight) * 2 + 1;
    z_pos = 0.5;

    var vector = new THREE.Vector3( x_pos , y_pos , z_pos );

    var projector = new THREE.Projector();
    projector.unprojectVector(vector, camera);
    var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
    var intersects = raycaster.intersectObjects(intersectObjects);

    if (intersects.length > 0) {

        xp = intersects[0].point.x.toFixed(2);
        yp = intersects[0].point.y.toFixed(2);
        zp = intersects[0].point.z.toFixed(2);  
        destination = new THREE.Vector3( xp , yp , zp );

        radians =  Math.atan2( ( driller.position.x - xp) , (driller.position.z - zp));
        radians += 90 * (Math.PI / 180);
        console.log(radians);

        var tween = new TWEEN.Tween(driller.rotation).to({ y : radians },200).easing(TWEEN.Easing.Linear.None).start();

    }

weissner-doors.de/drone/


1
这个问题是关于webGL的,它不使用ThreeJS。 - Jelle Bruisten

-1

从其中一个线程中提取。不确定(x,y,z),但您可以使用 canvas(x,y) 获取

getBoundingClientRect()

function getCanvasCoord(){
  var mx = event.clientX;
  var my = event.clientY;
  var canvas = document.getElementById('canvasId');
  var rect = canvas.getBoundingClientRect();// check if your browser supports this
  mx = mx - rect.left;
  my = my - rect.top;
  return {x: mx , y: my};
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接