这里有几种方法,需要提防可能的错误开始。
⚠️ ARKit + SceneKit(不正确)
如果您已经通过 SceneKit (ARSCNView) 使用 ARKit,您可能会认为 ARKit 会自动更新 SceneKit 相机(视图的 pointOfView 的 camera)以匹配 ARKit 使用的投影变换.这是正确的。
但是,ARKit 直接设置了SCNCamera 的projectionTransform。当您使用SCNCamera 的几何属性(例如zNear、zFar 和fieldOfView)时,SceneKit 会派生一个投影矩阵以用于渲染。但是如果直接设置projectionTransform,没有数学可以恢复near/far和xFov/yFov的值,所以对应的SCNCamera属性无效。也就是说,sceneView.pointOfView.camera.fieldOfView 和类似的 API 总是会为 ARKit 应用返回虚假结果。
那么,你能做些什么呢?继续阅读...
投影矩阵
AR 会话通过其委托不断地出售ARFrame 对象,或者您可以向它请求currentFrame。每一帧都附有一个描述成像参数的ARCamera,其中一个是依赖于视野的projectionMatrix。 (还有前面提到的SceneKitprojectionTransform,就是同一个矩阵。)
标准 3D 投影矩阵包括基于垂直视野和纵横比的缩放项。具体来说,矩阵如下所示:
[ xScale 0 0 0 ] xScale = yScale * aspectRatio (width/height)
[ 0 yScale 0 0 ] yScale = 1 / tan(yFov/2)
[ 0 0 nf1 nf2 ] nf1 and nf2 relate to near and far clip planes,
[ 0 0 -1 0 ] so aren't relevant to field of view
所以你应该能够通过求解yScale 方程得到yFov:
let projection = session.currentFrame!.camera.projectionMatrix
let yScale = projection[1,1]
let yFov = 2 * atan(1/yScale) // in radians
let yFovDegrees = yFov * 180/Float.pi
对于水平视野,您可以乘以纵横比(特别是宽/高比):
let imageResolution = session.currentFrame!.camera.imageResolution
let xFov = yFov * Float(imageResolution.width / imageResolution.height)
注意:这里的“水平”和“垂直”是相对于相机图像而言的,无论您的设备或 AR 视图 UI 的方向如何,相机图像本身都是横向的。
不过,如果您仔细观察,您可能会注意到此处xFov/yFov 之间的纵横比(以及imageResolution 的纵横比)不一定与您设备屏幕的纵横比相匹配(尤其是在 iPhone 上) X) 或您在其中绘制 AR 内容的视图。这是因为您测量了 相机图像的 FOV 角度,而不是您应用的 AR 视图的 FOV 角度。别担心,还有一个 API 可以做到这一点……
带视口的投影矩阵
ARCamera 提供了两个用于获取投影矩阵的 API。除了我们刚刚介绍的那个,还有projectionMatrix(for:viewportSize:zNear:zFar:),它考虑了演示。如果您想匹配的不是相机的 FOV,而是 ARSCNView 或 ARSKView(或 Unity 或 Unreal,可能?)渲染您的 AR 场景的 FOV,请使用它,传递设备方向并查看您的视图.然后做与上面相同的数学运算:
let imageResolution = session.currentFrame!.camera.imageResolution
let viewSize = sceneView.bounds.size
let projection = session.currentFrame!.camera.projectionMatrix(for: .portrait,
viewportSize: viewSize, zNear: zNear, zFar: zFar)
let yScale = projection[1,1] // = 1/tan(fovy/2)
let yFovDegrees = 2 * atan(1/yScale) * 180/Float.pi
let xFovDegrees = yFovDegrees * Float(viewSize.height / viewSize.width)
zNear 和 zFar 传递的内容无关紧要,因为我们没有使用依赖于此的矩阵部分。 (您可能仍需要确保zNear < zFar 和zNear != zFar != 0。)
注意:现在高度/宽度基于您的视图(或者更确切地说,是您传递给projectionMatrix(for:...) 的视图属性)。在此示例中,yFov 相对于 UI 是垂直的,因为方向是 portrait,因此您乘以高/宽纵横比得到 xFov。如果您在横向,则改为乘以宽度/高度。
相机内在
敏锐的观察者可能已经注意到,上述计算忽略了投影矩阵的一部分。这是因为definition of FOV angle 是相机的光学属性,与 3D 投影无关,因此整个投影矩阵是您可能并不真正需要的中间结果。
ARCamera 还公开了一个描述相机光学特性的intrinsics 矩阵。此矩阵中沿对角线的第一个和第二个值是相机图像中单个像素的水平和垂直focal length。如果您有焦距和图像宽度/高度,则可以根据definition of FOV angle 计算 FOV:
let imageResolution = session.currentFrame!.camera.imageResolution
let intrinsics = session.currentFrame!.camera.intrinsics
let xFovDegrees = 2 * atan(Float(imageResolution.width)/(2 * intrinsics[0,0])) * 180/Float.pi
let yFovDegrees = 2 * atan(Float(imageResolution.height)/(2 * intrinsics[1,1])) * 180/Float.pi
注意:与使用projectionMatrix 的版本一样,这是基于相机图像的大小和始终横向,而不是设备屏幕或查看您正在显示 AR 内容的视图。如果您需要基于视口的东西,请向上滚动到“带视口的投影矩阵”。