示例代码

呈现重建的场景并与场景中的内容互动

使用多边形网格估计物理环境的形状。

下载 (英文)

概览

在运行 iPadOS 13.4 或更高版本的第四代 iPad Pro 上,ARKit 利用激光雷达扫描仪来创建物理环境的多边形模型。激光雷达扫描仪会快速地从用户面前的广阔区域中获取深度信息,这样无需用户移动,ARKit 即可估计现实世界的形状。ARKit 会将深度信息转换为一系列顶点,这些顶点连接在一起形成“网格”。为了进行信息分区,ARKit 会创建多个锚点,并将每个锚点指定给网格的唯一部分。总的来说,网格锚点代表着用户周围现实世界中的“场景”

通过这些网格,您可以:

  • 更准确地定位现实中表面上的点。

  • 对 ARKit 能够识别的现实世界中的物体进行分类。

  • 以现实世界中的物体遮挡 app 的虚拟内容。

  • 让虚拟内容能够以逼真的方式与物理环境进行互动,例如,使虚拟的圆球在撞击现实世界中的墙壁后遵循物理定律弹开。

此示例 app 使用 RealityKit 来呈现增强现实体验。下图展示了 RealityKit 如何利用 ARKit 提供的现实世界信息,并在您运行此 app 并将设备指向现实世界中的椅子时创建调试可视化。

截屏显示了摄像头源中显示的椅子,其中包含由 RealityKit 呈现的网格重叠内容。

呈现物理环境的形状

为启用场景网格,该示例将世界配置的 sceneReconstruction (英文) 属性设置为其中一个网格选项。

arView.automaticallyConfigureSession = false
let configuration = ARWorldTrackingConfiguration()
configuration.sceneReconstruction = .meshWithClassification

该示例使用 RealityKit 的 ARView (英文) 功能来渲染其图形。为了在运行时呈现网格,ARView (英文) 提供了 sceneUnderstanding (英文) 调试选项。

arView.debugOptions.insert(.showSceneUnderstanding)

在该示例中,app 首次启动时,会在主视图控制器的 viewDidLoad 回调中配置并运行会话来开始增强现实体验。

arView.session.run(configuration)

添加平面检测

当 app 通过场景重建实现平面检测时,ARKit 会在制作网格时考虑这些信息。在某些位置,激光雷达扫描仪可能会在现实世界中的表面上产生一个轻微不平整的网格,这时如果 ARKit 在这个表面上检测到平面,它便会对网格进行平滑处理。

为了演示平面检测对网格的影响,这个 app 显示了一个切换按钮。在按钮处理程序中,该示例会调整平面检测配置并重新启动会话来使更改生效。

@IBAction func togglePlaneDetectionButtonPressed(_ button: UIButton) {
    guard let configuration = arView.session.configuration as? ARWorldTrackingConfiguration else {
        return
    }
    if configuration.planeDetection == [] {
        configuration.planeDetection = [.horizontal, .vertical]
        button.setTitle("Stop Plane Detection", for: [])
    } else {
        configuration.planeDetection = []
        button.setTitle("Start Plane Detection", for: [])
    }
    arView.session.run(configuration)
}

定位物体表面上的点

使用网格检索表面位置的 app 可以获得前所未有的精度。通过考虑网格,光线投射可以与非平面表面相交,或与特征较少或没有特征的表面相交,如白色墙壁。

为了演示准确的光线投射效果,这个 app 会在用户轻点屏幕时投射光线。该示例中指定了 ARRaycastQuery.Target.estimatedPlane (英文) 允许的目标和 ARRaycastQuery.TargetAlignment.any (英文) 对齐选项,这是在网格化的现实世界中物体上检索一个点所必需的。

let tapLocation = sender.location(in: arView)
if let result = arView.raycast(from: tapLocation, allowing: .estimatedPlane, alignment: .any).first {
    // ...

图片显示了一束光线与现实世界中的花瓶表面相交。

当用户的光线投射返回结果时,这个 app 通过在交点处放置一个小球体来提供视觉反馈。

let resultAnchor = AnchorEntity(world: result.worldTransform)
resultAnchor.addChild(sphere(radius: 0.01, color: .lightGray))
arView.scene.addAnchor(resultAnchor, removeAfter: 3)

截屏显示了网格表面上与用户的光线投射相交的位置上放置有虚拟球体。

对现实世界中的物体进行分类

ARKit 具有分类功能,可以分析现实世界的网格模型,从而识别现实世界中的特定物体。在网格中,ARKit 可以对地板、桌子、座椅、窗户和天花板进行分类。请参阅 ARMeshClassification (英文) 来查看完整的列表。

如果用户轻点屏幕,并且光线投射与某个网格化的现实世界中物体相交,这个 app 会显示文本来指示该网格物体的分类。

图片显示了一个网格化的现实世界中花瓶,其中带有显示其分类的标签。

ARView (英文)automaticallyConfigureSession (英文) 属性为 true 时,RealityKit 默认会停用分类功能,因为该功能不是遮挡和物理特效所必需的。为了启用网格分类,该示例通过将 sceneReconstruction (英文) 属性设置为 meshWithClassification (英文) 来覆盖默认设置。

arView.automaticallyConfigureSession = false
let configuration = ARWorldTrackingConfiguration()
configuration.sceneReconstruction = .meshWithClassification

这个 app 会尝试从网格中检索交点的分类。

nearbyFaceWithClassification(to: result.worldTransform.position) { (centerOfFace, classification) in
    // ...

网格中的每三个顶点构成一个三角形,称为一个“面”。ARKit 会为每个面分配一个分类,因此该示例会在网格内搜索交点附近的面。如果那个面有分类,这个 app 会在屏幕上显示这个分类。由于这个常规操作涉及大量的处理,因此该示例采用异步方式来完成相关工作,这样渲染器便不会卡顿。

DispatchQueue.global().async {
    for anchor in meshAnchors {
        for index in 0..<anchor.geometry.faces.count {
            // Get the center of the face so that we can compare it to the given location.
            let geometricCenterOfFace = anchor.geometry.centerOf(faceWithIndex: index)
            
            // Convert the face's center to world coordinates.
            var centerLocalTransform = matrix_identity_float4x4
            centerLocalTransform.columns.3 = SIMD4<Float>(geometricCenterOfFace.0, geometricCenterOfFace.1, geometricCenterOfFace.2, 1)
            let centerWorldPosition = (anchor.transform * centerLocalTransform).position
             
            // We're interested in a classification that is sufficiently close to the given location––within 5 cm.
            let distanceToFace = distance(centerWorldPosition, location)
            if distanceToFace <= 0.05 {
                // Get the semantic classification of the face and finish the search.
                let classification: ARMeshClassification = anchor.geometry.classificationOf(faceWithIndex: index)
                completionBlock(centerWorldPosition, classification)
                return
            }
        }
    }

获得分类信息后,该示例将创建 3D 文本来显示分类。

let textEntity = self.model(for: classification)

为了防止网格部分遮挡文本,该示例稍微偏移了文本,使文本更加清晰易读。该示例会计算光线在负方向的偏移量,从而有效地将文本稍微朝着摄像头向远离表面的方向移动。

let rayDirection = normalize(result.worldTransform.position - self.arView.cameraTransform.translation)
let textPositionInWorldCoordinates = result.worldTransform.position - (rayDirection * 0.1)

为了使文本始终以相同的大小显示在屏幕上,该示例采用了一个基于文本到摄像头距离的比例。

let raycastDistance = distance(result.worldTransform.position, self.arView.cameraTransform.translation)
textEntity.scale = .one * raycastDistance

为了显示该文本,该示例将其放置在调整后交点处的锚定实体中,朝向摄像头。

var resultWithCameraOrientation = self.arView.cameraTransform
resultWithCameraOrientation.translation = textPositionInWorldCoordinates
let textAnchor = AnchorEntity(world: resultWithCameraOrientation.matrix)
textAnchor.addChild(textEntity)
self.arView.scene.addAnchor(textAnchor, removeAfter: 3)

为了呈现检索到分类的面所对应的顶点位置,该示例在该顶点对应的现实世界位置处创建了一个小球体。

if let centerOfFace = centerOfFace {
    let faceAnchor = AnchorEntity(world: centerOfFace)
    faceAnchor.addChild(self.sphere(radius: 0.01, color: classification.color))
    self.arView.scene.addAnchor(faceAnchor, removeAfter: 3)
}

截屏显示了标识分类网格来源的虚拟球体。

用网格遮挡虚拟内容

“遮挡”是指从摄像头的角度,以现实世界的一部分盖住 app 内虚拟内容的功能。为了实现这种错觉,RealityKit 会检查用户所查看的虚拟内容前面有没有任何网格,并忽略绘制被这些网格遮挡的任何虚拟内容部分。该示例通过将 occlusion (英文) 选项添加到环境的 sceneUnderstanding (英文) 属性中来启用遮挡。

arView.environment.sceneUnderstanding.options.insert(.occlusion)

在运行时,这个 app 会忽略绘制被任何网格化现实世界部分挡住的虚拟文本部分。

截屏显示了现实世界中的桌子遮挡住 app 放置的虚拟物体。

使用物理特效与现实世界中的物体互动

借助场景网格,虚拟内容能够以逼真的方式与物理环境进行互动,因为网格为 RealityKit 的物理引擎提供了一个精确的现实世界模型。该示例通过将 physics (英文) 选项添加到环境的 sceneUnderstanding (英文) 属性中来启用物理特效。

arView.environment.sceneUnderstanding.options.insert(.physics)

为了检测虚拟内容何时与网格化的现实世界中物体接触,该示例使用 addAnchor(_:,removeAfter:) Scene (英文) 扩展中的碰撞形状来定义文本的比例。

if model.collision == nil {
    model.generateCollisionShapes(recursive: true)
    model.physicsBody = .init()
}

当这个 app 对物体进行分类并显示一些文本时,它会等待三秒钟,然后投下虚拟文本。当该示例将文本的 physicsBody (英文)mode (英文) 设置为 PhysicsBodyMode.dynamic (英文) 时,文本会显示重力下落效果。

Timer.scheduledTimer(withTimeInterval: seconds, repeats: false) { (timer) in
    model.physicsBody?.mode = .dynamic
}

随着文本下落,当它与网格化的现实世界中物体碰撞时,比如落到地板上,它会做出反应。

截屏显示了一个虚拟球体从现实世界中的篮筐内落下。

另请参阅

追踪表面并与它们互动

追踪和呈现平面 (英文)

检测物理环境中的表面,并在 3D 空间内呈现它们的形状和位置。

class ARPlaneAnchor (英文)

ARKit 在物理环境中检测到的 2D 表面。

class ARMeshAnchor (英文)

重建场景网格的一部分。

光线投射和命中测试 (英文)

在给定屏幕位置时查找现实世界中表面上的点。