从鼠标位置和深度图计算 3D 点

Posted

技术标签:

【中文标题】从鼠标位置和深度图计算 3D 点【英文标题】:Compute 3D point from mouse-position and depth-map 【发布时间】:2017-10-22 14:34:24 【问题描述】:

我需要使用渲染的深度图从屏幕空间位置计算 3D 坐标。不幸的是,使用常规光线追踪对我来说不是一个选项,因为我正在处理包含大约 5M 面的单个几何体。

所以我想我会做以下事情:

使用 RGBADepthPacking 将深度图渲染到渲染目标中 使用常规的 unproject-call 从鼠标位置计算光线(就像我在使用光线投射时所做的那样) 在鼠标坐标处从深度图中查找深度,并使用该距离沿射线计算一个点。

This kind of works,但不知何故,定位点总是稍微落后于对象,所以我的深度计算可能有问题。

现在关于上述步骤的一些细节

渲染深度图非常简单:

const depthTarget = new THREE.WebGLRenderTarget(w, h);
const depthMaterial = new THREE.MeshDepthMaterial(
  depthPacking: THREE.RGBADepthPacking
);

// in renderloop
renderer.setClearColor(0xffffff, 1);
renderer.clear();
scene.overrideMaterial = depthMaterial;
renderer.render(scene, camera, depthTarget);

在鼠标位置查找存储的颜色值:

renderer.readRenderTargetPixels(
  depthTarget, x, h - y, 1, 1, rgbaBuffer
);

并使用(改编自packing.glsl中的GLSL-Version)转换回浮点数:

const v4 = new THREE.Vector4()
const unpackDownscale = 255 / 256;
const unpackFactors = new THREE.Vector4(
  unpackDownscale / (256 * 256 * 256),
  unpackDownscale / (256 * 256),
  unpackDownscale / 256,
  unpackDownscale
);

function unpackRGBAToDepth(rgbaBuffer) 
  return v4.fromArray(rgbaBuffer)
    .multiplyScalar(1 / 255)
    .dot(unpackFactors);

最后计算深度值(我在 examples/js/shaders/SSAOShader.js 中的 readDepth() 中找到了相应的代码,并将其移植到 JS):

function computeDepth() 
  const cameraFarPlusNear = cameraFar + cameraNear;
  const cameraFarMinusNear = cameraFar - cameraNear;
  const cameraCoef = 2.0 * cameraNear;

  let z = unpackRGBAToDepth(rgbaBuffer);
  return cameraCoef / (cameraFarPlusNear - z * cameraFarMinusNear);

现在,由于此函数返回 0..1 范围内的值,我认为它是剪辑空间坐标中的深度,因此我使用以下方法将它们转换为“真实”单位:

const depth = camera.near + depth * (camera.far - camera.near);

这些计算显然有些偏差,我还没有弄清楚关于如何存储深度的数学和细节。 有人可以指出我犯的错误吗?

补充:我尝试过的其他事情

首先我认为应该可以在我的 unproject-call 中使用解压缩的深度值作为 z 的值,如下所示:

const x = mouseX/w * 2 - 1;
const y = -mouseY/h * 2 + 1;
const v = new THREE.Vector3(x, y, depth).unproject(camera);

但是,这也无法正确获取坐标。

[编辑 1 2017-05-23 11:00CEST]

根据@WestLangleys 的评论,我发现perspectiveDepthToViewZ() 函数听起来应该有帮助。用JS写的那个函数是

function perspectiveDepthToViewZ(invClipZ, near, far) 
  return (near * far) / ((far - near) * invClipZ - far);

但是,当使用深度图中的解压缩值调用时,结果会相差几个数量级。 See here.

【问题讨论】:

也许这些会有所帮助:github.com/mrdoob/three.js/blob/master/src/renderers/shaders/…, github.com/mrdoob/three.js/blob/master/examples/… 您是否假设您的深度缓冲区是线性的?我不完全确定你是否已经考虑过这一点,但这可能是你数学不好的原因。 @WestLangley 谢谢!我没有注意到那里的perspectiveDepthToViewZ()-函数,但不幸的是a)我仍然不太了解数学和b)结果,当提供深度缓冲区值时对我没有任何意义。跨度> @pailhead 已经考虑过了,这就是为什么我最初只想反转透视变换的原因。在这一点上,我只是在尝试直到得到有意义的数字:/ @WestLangley:事实证明,我做错了,perspectiveDepthToViewZ() 的解决方案现在有了令人信服的结果。如果您将其添加为答案,我将很乐意接受。不然我明天自己加。。。 【参考方案1】:

好的,所以。终于解决了。因此,对于遇到类似问题的每个人,以下是解决方案:

computeDepth-function 的最后一行是错误的。 packing.glsl中有一个函数perspectiveDepthToViewZ,很容易转成JS:

function perspectiveDepthToViewZ(invClipZ, near, far) 
  return (near * far) / ((far - near) * invClipZ - far);

(我相信这是逆投影矩阵的一部分)

function computeDepth() 
  let z = unpackRGBAToDepth(rgbaBuffer);
  return perspectiveDepthToViewZ(z, camera.near, camera.far);

现在这将返回该点在视图空间中的 z 轴值。剩下要做的就是将其转换回世界空间坐标:

const setPositionFromViewZ = (function() 
  const viewSpaceCoord = new THREE.Vector3();
  const projInv = new THREE.Matrix4();

  return function(position, viewZ) 
    projInv.getInverse(camera.projectionMatrix);
    position
      .set(
        mousePosition.x / windowWidth * 2 - 1,
        -(mousePosition.y / windowHeight) * 2 + 1,
        0.5
      )
      .applyMatrix4(projInv);

    position.multiplyScalar(viewZ / position.z);
    position.applyMatrix4(camera.matrixWorld);
  ;
) ();

【讨论】:

能否请您用正确的解决方案更新 jsfiddle,让我们凡人理解它? 太棒了,我试图找出解决方案好几天,但我不能只通过两次 - 我会详尽地研究它,非常感谢! :)

以上是关于从鼠标位置和深度图计算 3D 点的主要内容,如果未能解决你的问题,请参考以下文章

unity3d 2D平面游戏实现鼠标拖拽物体移动

C#XNA鼠标的位置投射到3D平面

从3个点检索正角度或负角度

如何从labview显示出点图片中,提取出鼠标所在位置的像素坐标?

Three.js 3D缩放到鼠标位置

js获取鼠标当前的位置