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

Mar*_*fuß 4 depth-buffer projection-matrix three.js raycasting

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

所以我想我将执行以下操作:

  • 使用RGBADepthPacking将深度图渲染到renderTarget中
  • 使用常规的unproject-call从鼠标位置计算射线(与使用射线投射时完全一样)
  • 从深度图的鼠标坐标处查找深度,并使用该距离沿射线计算一个点。

这种工作,但是以某种方式定位点总是在物体的后面,因此我的深度计算可能有问题。

现在,有关上述步骤的一些详细信息

渲染深度图非常简单:

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);
Run Code Online (Sandbox Code Playgroud)

使用以下命令在鼠标位置查找存储的颜色值:

renderer.readRenderTargetPixels(
  depthTarget, x, h - y, 1, 1, rgbaBuffer
);
Run Code Online (Sandbox Code Playgroud)

并使用(从packing.glsl中的GLSL版本改编)转换回float:

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);
}
Run Code Online (Sandbox Code Playgroud)

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

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

  let z = unpackRGBAToDepth(rgbaBuffer);
  return cameraCoef / (cameraFarPlusNear - z * cameraFarMinusNear);
}
Run Code Online (Sandbox Code Playgroud)

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

const depth = camera.near + depth * (camera.far - camera.near);
Run Code Online (Sandbox Code Playgroud)

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

另外:我尝试过的其他东西

首先,我认为应该可以在我的unproject-call中将解压后的depth-value用作z的值,如下所示:

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

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

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

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

function perspectiveDepthToViewZ(invClipZ, near, far) {
  return (near * far) / ((far - near) * invClipZ - far);
}
Run Code Online (Sandbox Code Playgroud)

但是,当使用深度图的解压缩值进行调用时,结果将减少几个数量级。看这里

Mar*_*fuß 5

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

computeDepth函数的最后一行是错误的。有一个功能perspectiveDepthToViewZpacking.glsl,这是非常容易转换到JS:

function perspectiveDepthToViewZ(invClipZ, near, far) {
  return (near * far) / ((far - near) * invClipZ - far);
}
Run Code Online (Sandbox Code Playgroud)

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

function computeDepth() {
  let z = unpackRGBAToDepth(rgbaBuffer);
  return perspectiveDepthToViewZ(z, camera.near, camera.far);
}
Run Code Online (Sandbox Code Playgroud)

现在,这将返回视图空间中该点的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);
  };
}) ();
Run Code Online (Sandbox Code Playgroud)