在 ARKit 中将屏幕点取消投影到水平面

afe*_*iss 4 opengl-es matrix augmented-reality metal arkit

我正在尝试使用 ARKit 将屏幕上的某个点取消投影到世界空间中 ARPlaneAnchor 上的坐标。基本上我想根据我的相机指向的位置在地平面上绘制一个标记。ARKit 提供了一些方法来执行此操作,但是它们附加到 ARCamera 类,不幸的是,在我的场景中,我只能访问相机的投影、视图和变换矩阵,而无法引用 ARFrame 或 ARCamera 对象本身。我写了一些与 gluUnproject() 类似的东西(据我所知)。

func screenToWorld(screenPoint: CGPoint, depth: Float, screenSize: CGSize, projectionMatrix: simd_float4x4, viewMatrix:simd_float4x4) -> float3{

    let viewProj = viewMatrix * projectionMatrix
    let inverse = simd_inverse(viewProj)

    let x = (2.0 * screenPoint.x ) / screenSize.width - 1.0
    let y = (2.0 * screenPoint.y) / screenSize.height - 1.0

    let inPoint = float4(Float(x), Float(y), depth * 2.0 - 1.0, 1.0)

    var position = inPoint * inverse
    position.w = 1.0 / position.w

    position.x *= position.w
    position.y *= position.w
    position.z *= position.w

    return float3(position.x, position.y, position.z)
}


let pos = screenToWorld(screenPoint: CGPoint(360, 640), depth: depthBufferValue, screenSize: CGSize(720, 1280), projectionMatrix: projectionMatrix, viewMatrix: viewMatrix)
Run Code Online (Sandbox Code Playgroud)
  1. screenPoint 是一些窗口坐标
  2. 深度是通过在较早的过程中从深度缓冲区读回一个值来计算的
  3. screenSize 是视口的大小
  4. 投影矩阵来自 projectionMatrix(for:.portrait, viewportSize: CGSize(720,1280),zNear: 0.001, zFar: 1000)
  5. viewMatrix 来自 viewMatrix(for:.portrait)

我唯一不确定的是 viewMatrix 属性。我唯一能想到的可能是连接到相机的变换矩阵。但是,我试过这个,它似乎也没有给我正确的位置。在 gluUnproject 中,他们将这个值称为 modelView 矩阵,在 glm 中它只是称为模型,所以我对它应该是什么有点困惑。

用我从 unProject 函数中得到的值,我像这样翻译了一个单位矩阵

var modelMatrix = matrix_identity_float4x4
modelMatrix = modelMatrix.translatedBy(x: pos.x, y: pos.y, z: pos.z)
Run Code Online (Sandbox Code Playgroud)

然后像这样将值发送到着色器......

encoder.setVertexBytes(&projectionMatrix, length: MemoryLayout<simd_float4x4>.stride, index: 1)
encoder.setVertexBytes(&viewMatrix, length: MemoryLayout<simd_float4x4>.stride, index: 2)
encoder.setVertexBytes(&modelMatrix, length: MemoryLayout<simd_float4x4>.stride, index: 3) 
Run Code Online (Sandbox Code Playgroud)

在我的顶点着色器里面我做

VertexOut vertexOut;
float4x4 modelViewMatrix = viewMatrix * modelMatrix;
vertexOut.position = projectionMatrix * modelViewMatrix * vertexIn.position;
return vertexOut;
Run Code Online (Sandbox Code Playgroud)

我认为我的 z 值是正确的(来自深度缓冲区的深度值),所以我真的只需要找到我的 x 和 y。我也不是 100% 确定如何处理我的 unproject 函数返回的值。我认为这只是世界坐标,但也许它需要以某种方式进行缩放?

我希望得到一些帮助以获得正确的坐标,或者如果上面有任何错误,请指出它们!

afe*_*iss 6

对于将来遇到此问题的任何人,解决方案(如@rabbid76 所建议的)正在翻转我的投影和视图矩阵以及我的逆矩阵和 inPoint 的乘法顺序。我还需要翻转要检查的 screenPoint 的 y 坐标。

这是完整的功能:

func screenToWorld(screenPoint: CGPoint, depth: Float, screenSize: CGSize, projectionMatrix: simd_float4x4, viewMatrix:simd_float4x4) -> float3{

    let viewProj = projectionMatrix * viewMatrix
    let inverse = simd_inverse(viewProj)

    let x = (2.0 * screenPoint.x ) / screenSize.width - 1.0
    let y = 1.0 - (2.0 * screenPoint.y) / screenSize.height 

    let inPoint = float4(Float(x), Float(y), depth * 2.0 - 1.0, 1.0)

    var position = inverse * inPoint
    position.w = 1.0 / position.w

    position.x *= position.w
    position.y *= position.w
    position.z *= position.w

    return float3(position.x, position.y, position.z)
}
Run Code Online (Sandbox Code Playgroud)