馬上注冊,結交更多好友,享用更多功能,讓你輕松玩轉社區。
您需要 登錄 才可以下載或查看,沒有帳號?注冊帳號
x
本帖最后由 geekli 于 2019-10-21 21:02 編輯
本部分主要通過ARKit實現平面檢測并在其上面放置一個虛擬物體。
效果預覽:
在正式開始前,我們需要了解“Horizontal Plane”這一概念。當我們在ARKit中討論水平面時,我們究竟在討論什么呢?當我們在ARKit中檢測到一個水平面時,我們在技術上檢測到的其實是一個ARPlaneAnchor。那么什么是ARPlaneAnchor呢?ARPlaneAnchor大體上可以理解為一個包含檢測到的水平面信息的對象(水平與位置信息)。
本部分主要介紹Horizontal Plane的實現方式,所以我們直接在這個工程(鏈接: https://pan.baidu.com/s/1eJeIbkzk1OUy_YLk6X3yog 提取碼: vv6y )上面繼續開發,對于如何在Xcode使用ARKit實現AR效果不懂的可以查看之前的文章:
下載完后在Xcode打開,并運行項目,如下圖所示:
Step 1: 水平面檢測
在ViewController的setUpSceneView()方法中添加以下內容:
[AppleScript] 純文本查看 復制代碼 configuration.planeDetection = .horizontal
通過將ARWorldTrackingConfiguration的planeDetection屬性設置為.horizontal,這告訴ARKit去查找任何水平面。一旦ARKit檢測到一個水平面,該水平面將被添加到sceneView的session中。
為了檢測水平面,必須采用ARSCNViewDelegate協議。在ViewController類下面,創建一個ViewController類擴展來實現協議: [AppleScript] 純文本查看 復制代碼 extension ViewController: ARSCNViewDelegate {
}
在類擴展的內部,實現renderer(_:didAdd:for:)方法: [AppleScript] 純文本查看 復制代碼 func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
}
每當場景視圖的session添加了一個新的ARAnchor時,就會調用這個協議方法。ARAnchor是表示三維空間中物理位置和方向的對象。稍后我們將使用ARAnchor來檢測水平面。
將sceneView的委托分配給setUpSceneView()中的ViewController。
還可以設置sceneView的調試選項來顯示現實環境中的特征點。這可以幫助找到一個特征點足夠多的水平面。一旦檢測到足夠的特征點來識別水平表面,就會調用renderer(_:didAdd:for:)。
目前setUpSceneView()方法應該是這樣的: [AppleScript] 純文本查看 復制代碼 func setUpSceneView() {
let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = .horizontal
sceneView.session.run(configuration)
sceneView.delegate = self
sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints]
}
更新renderer(_:didAdd:for:)方法: [AppleScript] 純文本查看 復制代碼 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接收到關于我們的環境的額外信息,我們需要擴展之前檢測到的水平面,以利用更大的表面,或者用新的環境信息更準確地表示,實現方法renderer(_:didUpdate:for:): [AppleScript] 純文本查看 復制代碼 func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
}
每次更新SceneKit節點的屬性以匹配其對應的錨(anchor)時,都會調用此方法。
node參數為我們提供了錨的更新位置。錨參數給出了更新后的寬度和高度。有了這兩個參數,我們可以更新之前實現的SCNPlane,用更新后的寬度和高度反映新的位置。
接下來,在renderer(_:didUpdate:for:)中添加以下代碼: [AppleScript] 純文本查看 復制代碼 // 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類中插入以下方法,將船放置在水平面的頂部: [AppleScript] 純文本查看 復制代碼 @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中現有的平面錨。 [AppleScript] 純文本查看 復制代碼 func addTapGestureToSceneView() {
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(ViewController.addShipToSceneView(withGestureRecognizer:)))
sceneView.addGestureRecognizer(tapGestureRecognizer)
}
該方法將向sceneView添加一個tap手勢識別器。
對于頂部的cherry,調用viewDidLoad()內部方法,向sceneView添加一個tap手勢識別器: [AppleScript] 純文本查看 復制代碼 addTapGestureToSceneView()
現在運行,你應該能夠檢測到一個可視化的水平面,然后上面會放置一個小船,你可以任意角度查看它。效果如下圖:
可以通過取消viewDidLoad()內部的configureLighting()注釋來啟用照明。該功能非常簡單,用兩行代碼來啟用照明: [AppleScript] 純文本查看 復制代碼 sceneView.autoenablesDefaultLighting = true
sceneView.automaticallyUpdatesLighting = true
完整項目鏈接:https://github.com/appcoda/ARKitHorizontalPlaneDemo 參考鏈接:https://www.appcoda.com/arkit-horizontal-plane/
------AR Portal(AR開發者社區)整理
關注微信公眾號:AR開發者社區(國內領先的AR開發者交流學習社區和AR內容平臺)
|