ARKit教程10_第七章:建立一個門戶應用

前言

ARKit感興趣的同學,可以訂閱ARKit教程專題
源代碼地址在這里

正文

在接下來的四章中,我們將使用 ARKitSceneKit 實現門戶應用。門戶應用程序可用于教育目的,如從太空到太陽系的虛擬游覽,或用于更悠閑的活動,如享受虛擬海灘度假。

門戶應用

我們將構建的門戶應用可讓我們將虛擬的門口放置在未來房間的某處,位于現實世界中的水平平面上。我們可以進出這個虛擬房間,還可以探索里面的東西。

在本章中,我們將設置門戶應用的基礎知識。在本章結束時,我們將了解到:

  • 如何設置一個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 一起使用各種渲染器,例如 SceneKitSpriteKit。有關詳細信息,請參閱第 1 章。

ARSCNViewApple 提供的一個框架,你可以使用該框架將 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: ARSessionConfigurationARWorldTrackingConfiguration

    不建議使用 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 = ""
}

這將重置messageLabelsessionStateLabel的不統一性和文本。請記住, 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 方法有兩個參數:要渲染的平面的centerextent,兩者都是vector_float3類型。此類型表示點的坐標。該函數返回為平面創建的 SCNNode對象。
  • 2: 通過指定平面的寬度和高度來實例化 SCNPlane。從范圍的X坐標獲取寬度,從其Z坐標獲取高度。
  • 3: 初始化并分配 SCNMaterial對象的漫反射內容。漫反射圖層顏色設置為半透明黃色。
  • 4: 然后, SCNMaterial對象將添加到平面的材料數組中。這將定義平面的紋理和顏色。
  • 5: 這將創建具有平面幾何體的 SCNNodeSCNPlane 繼承自 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 中斷時刪除平面節點。這個工作將在下一章中處理。

上一章 目錄 下一章
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,247評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,520評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,362評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,805評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,541評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,896評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,062評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,608評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,356評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,555評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,769評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,175評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,489評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,289評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,516評論 2 379