追踪并可视化平面

检测物理环境中的表面并在3D空间中可视化其形状和位置。


概述

这个示例代码运行了通过在SceneKit视图中展示内容的一个ARKit世界追踪会话。为了展示平面检测,这个app可视化的表现了每一个检测到的ARPlaneAnchor 对象的预估形状,和一个关于它的边界矩形。在支持的设备上,ARKit可以识别多种真实世界的表面类型,所以app可以为每一个检测到的平面打上标识文字的标签。

记录

ARKit要求iOS设备的处理器不低于A9。ARKit是无法在iOS模拟器中运行的

配置并运行AR会话

ARSCNView 类是一个SceneKit视图,该视图包含了管理运动追踪和图像处理的 ARSession 对象,而这些都是创建一个AR体验所需要的。然而,为了运行一个会话,你必须提供一个会话配置项。

ARWorldTrackingConfiguration 类提供了高准确度的运动追踪和内置特性来帮助你在真实世界表面上防止虚拟内容。为了开始一个AR会话,创建一个会话配置项对象,并设置你需要的可选项(例如平面检测),接下来在ARSCNView 实例的session 对象上调用run(_:options:) 方法:

let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = [.horizontal, .vertical]
sceneView.session.run(configuration)

仅当用来展示会话的视图在屏幕上时,才运行会话。

重要

如果你的app需要ARKit作为其核心功能,在你的app的Info.plist文件的UIRequiredDeviceCapabilities 位置使用arkit 键值,来保证你的app只会运行在支持ARKit的设备上。如果AR是你的APP的一个可选特性,使用isSupported 属性来决定是否提供基于AR的特性。

在检测到的平面上放置3D内容

在你设置好你的AR会话之后,你便可以使用SceneKit在视图中放置虚拟内容。

当平面检测被启用之后,ARKit将会为每一个检测到的平面添加和更新锚点。默认情况下,ARSCNView 类为每一个锚点添加一个SCNNode 到SceneKit场景。你的视图的代理可以通过实现renderer(_:didAdd:for:) 方法来添加内容到场景中。当你添加的内容是作为对应锚点的子节点时,ARSCNView将会自动移动该内容,因为ARKit会优化对于平面位置的预估。

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
    // Place content only for anchors found by plane detection.
    // 只为通过平面检测找到的锚点放置内容
    guard let planeAnchor = anchor as? ARPlaneAnchor else { return }

    // Create a custom object to visualize the plane geometry and extent.
    // 创建一个自定义对象来可视化平面的几何形状和范围
    let plane = Plane(anchor: planeAnchor, in: sceneView)

    // Add the visualization to the ARKit-managed node so that it tracks
    // changes in the plane anchor as plane estimation continues.
    // 添加该可视化对象到ARKit管理的节点中,因此ARKit将会追踪它在平面锚点中随着平面预估的继续进行的变化
    node.addChildNode(plane)
}

ARKit提供了追踪预估平面区域的两种方法。一个平面锚点的几何形状描述了一个紧紧包围所有点的凸多边形,该多边形就是ARKIt当前预估的同样平面的一部分(ARSCNPlaneGeometry 可以很容易的将其可视化)。ARKit同样在平面锚点的范围和中心提供了一个简单的预估,一同描述了一个矩形边界(SCNPlane 可以很容易的将其可视化)。

// Create a mesh to visualize the estimated shape of the plane.
// 创建一个网格来可视化平面的预估形状
guard let meshGeometry = ARSCNPlaneGeometry(device: sceneView.device!)
    else { fatalError("Can't create plane geometry") }
meshGeometry.update(from: anchor.geometry)
meshNode = SCNNode(geometry: meshGeometry)

// Create a node to visualize the plane's bounding rectangle.
// 创建一个节点来可视化平面的边界矩形
let extentPlane: SCNPlane = SCNPlane(width: CGFloat(anchor.extent.x), height: CGFloat(anchor.extent.z))
extentNode = SCNNode(geometry: extentPlane)
extentNode.simdPosition = anchor.center

// `SCNPlane` is vertically oriented in its local coordinate space, so
// rotate it to match the orientation of `ARPlaneAnchor`.
// `SCNPlane` 在其坐标系中是垂直方向的,所以需要将其旋转来符合`ARPlaneAnchor`的方向
extentNode.eulerAngles.x = -.pi / 2

ARKit持续更新它对于检测到平面的形状和范围的预估。为了展现当前每个平面预估的形状,这个示例app同样实现了renderer(_:didUpdate:for:) 方法,来更新ARSCNPlaneGeometrySCNPlane 对象来反应ARKit的最新信息。

func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
    // Update only anchors and nodes set up by `renderer(_:didAdd:for:)`.
    // 只更新锚点和在`renderer(_:didAdd:for:)`方法中设置过的节点
    guard let planeAnchor = anchor as? ARPlaneAnchor,
        let plane = node.childNodes.first as? Plane
        else { return }

    // Update ARSCNPlaneGeometry to the anchor's new estimated shape.
    // 更新ARSCNPlaneGeometry到锚点的新评估的形状
    if let planeGeometry = plane.meshNode.geometry as? ARSCNPlaneGeometry {
        planeGeometry.update(from: planeAnchor.geometry)
    }

    // Update extent visualization to the anchor's new bounding rectangle.
    // 更新范围可视化到锚点的新的边界矩形
    if let extentGeometry = plane.extentNode.geometry as? SCNPlane {
        extentGeometry.width = CGFloat(planeAnchor.extent.x)
        extentGeometry.height = CGFloat(planeAnchor.extent.z)
        plane.extentNode.simdPosition = planeAnchor.center
    }

    // Update the plane's classification and the text position
    // 更新平面的分类和文字位置
    if #available(iOS 12.0, *),
        let classificationNode = plane.classificationNode,
        let classificationGeometry = classificationNode.geometry as? SCNText {
        let currentClassification = planeAnchor.classification.description
        if let oldClassification = classificationGeometry.string as? String, oldClassification != currentClassification {
            classificationGeometry.string = currentClassification
            classificationNode.centerAlign()
        }
    }

}

在iPhone XS, iPhone XS Max, 和 iPhone XR上,ARKit可以同样对检测到的平面进行分类,上报平面表示了哪类一般真实世界的表面(例如,一张桌子,地板或者墙)。在这个例子中,the renderer(_:didUpdate:for:) 方法同样展示并更新了一个文本标签来显示该信息。


下面附几张iPhone X 和 iPhone XS Max运行结果

以上两张iPhone X效果,并没有文字标签

以上两张iPhone XS Max效果,墙识别除了Wall,地板识别除了Floor,坐垫未识别出是Unknown

尝试对示例程序作了一些修改,发现要深入学习一下SceneKit了,无底洞啊!