前言
對ARKit感興趣的同學(xué),可以訂閱ARKit教程專題
源代碼地址在這里
正文
在接下來的四章中,我們將使用 ARKit 和 SceneKit 實現(xiàn)門戶應(yīng)用。門戶應(yīng)用程序可用于教育目的,如從太空到太陽系的虛擬游覽,或用于更悠閑的活動,如享受虛擬海灘度假。
門戶應(yīng)用
我們將構(gòu)建的門戶應(yīng)用可讓我們將虛擬的門口放置在未來房間的某處,位于現(xiàn)實世界中的水平平面上。我們可以進出這個虛擬房間,還可以探索里面的東西。
在本章中,我們將設(shè)置門戶應(yīng)用的基礎(chǔ)知識。在本章結(jié)束時,我們將了解到:
- 如何設(shè)置一個ARSession。
- 如何使用 ARKit 檢測和渲染水平平面。
開始
我們需要重新創(chuàng)建一個應(yīng)用,我們打開Main.storyboard,作如下設(shè)置:

@IBOutlet var sceneView: ARSCNView!
@IBOutlet weak var messageLabel: ARLabel!
@IBOutlet weak var sessionStateLabel: ARLabel!
上面三個對象含義如下:
- 1: sceneView用于使用 3D SceneKit 對象增強攝像機視圖。
- 2: messageLabel將向用戶顯示說明性消息。例如,告訴他們?nèi)绾闻c你的應(yīng)用進行交互。
- 3: sessionStateLabel將通知用戶會話中斷,例如應(yīng)用進入后臺或環(huán)境照明不足的時候。
注意:ARKit 處理所有傳感器和攝像機數(shù)據(jù),但不會呈現(xiàn)任何虛擬內(nèi)容。要渲染場景中的內(nèi)容,可以與 ARKit 一起使用各種渲染器,例如 SceneKit 或 SpriteKit。有關(guān)詳細(xì)信息,請參閱第 1 章。
ARSCNView 是 Apple 提供的一個框架,你可以使用該框架將 ARKit 數(shù)據(jù)與 SceneKit 輕松集成。使用 ARSCNView 有很多好處,這就是為什么你將在本章的項目中使用它的原因。有關(guān)詳細(xì)信息,請參閱第 2 章。
設(shè)置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,因為它只考慮設(shè)備的旋轉(zhuǎn),而不是設(shè)備的位置。對于使用 A9 處理器的設(shè)備, ARWorldTrackingSessionConfiguration可提供最佳結(jié)果,因為它可跟蹤設(shè)備的所有運動程度。2: configuration.planeDetection設(shè)置為檢測水平平面。平面的范圍可以更改,并且當(dāng)攝像機移動時,多個平面可以合并到一個平面中。它可以在任何水平表面上找到平面,如地板、桌子或沙發(fā)。
3: 這支持光估計計算,渲染框架可以使用該計算使虛擬內(nèi)容看起來更逼真。
4:使用指定的會話配置啟動會話的 AR 處理。這將從攝像機開始 ARKit 會話和視頻捕獲,該攝像機顯示在sceneView中。
5:對于調(diào)試生成,這將添加可見的功能點;這些被疊加在攝像機視圖上。
現(xiàn)在我們設(shè)置一下默認(rèn)值。將重置標(biāo)簽替換為以下內(nèi)容:
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的不統(tǒng)一性和文本。請記住, messageLabel用于向用戶顯示說明信息,而sessionStateLabel用于顯示任何錯誤消息,以防出現(xiàn)問題。在viewDidLoad()中添加如下代碼
override func viewDidLoad() {
super.viewDidLoad()
resetLabels()
runSession()
}
這將在應(yīng)用啟動并加載視圖時運行 ARKit 會話。
接下來,構(gòu)建并運行應(yīng)用。需要注意的是,我們需要向應(yīng)用請求相機權(quán)限。
ARSCNView 執(zhí)行顯示攝像機視頻捕獲的繁重工作。由于我們處于調(diào)試模式,因此還可以看到渲染要素點,這些要素點形成點云,顯示場景分析的中間結(jié)果。
平面檢測和渲染
以前,在 runSession() 中,將planeDetection設(shè)置為.horizontal,這意味著你的應(yīng)用可以檢測水平平面。我們可以在 ARSCNViewDelegate 協(xié)議的委托回調(diào)方法中獲取捕獲的平面信息。
首先做一個擴展,以便實現(xiàn) ARSCNViewDelegate 協(xié)議:
extension PortalViewController: ARSCNViewDelegate {
}
在runSession()函數(shù)中添加如下代碼:
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:當(dāng) ARSession 檢測到新平面時,將調(diào)用委托方法renderer(_:didAdd:for:),并且 ARSCNView 會自動為平面添加 ARPlaneAnchor。
- 2: 回調(diào)發(fā)生在后臺線程上。在這里,將block調(diào)度到主隊列,因為更新 UI 的任何操作都應(yīng)在主 UI 線程上完成。
- 3: 這將檢查添加的 ARAnchor 是否是 ARPlaneAnchor。
- 4: 這將檢查是否處于調(diào)試模式。
- 5: 如果是這樣,通過通過ARKit檢測到的平面錨點的中心和范圍坐標(biāo)來創(chuàng)建平面SCNNode對象。createPlaneNode() 后面會有具體實現(xiàn)的。
- 6: 節(jié)點對象是 ARSCNView 自動添加到場景中的空 SCNNode;其坐標(biāo)對應(yīng)于 ARAnchor 的位置。在這里,將調(diào)試平面節(jié)點添加為子節(jié)點,以便將其放置在與節(jié)點相同的位置。
- 7: 最后,無論你是否處于調(diào)試模式,你都會向用戶更新說明消息,以指示應(yīng)用現(xiàn)在可以將門戶放入場景中。
我們需要引入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 方法有兩個參數(shù):要渲染的平面的center和extent,兩者都是vector_float3類型。此類型表示點的坐標(biāo)。該函數(shù)返回為平面創(chuàng)建的 SCNNode對象。
- 2: 通過指定平面的寬度和高度來實例化 SCNPlane。從范圍的X坐標(biāo)獲取寬度,從其Z坐標(biāo)獲取高度。
- 3: 初始化并分配 SCNMaterial對象的漫反射內(nèi)容。漫反射圖層顏色設(shè)置為半透明黃色。
- 4: 然后, SCNMaterial對象將添加到平面的材料數(shù)組中。這將定義平面的紋理和顏色。
- 5: 這將創(chuàng)建具有平面幾何體的 SCNNode。SCNPlane 繼承自 SCNGeometry,該類僅提供 SceneKit 呈現(xiàn)的可見對象的形式。通過將幾何體附加到 SCNNode 對象來指定幾何體的位置和方向。多個節(jié)點可以引用同一幾何對象,允許其顯示在場景中的不同位置。
- 6: 設(shè)置平面節(jié)點的位置。請注意,該節(jié)點將轉(zhuǎn)換為 ARKit 通過 ARPlaneAnchor 實例報告的坐標(biāo)(center.x,0,center.z)。
- 7: 默認(rèn)情況下, SceneKit 中的平面是垂直的,因此我們需要將平面旋轉(zhuǎn) 90 度才能使其水平。
- 8:這將返回在前面的步驟中創(chuàng)建的平面節(jié)點對象。
編譯運行,效果如下:

移動設(shè)備, 會顯示多個平面。當(dāng)它找到更多的平面時,它會將它們添加到視圖中。但是,由于 ARKit 分析場景中的更多要素,現(xiàn)有平面不會更新或更改大小。

ARKit 會根據(jù)找到的新特征點不斷更新平面的位置和范圍。要在應(yīng)用中接收這些更新,請?zhí)砑右韵?strong>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:)當(dāng)相應(yīng)的 ARAnchor 更新時被調(diào)用。
- 2: 更新 UI 的操作應(yīng)在主 UI 線程上執(zhí)行。
- 3: 檢查 ARAnchor 是否為 ARPlaneAnchor,并確保它至少有一個子節(jié)點對應(yīng)于平面的 SCNNode。
- 4: updatePlaneNode(_:center:extent:)將平面的坐標(biāo)和大小更新為 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: 檢查節(jié)點是否具有 SCNPlane 幾何體。
- 2: 使用傳入的新值更新節(jié)點幾何體。使用 ARPlaneAnchor的范圍或大小來更新平面的寬度和高度。
- 3: 使用新位置更新平面節(jié)點的位置。
現(xiàn)在,我們可以成功更新平面的位置,生成并運行應(yīng)用。我們將看到,當(dāng)平面檢測到新特征點時,其尺寸和位置會發(fā)生變化。
還有一個問題需要解決。應(yīng)用檢測到平面后,如果退出應(yīng)用并返回,我們可以看到以前檢測到的平面現(xiàn)在位于攝像機視圖中的其他對象之上;如果退出應(yīng)用并返回,將看到以前檢測到的平面現(xiàn)在位于攝像機視圖中的其他對象之上。它不再與之前檢測到的平面表面匹配。

要解決此問題,我們需要在 ARSession 中斷時刪除平面節(jié)點。這個工作將在下一章中處理。
| 上一章 | 目錄 | 下一章 |
|---|