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

蘋果API搬運工發表於2018-07-25

說明

ARKit系列文章目錄

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


本文是我們書籍ARKit by Tutorials中的第7章,“建立你的時空門”.這本書向你展示瞭如何用蘋果的擴增實境框架ARKit,來構建五個沉浸式的,好看的AR應用.開始吧

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

時空門app

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

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

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

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

開始

在Xcode中,開啟starter工程, Portal.xcodeproj.建立並執行工程,你會看到一個空白螢幕.

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

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

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

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

現在,開啟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完成了繁重的相機視訊捕捉和顯示任務.因為是除錯模式,你可以看到渲染出的特徵點,它們形成了點雲,顯示出場景分析的中間結果.

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

平面探測和渲染

先前,在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能探測到合適的平面,你就能看到一個黃色的水平面了.

[ARKit]11-[譯]在ARKit中建立一個時空門App:準備開始
移動一下裝置,你會注意到app有時會顯示多個平面.當它發現更多平面時,它將其加到檢視中.然而,已經存在的平面,卻不會隨著ARKit分析出更多特徵點而更新或改變尺寸.
[ARKit]11-[譯]在ARKit中建立一個時空門App:準備開始

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再重新回來,你會看到前一個探測到的平面還在相機檢視上,顯示在其他物體前面;它已經不再匹配先前的平面了.

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

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

下一步做什麼?

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

本章節簡單總結:

  • 探索starter專案,並複習了ARKit基礎.
  • 配置一個ARSession來在app中展示相機的輸出.
  • 新增平面檢測和其他函式,以便app能使用ARSCNViewDelegate協議來渲染水平面.

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

如果你喜歡本教程,可以來檢視我們的完整版書籍ARKit by Tutorials.

資料下載地址

相關文章