ARKit教程10_第七章:建立一個門戶應(yīng)用

前言

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

正文

在接下來的四章中,我們將使用 ARKitSceneKit 實現(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 一起使用各種渲染器,例如 SceneKitSpriteKit。有關(guān)詳細(xì)信息,請參閱第 1 章。

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

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

這將重置messageLabelsessionStateLabel的不統(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ù):要渲染的平面的centerextent,兩者都是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é)點。這個工作將在下一章中處理。

上一章 目錄 下一章
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容