前言
對ARKit感興趣的同學,可以訂閱ARKit教程專題
源代碼地址在這里
正文
在接下來的四章中,我們將使用 ARKit 和 SceneKit 實現門戶應用。門戶應用程序可用于教育目的,如從太空到太陽系的虛擬游覽,或用于更悠閑的活動,如享受虛擬海灘度假。
門戶應用
我們將構建的門戶應用可讓我們將虛擬的門口放置在未來房間的某處,位于現實世界中的水平平面上。我們可以進出這個虛擬房間,還可以探索里面的東西。
在本章中,我們將設置門戶應用的基礎知識。在本章結束時,我們將了解到:
- 如何設置一個ARSession。
- 如何使用 ARKit 檢測和渲染水平平面。
開始
我們需要重新創建一個應用,我們打開Main.storyboard,作如下設置:
@IBOutlet var sceneView: ARSCNView!
@IBOutlet weak var messageLabel: ARLabel!
@IBOutlet weak var sessionStateLabel: ARLabel!
上面三個對象含義如下:
- 1: sceneView用于使用 3D SceneKit 對象增強攝像機視圖。
- 2: messageLabel將向用戶顯示說明性消息。例如,告訴他們如何與你的應用進行交互。
- 3: sessionStateLabel將通知用戶會話中斷,例如應用進入后臺或環境照明不足的時候。
注意:ARKit 處理所有傳感器和攝像機數據,但不會呈現任何虛擬內容。要渲染場景中的內容,可以與 ARKit 一起使用各種渲染器,例如 SceneKit 或 SpriteKit。有關詳細信息,請參閱第 1 章。
ARSCNView 是 Apple 提供的一個框架,你可以使用該框架將 ARKit 數據與 SceneKit 輕松集成。使用 ARSCNView 有很多好處,這就是為什么你將在本章的項目中使用它的原因。有關詳細信息,請參閱第 2 章。
設置ARKit
func runSession(){
let configuation = ARWorldTrackingConfiguration.init()
configuation.planeDetection = .horizontal
configuation.isLightEstimationEnabled = true
sceneView.session.run(configuation)
#if DEBUG
sceneView.debugOptions = [SCNDebugOptions.showFeaturePoints]
#endif
sceneView.delegate = self
}
上面的代碼作用如下:
1: 實例化了ARWorldTrackingConfiguration對象。這將定義 ARSession 的配置。有兩種類型的配置可用于 ARSession: ARSessionConfiguration和 ARWorldTrackingConfiguration。
不建議使用 ARSessionConfiguration,因為它只考慮設備的旋轉,而不是設備的位置。對于使用 A9 處理器的設備, ARWorldTrackingSessionConfiguration可提供最佳結果,因為它可跟蹤設備的所有運動程度。2: configuration.planeDetection設置為檢測水平平面。平面的范圍可以更改,并且當攝像機移動時,多個平面可以合并到一個平面中。它可以在任何水平表面上找到平面,如地板、桌子或沙發。
3: 這支持光估計計算,渲染框架可以使用該計算使虛擬內容看起來更逼真。
4:使用指定的會話配置啟動會話的 AR 處理。這將從攝像機開始 ARKit 會話和視頻捕獲,該攝像機顯示在sceneView中。
5:對于調試生成,這將添加可見的功能點;這些被疊加在攝像機視圖上。
現在我們設置一下默認值。將重置標簽替換為以下內容:
func resetLabels(){
messageLabel.alpha = 1.0
messageLabel.text = "Move the phone around and allow the app to find a plane. You will see a yellow horizontal plane."
sessionStateLabel.alpha = 0.0
sessionStateLabel.text = ""
}
這將重置messageLabel和sessionStateLabel的不統一性和文本。請記住, messageLabel用于向用戶顯示說明信息,而sessionStateLabel用于顯示任何錯誤消息,以防出現問題。在viewDidLoad()中添加如下代碼
override func viewDidLoad() {
super.viewDidLoad()
resetLabels()
runSession()
}
這將在應用啟動并加載視圖時運行 ARKit 會話。
接下來,構建并運行應用。需要注意的是,我們需要向應用請求相機權限。
ARSCNView 執行顯示攝像機視頻捕獲的繁重工作。由于我們處于調試模式,因此還可以看到渲染要素點,這些要素點形成點云,顯示場景分析的中間結果。
平面檢測和渲染
以前,在 runSession() 中,將planeDetection設置為.horizontal,這意味著你的應用可以檢測水平平面。我們可以在 ARSCNViewDelegate 協議的委托回調方法中獲取捕獲的平面信息。
首先做一個擴展,以便實現 ARSCNViewDelegate 協議:
extension PortalViewController: ARSCNViewDelegate {
}
在runSession()函數中添加如下代碼:
sceneView?.delegate = self
接下來,我們需要添加一些代理方法:
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor){
DispatchQueue.main.async {
if let planeAnchor = anchor as? ARPlaneAnchor{
#if DEBUG
let debugPlaneNode = createPlaneNode(center: planeAnchor.center, extent: planeAnchor.extent)
node.addChildNode(debugPlaneNode)
#endif
self.messageLabel.alpha = 1.0
self.messageLabel.text = "Tap on the detected horizontal plane to place the portal"
}
}
}
上面的代碼作用如下:
- 1:當 ARSession 檢測到新平面時,將調用委托方法renderer(_:didAdd:for:),并且 ARSCNView 會自動為平面添加 ARPlaneAnchor。
- 2: 回調發生在后臺線程上。在這里,將block調度到主隊列,因為更新 UI 的任何操作都應在主 UI 線程上完成。
- 3: 這將檢查添加的 ARAnchor 是否是 ARPlaneAnchor。
- 4: 這將檢查是否處于調試模式。
- 5: 如果是這樣,通過通過ARKit檢測到的平面錨點的中心和范圍坐標來創建平面SCNNode對象。createPlaneNode() 后面會有具體實現的。
- 6: 節點對象是 ARSCNView 自動添加到場景中的空 SCNNode;其坐標對應于 ARAnchor 的位置。在這里,將調試平面節點添加為子節點,以便將其放置在與節點相同的位置。
- 7: 最后,無論你是否處于調試模式,你都會向用戶更新說明消息,以指示應用現在可以將門戶放入場景中。
我們需要引入SceneKit:
import SceneKit
在func createPlaneNode(center: vector_float3, extent: vector_float3) -> SCNNode添加如下代碼:
// 1
func createPlaneNode(center: vector_float3, extent: vector_float3) -> SCNNode {
// 2
let plane = SCNPlane(width: CGFloat(extent.x), height: CGFloat(extent.z))
// 3
let planeMaterial = SCNMaterial() planeMaterial.diffuse.contents = UIColor.yellow.withAlphaComponent(0.4)
// 4
plane.materials = [planeMaterial]
// 5
let planeNode = SCNNode(geometry: plane)
// 6
planeNode.position = SCNVector3Make(center.x, 0, center.z)
// 7
planeNode.transform = SCNMatrix4MakeRotation(-Float.pi / 2, 1, 0, 0)
// 8
return planeNode
}
上面代碼作用如下:
- 1: createPlaneNode 方法有兩個參數:要渲染的平面的center和extent,兩者都是vector_float3類型。此類型表示點的坐標。該函數返回為平面創建的 SCNNode對象。
- 2: 通過指定平面的寬度和高度來實例化 SCNPlane。從范圍的X坐標獲取寬度,從其Z坐標獲取高度。
- 3: 初始化并分配 SCNMaterial對象的漫反射內容。漫反射圖層顏色設置為半透明黃色。
- 4: 然后, SCNMaterial對象將添加到平面的材料數組中。這將定義平面的紋理和顏色。
- 5: 這將創建具有平面幾何體的 SCNNode。SCNPlane 繼承自 SCNGeometry,該類僅提供 SceneKit 呈現的可見對象的形式。通過將幾何體附加到 SCNNode 對象來指定幾何體的位置和方向。多個節點可以引用同一幾何對象,允許其顯示在場景中的不同位置。
- 6: 設置平面節點的位置。請注意,該節點將轉換為 ARKit 通過 ARPlaneAnchor 實例報告的坐標(center.x,0,center.z)。
- 7: 默認情況下, SceneKit 中的平面是垂直的,因此我們需要將平面旋轉 90 度才能使其水平。
- 8:這將返回在前面的步驟中創建的平面節點對象。
編譯運行,效果如下:
移動設備, 會顯示多個平面。當它找到更多的平面時,它會將它們添加到視圖中。但是,由于 ARKit 分析場景中的更多要素,現有平面不會更新或更改大小。
ARKit 會根據找到的新特征點不斷更新平面的位置和范圍。要在應用中接收這些更新,請添加以下renderer(_:didUpdate:for:)代理方法。
func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor){
DispatchQueue.main.async {
if let planeAnchor = anchor as? ARPlaneAnchor, !node.childNodes.isEmpty{
updatePlaneNode(node.childNodes[0], center: planeAnchor.center, extent: planeAnchor.extent)
}
}
}
以上代碼作用如下:
- 1: renderer(_:didUpdate:for:)當相應的 ARAnchor 更新時被調用。
- 2: 更新 UI 的操作應在主 UI 線程上執行。
- 3: 檢查 ARAnchor 是否為 ARPlaneAnchor,并確保它至少有一個子節點對應于平面的 SCNNode。
- 4: updatePlaneNode(_:center:extent:)將平面的坐標和大小更新為 ARPlaneAnchor 中包含的最新的值。
我們打開SCNNodeHelpers.swift文件,添加如下代碼:
func updatePlaneNode(_ node: SCNNode, center: vector_float3, extent: vector_float3){
let geometry = node.geometry as? SCNPlane
geometry?.width = CGFloat(extent.x)
geometry?.height = CGFloat(extent.z)
node.position = SCNVector3Make(center.x, 0, center.z)
}
以上代碼作用如下:
- 1: 檢查節點是否具有 SCNPlane 幾何體。
- 2: 使用傳入的新值更新節點幾何體。使用 ARPlaneAnchor的范圍或大小來更新平面的寬度和高度。
- 3: 使用新位置更新平面節點的位置。
現在,我們可以成功更新平面的位置,生成并運行應用。我們將看到,當平面檢測到新特征點時,其尺寸和位置會發生變化。
還有一個問題需要解決。應用檢測到平面后,如果退出應用并返回,我們可以看到以前檢測到的平面現在位于攝像機視圖中的其他對象之上;如果退出應用并返回,將看到以前檢測到的平面現在位于攝像機視圖中的其他對象之上。它不再與之前檢測到的平面表面匹配。
要解決此問題,我們需要在 ARSession 中斷時刪除平面節點。這個工作將在下一章中處理。
上一章 | 目錄 | 下一章 |
---|