ARKit从入门到精通(3)--平面检测

本部分主要实现通过ARKit在现实环境找到一个plane并放置一个3D物体船。

效果预览:

ARKit从入门到精通(3)--平面检测_第1张图片

ARKit从入门到精通(3)--平面检测_第2张图片

 

在正式开始前,我们需要了解“Horizontal Plane”这一概念。当我们在ARKit中讨论水平面时,我们究竟在讨论什么呢?当我们在ARKit中检测到一个水平面时,我们在技术上检测到的其实是一个ARPlaneAnchor。那么什么是ARPlaneAnchor呢?ARPlaneAnchor大体上可以理解为一个包含检测到的水平面信息的对象(水平与位置信息)。

本部分主要介绍Horizontal Plane的实现方式,所以我们直接在这个工程(链接: https://pan.baidu.com/s/1eJeIbkzk1OUy_YLk6X3yog 提取码: vv6y )上面继续开发,对于如何在Xcode使用ARKit实现AR效果不懂的可以查看之前的文章:

下载完后在Xcode打开,并运行项目,如下图所示:

ARKit从入门到精通(3)--平面检测_第3张图片

 

 

Step 1: 水平面检测

在ViewController的setUpSceneView()方法中添加以下内容:

configuration.planeDetection = .horizontal

通过将ARWorldTrackingConfiguration的planeDetection属性设置为.horizontal,这告诉ARKit去查找任何水平面。一旦ARKit检测到一个水平面,该水平面将被添加到sceneView的session中。

为了检测水平面,必须采用ARSCNViewDelegate协议。在ViewController类下面,创建一个ViewController类扩展来实现协议:

extension ViewController: ARSCNViewDelegate {

}

在类扩展的内部,实现renderer(_:didAdd:for:)方法:

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {

}

每当场景视图的session添加了一个新的ARAnchor时,就会调用这个协议方法。ARAnchor是表示三维空间中物理位置和方向的对象。稍后我们将使用ARAnchor来检测水平面。

将sceneView的委托分配给setUpSceneView()中的ViewController。

还可以设置sceneView的调试选项来显示现实环境中的特征点。这可以帮助找到一个特征点足够多的水平面。一旦检测到足够的特征点来识别水平表面,就会调用renderer(_:didAdd:for:)。

目前setUpSceneView()方法应该是这样的:

func setUpSceneView() {
    let configuration = ARWorldTrackingConfiguration()
    configuration.planeDetection = .horizontal
    
    sceneView.session.run(configuration)
    
    sceneView.delegate = self
    sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints]
}

更新renderer(_:didAdd:for:)方法:

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
    // 1
    guard let planeAnchor = anchor as? ARPlaneAnchor else { return }
    
    // 2
    let width = CGFloat(planeAnchor.extent.x)
    let height = CGFloat(planeAnchor.extent.z)
    let plane = SCNPlane(width: width, height: height)
    
    // 3
    plane.materials.first?.diffuse.contents = UIColor.transparentLightBlue
    
    // 4
    let planeNode = SCNNode(geometry: plane)
    
    // 5
    let x = CGFloat(planeAnchor.center.x)
    let y = CGFloat(planeAnchor.center.y)
    let z = CGFloat(planeAnchor.center.z)
    planeNode.position = SCNVector3(x,y,z)
    planeNode.eulerAngles.x = -.pi / 2
    
    // 6
    node.addChildNode(planeNode)
}

创建一个SCNPlane来可视化ARPlaneAnchor。SCNPlane是一个矩形的“单边”平面几何。我们获取未包装的ARPlaneAnchor区段的x和z属性,并使用它们创建一个SCNPlane。

使用刚刚创建的SCNPlane几何图形初始化一个SCNNode。

初始化x、y和z常量来表示planeAnchor的中心x、y和z位置。这是我们的planeNode的位置。

最后,将planeNode作为子节点添加到新添加的SceneKit节点上。

然后Build项目,效果如下图:

ARKit从入门到精通(3)--平面检测_第4张图片

随着ARKit接收到关于我们的环境的额外信息,我们需要扩展之前检测到的水平面,以利用更大的表面,或者用新的环境信息更准确地表示,实现方法renderer(_:didUpdate:for:):

func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {

}

每次更新SceneKit节点的属性以匹配其对应的锚(anchor)时,都会调用此方法。

node参数为我们提供了锚的更新位置。锚参数给出了更新后的宽度和高度。有了这两个参数,我们可以更新之前实现的SCNPlane,用更新后的宽度和高度反映新的位置。

接下来,在renderer(_:didUpdate:for:)中添加以下代码:

// 1
guard let planeAnchor = anchor as?  ARPlaneAnchor,
    let planeNode = node.childNodes.first,
    let plane = planeNode.geometry as? SCNPlane
    else { return }

// 2
let width = CGFloat(planeAnchor.extent.x)
let height = CGFloat(planeAnchor.extent.z)
plane.width = width
plane.height = height

// 3
let x = CGFloat(planeAnchor.center.x)
let y = CGFloat(planeAnchor.center.y)
let z = CGFloat(planeAnchor.center.z)
planeNode.position = SCNVector3(x, y, z)

Build应用,实现效果如下图:

Step 2: 添加3D物体

在本部分教程刚开始的时候下载的project包含了一个3D物体(ship)。

在ViewController类中插入以下方法,将船放置在水平面的顶部:

@objc func addShipToSceneView(withGestureRecognizer recognizer: UIGestureRecognizer) {
    let tapLocation = recognizer.location(in: sceneView)
    let hitTestResults = sceneView.hitTest(tapLocation, types: .existingPlaneUsingExtent)
    
    guard let hitTestResult = hitTestResults.first else { return }
    let translation = hitTestResult.worldTransform.translation
    let x = translation.x
    let y = translation.y
    let z = translation.z
    
    guard let shipScene = SCNScene(named: "ship.scn"),
        let shipNode = shipScene.rootNode.childNode(withName: "ship", recursively: false)
        else { return }
    
    
    shipNode.position = SCNVector3(x,y,z)
    sceneView.scene.rootNode.addChildNode(shipNode)
}

对于这部分内容不熟悉的可以查看之前的文章:

这里与之前唯一的区别是,我们在types参数中传递了一个不同的参数来检测sceneView中现有的平面锚。

func addTapGestureToSceneView() {
    let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(ViewController.addShipToSceneView(withGestureRecognizer:)))
    sceneView.addGestureRecognizer(tapGestureRecognizer)
}

该方法将向sceneView添加一个tap手势识别器。

对于顶部的cherry,调用viewDidLoad()内部方法,向sceneView添加一个tap手势识别器:

addTapGestureToSceneView()

现在运行,你应该能够检测到一个可视化的水平面,然后上面会放置一个小船,你可以任意角度查看它。效果如下图:

可以通过取消viewDidLoad()内部的configureLighting()注释来启用照明。该功能非常简单,用两行代码来启用照明:


sceneView.autoenablesDefaultLighting = true
sceneView.automaticallyUpdatesLighting = true

完整项目链接:https://github.com/appcoda/ARKitHorizontalPlaneDemo

参考链接:https://www.appcoda.com/arkit-horizontal-plane/

 

------AR Portal(AR开发者社区)整理

关注微信公众号(AR开发者交流社区,提供AR开发干货,推动AR内容发展):AR开发者社区

你可能感兴趣的:(AR开发,ARKit)