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

IT技术 javascript 3d mouse webgl coordinate
2021-02-07 01:59:02

我正在 WebGL 中构建一个棋盘游戏。该板可以旋转/缩放。我需要一种方法将画布元素 (x,y) 上的点击转换为 3D 空间 (x, y, z) 中的相关点。最终的结果是我想知道(x,y,z)坐标,该坐标包含与用户最近的物体接触的点。例如,用户点击一个棋子,你想象一条光线穿过 3D 空间,穿过棋子和游戏板,但我想要棋子的 (x, y, z) 坐标在它所在的点感动。

我觉得这一定是一个非常普遍的问题,但我似乎无法在我的谷歌中找到解决方案。必须有某种方法将 3D 空间的当前视图投影到 2D 中,以便您可以将 2D 空间中的每个点映射到 3D 空间中的相关点。我希望用户能够将鼠标悬停在板上的空间上,并让专色更改颜色。

4个回答

您正在寻找一个 unproject 函数,它将屏幕坐标转换为从相机位置投射到 3D 世界的光线。然后,您必须执行光线/三角形相交测试,以找到距离相机最近且也与光线相交的三角形。

我在jax/camera.js#L568 上有一个取消投影的例子——但你仍然需要实现光线/三角形相交。我在jax/triangle.js#L113有一个实现

然而,有一种更简单且(通常)更快的替代方法,称为“挑选”。如果您想选择整个对象(例如,棋子),并且您不关心鼠标实际单击的位置,请使用此选项。WebGL 的方法是将整个场景以各种深浅不一的蓝色(蓝色是关键,而红色和绿色用于场景中对象的唯一 ID)渲染到纹理,然后从那种质地。将 RGB 解码为对象的 ID 将为您提供单击的对象。同样,我已经实现了这个,它可以在jax/world.js#L82 上找到(另见第 146、162、175 行。)

这两种方法都有利有弊(在此处和之后的一些评论中进行了讨论),您需要确定哪种方法最能满足您的需求。大场景的选择速度较慢,但​​在纯 JS 中取消投影非常慢(因为 JS 本身并不是那么快)所以我最好的建议是对两者进行试验。

仅供参考,您还可以查看 GLU 项目和取消项目代码,我的代码松散地基于这些代码:http : //www.opengl.org/wiki/GluProject_and_gluUnProject_code

正确的。您可以通过使用不同的拾取颜色绘制每个三角形来解决这个问题,但这会非常慢(我怀疑它在 JavaScript 中是否可行,但发生了奇怪的事情),因为它需要对每个三角形进行一次新的传递。对于精确的三角形/顶点信息,最好的办法是取消投影。
2021-03-28 01:59:02
澄清一下:在 WebGL 中选择通常比取消投影更快,纯粹是因为 JS 很慢,并且选择利用 GPU 进行硬件加速,从而绕过 JS 瓶颈。在传统的 C 实现中,取消投影可能是要走的路,特别是如果您只需要检查非常少量的三角形。
2021-04-04 01:59:02
哇,非常感谢,我会调查这一切。这完美地回答了我的问题。
2021-04-08 01:59:02
所以如果我需要知道确切的三角形/顶点,选择是不够的?
2021-04-10 01:59:02
只是为了澄清,术语“采摘”与使用的任何特定技术无关。同样取决于代码的结构,当屏幕上有几千个可见的对象时,通过 colorids 选择可能会变得非常慢。这就是为什么我建议在应用适当的空间细分的情况下进行光线拾取(同时应用于场景和网格三角形)。
2021-04-12 01:59:02

我目前正在解决这个问题 - 我采取的方法是

  1. 渲染对象以选择每个具有独特颜色的缓冲区
  2. 读取缓冲区像素,映射回拾取的对象
  3. 渲染拾取的对象以缓冲每个像素颜色的 Z 深度函数
  4. 读取缓冲区像素,映射回 Z 深度
  5. 我们已经选取了对象并为选取坐标近似了 Z

这是工作演示

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/

从其中一个线程中培养出来的。不确定 (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};
}