[ARKit]11-[譯]在ARKit中建立一個時空門App:準備開始

說明

ARKit系列文章目錄node

譯者注:本文是Raywenderlich上《ARKit by Tutorials》免費章節的翻譯,是原書第7章.原書7~9章完成了一個時空門app.
官網原文地址www.raywenderlich.com/195361/buil…ios


本文是咱們書籍ARKit by Tutorials中的第7章,「建立你的時空門」.這本書向你展現瞭如何用蘋果的加強現實框架ARKit,來構建五個沉浸式的,好看的AR應用.開始吧swift

經過這一系列教程,你將用ARKit和SceneKit實現一個時空門應用.時空門類的app能夠用於教育目的,好比一個太陽系虛擬瀏覽應用,或者一些休閒活動,好比享受一場虛擬的沙灘假期.數組

時空門app

在這個應用中,你將在現實世界中的某個水平面上,放置一個通往充滿將來感的房間的虛擬門.你能夠走進走出這個房間,探索裏面有什麼.xcode

在該教程中,你將創建時空門應用的基礎.在本教程中,你將學會如何:session

  • 創建一個ARSession
  • 用ARKit檢測並渲染水平面

你準備好創建通往另外一個世界的通道了麼?app

開始

在Xcode中,打開starter工程, Portal.xcodeproj.建立並運行工程,你會看到一個空白屏幕. 框架

啊,是的,一個空白充滿機遇的畫布!
打開Main.storyboard再展開Portal View Controller Scene async

PortalViewController是應用啓動後呈現給用戶的界面.它包含一個ARSCNView來顯示相機預覽畫面.還包含了兩個UILabels來提供說明和反饋給用戶.ide

如今,打開PortalViewController.swift.在這個文件中,你將看到下面的變量,它們表明了storyboard中的元素:

// 1
@IBOutlet var sceneView: ARSCNView?
// 2
@IBOutlet weak var messageLabel: UILabel?
// 3
@IBOutlet weak var sessionStateLabel: UILabel?
複製代碼

讓咱們看看其中的內容:

  1. sceneView用來顯示3D的SceneKit物體在相機視圖上.
  2. messageLabel,它是個UILabel,會給用戶展現說明性的消息.說明,告訴他們如何與你的app交互.
  3. sessionStateLabel,另外一個UILabel,會通知用戶session打斷狀況,例如當app進入後臺或環境光不知足條件.

注意:ARKit會處理全部的傳感器和相機數據,但它不會實際去渲染任何虛擬內容.要在你的場景中渲染內容,可使用與ARKit協同的各類渲染器,好比SceneKit or SpriteKit.
ARSCNView是蘋果提供的一個框架,你能夠輕易將ARKit中數據與SceneKit融合在一塊兒.使用ARSCNView會有不少好處,這就是爲何你要在本教程的項目中用它.

在starter工程中,你將會在Helpers分組下看到不少工具類.你會在app後面的開發中用到它們.

創建ARKit

第一步是用相機來捕捉視頻流.爲此,你須要使用ARSCNView對象.

打開PortalViewController.swift並添加下面的方法:

func runSession() {
  // 1 
  let configuration = ARWorldTrackingConfiguration.init()
  // 2
  configuration.planeDetection = .horizontal
  // 3
  configuration.isLightEstimationEnabled = true
  // 4
  sceneView?.session.run(configuration)

  // 5
  #if DEBUG
    sceneView?.debugOptions = [ARSCNDebugOptions.showFeaturePoints]
  #endif
}
複製代碼

代碼說明:

  1. 首先實例化一個ARWorldTrackingConfiguration對象.它爲ARSession定義了配置信息.對ARSession來講可用的配置類型有兩種: ARSessionConfigurationARWorldTrackingConfiguration.
    使用ARSessionConfiguration是不推薦的,由於它使用了設備的旋轉信息,尚未位置.對於使用A9處理器的設置來講,ARWorldTrackingSessionConfiguration是最佳選擇,由於它追蹤了設備的全部運動.
  2. configuration.planeDetection是設置爲檢測水平面.平面的範圍可能會改變,而且隨着相機的移動多個平面可能會合併成一個.它能夠找到任何水平面例如地板,桌子或牀.
  3. 它啓用了燈光估計計算,能夠被渲染框架利用來製造出更真實的虛擬物體.
  4. 使用指定的配置來啓動session的AR進程.這將會啓動ARKit session和視頻捕捉,並顯示在sceneView上.
  5. 調試設置,這樣會添加可見的特徵點;覆蓋在相機視圖上.

如今,是時候創建labels的默認設置了.用下面的代碼替換resetLabels():

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是用於展現錯誤信息的,以防出錯.

如今,添加runSession()PortalViewController中的viewDidLoad() 裏面:

override func viewDidLoad() {
  super.viewDidLoad()    
  resetLabels()
  runSession()
}
複製代碼

這將會在app啓動並加載視圖時運行ARKit session.

接着,構建並運行app.不要忘記--你須要給app授於相機訪問權限.

ARSCNView完成了繁重的相機視頻捕捉和顯示任務.由於是調試模式,你能夠看到渲染出的特徵點,它們造成了點雲,顯示出場景分析的中間結果.

平面探測和渲染

先前,在runSession()中, 你設置了planeDetection.horizontal,這意味着你的app能探測水平面.你可以在ARSCNViewDelegate協議的代理回調方法中得到捕捉到的平面信息.

PortalViewController中添加類擴展,實現ARSCNViewDelegate協議:

extension PortalViewController: ARSCNViewDelegate {

}
複製代碼

runSession() 末尾添加下面代碼:

sceneView?.delegate = self
複製代碼

這行代碼將PortalViewController設置爲sceneView對象的ARSCNViewDelegate代理.

ARPlaneAnchors會被自動添加到ARSession錨點數組中,而且ARSCNView自動將ARPlaneAnchor對象轉換爲SCNNode節點.

如今,要渲染這些平面,你須要作的是實現ARSCNViewDelegate代理方法:

// 1
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
  // 2
  DispatchQueue.main.async {
    // 3
    if let planeAnchor = anchor as? ARPlaneAnchor {
        // 4
      #if DEBUG
        // 5
        let debugPlaneNode = createPlaneNode(
          center: planeAnchor.center,
          extent: planeAnchor.extent)
        // 6 
        node.addChildNode(debugPlaneNode)
      #endif
      // 7
      self.messageLabel?.text =
      "Tap on the detected horizontal plane to place the portal"
    }
  }
}
複製代碼

代碼含義:

  1. ARSession探測到新的平面時,renderer(_:didAdd:for:) 方法會被調用,而且ARSCNView自動爲平面添加一個ARPlaneAnchor.
  2. 這個回調是在後臺線程.這裏,你須要派發到主線程,由於更新UI須要在主線程完成.
  3. 檢查ARAnchor是不是一個ARPlaneAnchor.
  4. 檢查是否在debug模式.
  5. 若是是,用ARKit探測到的planeAnchor的中心點和麪積座標來建立平面SCNNode節點.createPlaneNode() 是個幫助類的方法稍後實現.
  6. node對象是一個空的SCNNode,會被ARSCNView自動添加到場景中;它的座標對準到ARAnchor的位置上.這裏,你添加一個debugPlaneNode做爲子節點,這樣它就會被放置在節點的位置上.
  7. 最後,不論是否在debug模式,咱們都爲用戶更新說明信息,以提示用戶app如今已經準備好放置時空門到場景中了.

如今是時候創建幫助類的方法了.

建立一個新的Swift文件,命名爲SCNNodeHelpers.swift.用來盛放渲染SCNNode對象相關的全部工具方法.

導入SceneKit到文件中:

import SceneKit
複製代碼

如今,添加下面的幫助方法:

// 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平面.寬度是extent中的x座標,高度是z座標.
  3. 初始化SCNMaterial對象並賦值漫反射內容.漫反射層顏色設置爲半透明的黃色.
  4. SCNMaterial對象而後被添加到平面的materials數組中.這定義了平面的紋理和顏色.
  5. 建立一個帶有plane幾何體的SCNNode節點.SCNPlane繼承於SCNGeometry類,它只提供了SceneKit渲染出的可見物體.經過將幾何體附加到SCNNode對象來指定它的位置和朝向.多個節點能夠引用同一個幾何體對象,並容許在一個場景的不一樣位置出現.
  6. 設置planeNode的位置.注意,節點是根據ARKit上報的ARPlaneAnchor實例對象的信息被平移到座標點 (center.x, 0, center.z) 處.
  7. SceneKit中的平面默認是豎直的,因此你須要旋轉90度以使它呈水平狀態.
  8. 該步返回前步建立的planeNode對象.

運行一下app,若是ARKit能探測到合適的平面,你就能看到一個黃色的水平面了.

移動一下設備,你會注意到app有時會顯示多個平面.當它發現更多平面時,它將其加到視圖中.然而,已經存在的平面,卻不會隨着ARKit分析出更多特徵點而更新或改變尺寸.

ARKit會根據新發現的特徵點來持續更新平面的位置和尺寸.要想接收這些更新,在PortalViewController.swift中添加下列的renderer(_:didUpdate:for:) 代理方法:

// 1
func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
  // 2 
  DispatchQueue.main.async {
    // 3
    if let planeAnchor = anchor as? ARPlaneAnchor,
      node.childNodes.count > 0 {
      // 4 
      updatePlaneNode(node.childNodes[0],
                      center: planeAnchor.center,
                      extent: planeAnchor.extent)
    }
  }
}
複製代碼

解釋:

  1. renderer(_:didUpdate:for:) 將會在相應的ARAnchor更新時被調用.
  2. 在主線程更新UI操做.
  3. 檢查ARAnchor,確保是一個ARPlaneAnchor類型,而且它至少有一個子節點對應於平面的SCNNode.
  4. updatePlaneNode(_:center:extent:) 方法將會在稍後實現.它根據ARPlaneAnchor中的信息更新平面的座標和尺寸.

打開SCNNodeHelpers.swift文件,添加下列代碼:

func updatePlaneNode(_ node: SCNNode, center: vector_float3, extent: vector_float3) {
  // 1 
  let geometry = node.geometry as? SCNPlane
  // 2
  geometry?.width = CGFloat(extent.x)
  geometry?.height = CGFloat(extent.z)
  // 3
  node.position = SCNVector3Make(center.x, 0, center.z)
}
複製代碼

代碼解釋:

  1. 檢查節點是否有SCNPlane幾何體.
  2. 使用傳遞過來的參數更新節點的幾何體.用ARPlaneAnchor中的extent或size來更新平面的寬度和高度.
  3. 更新平面節點的位置到新位置上.

如今你能夠成功地更新平面的位置了,運行一下app.你會看到平面的尺寸和位置會隨着探測到新的特徵點而調整.

還有一個問題須要解決.一旦app檢測到平面,若是你退出app再從新回來,你會看到前一個探測到的平面還在相機視圖上,顯示在其餘物體前面;它已經再也不匹配先前的平面了.

要修復這個問題,你須要在ARSession被打斷時移除平面節點.咱們會在下一章處理這個問題.

下一步作什麼?

你可能還沒意識到,但你已經踏上了建立一個時空門app的漫漫長路!是的,還有不少要作的事,可是你已經在進入虛擬空間的路上了.

本章節簡單總結:

  • 探索starter項目,並複習了ARKit基礎.
  • 配置一個ARSession來在app中展現相機的輸出.
  • 添加平面檢測和其餘函數,以便app能使用ARSCNViewDelegate協議來渲染水平面.

在下一章教程中,你將會學習如何處理session的打斷,及在視圖中使用SceneKit來渲染3D物體.點擊這裏來繼續本系列教程的第2部分!

若是你喜歡本教程,能夠來查看咱們的完整版書籍ARKit by Tutorials.

資料下載地址

相關文章
相關標籤/搜索