ARKit 初探

0. 前言

做爲一名剛入門的 iOS 開發者,前陣子稍稍研究了一下最新發布的 ARKit,而後結合幾個其餘開源項目作成了一個 ARGitHubCommits。前天在上海第 8 次 T 沙龍上分享了一個《ARKit 初探》的 topic,如今將它寫成文章,以便瀏覽。node

1. ARKit 簡介

下面是蘋果開發者官網 ARKit 頁面的一段介紹:git

iOS 11 引入了新的 ARKit 框架,讓您輕鬆建立無可比擬的 iPhone 和 iPad 加強現實體驗。 經過將數字對象和信息與您周圍的環境相融合,ARKit 爲 App 解開了屏幕之縛,帶領着它們跨越屏幕的界限,讓它們以全新的方式與現實世界交流互動。github

可見,蘋果在 AR 的市場上應該是作了不少準備。不只有本文要介紹的 ARKit,在最新發布的 iPhone X 中也對攝像頭作了優化,配備了前置景深攝像頭,將 AR 和麪部識別結合起來。因此,AR 可能將會是將來幾年內的一個重要發展方向。算法

2. 設備要求

蘋果在硬件上也作了一些努力。咱們能夠從官網的介紹中得出如下幾個信息:bash

  • 創建在優秀的硬件設施和算法上,ARKit 採集的現實世界數據相對來講比較精準(比 Google 的 ARCore 要好些)。
  • 能夠利用 ARKit 來探測水平面,而後能夠在水平面上放置小的物體。
  • ARKit 會根據周圍光線的亮度自動調節虛擬物體的亮度和陰影、紋理等信息。

固然,要運行 ARKit,在硬件上也有一些要求。必定是要具有 A9 及以上的處理器(iPhone 6s 爲 A9 處理器)的設備才能夠運行 AR。軟件上,若是要開發 ARKit App,那麼要有 Xcode 9 和 iOS 11 SDK。session

當你作好了一切準備,那就讓咱們進入 ARKit 的世界!app

3. AR 工做流程

AR 工做流程

上圖解釋的是 ARKit 的工做流程。其中藍色表示 ARKit 負責的部分,綠色表示 SceneKit 負責的部分。固然,創建虛擬世界也可使用其餘的框架,好比 SpriteKit、Metal,本文將以 SceneKit 爲例子進行講解。框架

  1. 首先,ARKit 利用攝像頭拍攝現實場景的畫面,而後 SceneKit 用來創建虛擬世界。
  2. 創建好了之後,ARKit 負責將現實世界和虛擬世界的信息融合,並渲染出一個 AR 世界。
  3. 在渲染的同時,ARKit 要負責如下三件事:
  • 維持世界追蹤 指的是當你移動攝像頭,要去獲取新的現實世界的信息。
  • 進行場景解析 指的是解析現實世界中有無特徵點、平面等關鍵信息。
  • 處理與虛擬世界的互動 指的是當用戶點擊或拖動屏幕時,處理有沒有點擊到虛擬物體或者要不要進行添加/刪除物體的操做。

因而可知,ARKit 主要作的事是:捕捉現實世界信息、將現實和虛擬世界混合渲染、而且時刻處理新的信息或者進行互動優化

理解了 AR 的工做流程後,讓咱們來看看 ARKit 中一些重要的類的職責。ui

4. ARKit 和 SceneKit 關係圖

ARKit 和 SceneKit 關係圖

上面是 ARKit 和 SceneKit 的關鍵的類的關係圖。其中 ARSCNView 是繼承自 SCNView 的,因此其中關於 3D 物體的屬性、方法都是 SCNView 的(如 SCNScene、SCNNode 等)。

下面簡單介紹一下 ARKit 中各個類是如何協做的。

ARSCNView

最頂層的 ARSCNView 主要負責綜合虛擬世界(SceneKit)的信息和現實世界的信息(由ARSession 類負責採集),而後將它們綜合渲染呈現出一個 AR 世界。

ARSession

ARSession 類負責採集現實世界的信息。這一行爲也被稱做__世界追蹤__。它主要的職責是:

  • 追蹤設備的位置以及旋轉,這裏的兩個信息均是相對於設備起始時的信息。
  • 追蹤物理距離(以「米」爲單位),例如 ARKit 檢測到一個平面,咱們但願知道這個平面有多大。
  • 追蹤咱們手動添加的但願追蹤的點,例如咱們手動添加的一個虛擬物體。

它採集到的現實世界信息以 ARFrame 的形式返回。

固然,爲了有一個比較好的追蹤效果,要知足如下要求:

  • 運動傳感器不能中止工做。若是運動傳感器中止了工做,那麼就沒法拿到設備的運動信息。根據咱們以前提到的世界追蹤的工做原理,毫無疑問,追蹤質量會降低甚至沒法工做。
  • 真實世界的場景須要有必定特徵點可追蹤。世界追蹤須要不斷分析和追蹤捕捉到的圖像序列中特徵點,若是圖像是一面白牆,那麼特徵點很是少,那麼追蹤質量就會降低。
  • 設備移動速度不能過快。若是設備移動太快,那麼 ARKit 沒法分析出不一樣圖像幀之中的特徵點的對應關係,也會致使追蹤質量降低。

總的說來,就是要提示用戶移動手機,且速度不能太快,要在略微複雜的場景中探測

ARFrame

ARFrame 包含了兩部分信息:ARAnchor 和 ARCamera。其中,

  • ARCamera 指的是當前攝像機的位置和旋轉信息。這一部分 ARKit 已經爲咱們配置好,不用特別配置。
  • ARAnchor 指的是現實世界中的__錨點__,具體解釋以下:

ARAnchor

  • 能夠把 ARAnchor(錨點)理解爲真實世界中的某個點或平面,anchor 中包含位置信息和旋轉信息。拿到 anchor 後,能夠在該 anchor 處放置一些虛擬物體。
  • 與 SCNNode 能夠綁定
  • 它有一個子類:ARPlaneAnchor,專門指的是一個表明水平面的錨點。

ARConfiguration

指的是 ARSession 將如何追蹤世界,有如下幾種子類:

  • ARWorldTrackingConfiguration(6 DOF) 是 ARSession 的默認配置,以6個自由度(x y z軸上的位移及繞着三個軸的旋轉)追蹤現實錨點和虛擬物體。
  • AROrientationTrackingConfiguration(3 DOF) 以 3 個自由度(沒有位移,只有旋轉)追蹤,可是被蘋果文檔聲明不推薦使用。這樣的追蹤質量將比較差。
  • ARFaceTrackingConfiguration(iPhone X Only) 以面部識別來追蹤,只有裝備了 True Depth 前置景深攝像頭的 iPhone X 才能使用。

並且,若是要開啓平面檢測,須要加入如下語句:

let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = .horizontal
複製代碼

總結

用一段話總結的話,就是 ARSCNView 結合 SCNScene 中的虛擬世界信息和 ARsession 捕捉到的現實世界信息,渲染出 AR 世界。ARConfiguration 指導 ARSession 如何追蹤世界,追蹤的結果以 ARFrame 返回。ARFrame 中的 ANAnchor 信息爲 SceneKit 中的 SCNNode 提供了一些放置的點,以便將虛擬節點和現實錨點綁定。

5. ARKit 中的 Delegate

ARSCNViewDelegate

先介紹 ARSCNView 的代理:ARSCNViewDelegate,他有如下幾個回調方法。

func renderer(SCNSceneRenderer, nodeFor: ARAnchor)
複製代碼

當 ARSession 檢測到一個錨點時,能夠在這個回調方法中決定是否給它返回一個 SCNNode。默認是返回一個空的 SCNNode(),咱們能夠根據本身的須要將它改爲只在檢測到平面錨點(ARPlaneAnchor)時返回一個錨點,諸如此類。

func renderer(SCNSceneRenderer, didAdd: SCNNode, for: ARAnchor)
func renderer(SCNSceneRenderer, willUpdate: SCNNode, for: ARAnchor)
func renderer(SCNSceneRenderer, didUpdate: SCNNode, for: ARAnchor)
func renderer(SCNSceneRenderer, didRemove: SCNNode, for: ARAnchor)
複製代碼

以上方法會在爲一個錨點已經添加、將要更新、已經更新、已經移除一個虛擬錨點時進行回調。

ARSessionDelegate

ARSession 類也有本身的代理:ARSessionDelegate

func session(ARSession, didUpdate: ARFrame)
複製代碼

在 ARKit 中,當用戶移動手機時,會實時更新不少 ARFrame。這個方法會在更新了 ARFrame 時,進行回調。它能夠用於相似於__始終想維持一個虛擬物體在屏幕中間__的場景,只須要在這個方法中將該節點的位置更新爲最新的 ARFrame 的中心點便可。

func session(ARSession, didAdd: [ARAnchor])
func session(ARSession, didUpdate: [ARAnchor])
func session(ARSession, didRemove: [ARAnchor])
複製代碼

若是使用了 ARSCNViewDelegate 或 ARSKViewDelegate,那上面三個方法沒必要實現。由於另外的 Delegate 的方法中除了錨點之外,還包含節點信息,這可讓咱們有更多的信息進行處理。

6. 一些實踐

下面就幾種經常使用場景給出一些示例代碼。

添加物體

添加物體能夠有如下兩種方式:自動檢測並添加或者手動點擊添加。

自動檢測並添加

主要利用了 ARSCNViewDelegate 中的回調方法:

func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
    if let planeAnchor = anchor as? ARPlaneAnchor {
        let node = SCNNode()
        node.geometry = SCNBox(width: CGFloat(planeAnchor.extent.x), height: CGFloat(planeAnchor.extent.y), length: CGFloat(planeAnchor.extent.z), chamferRadius: 0)
        return node
    }
    return nil
}
複製代碼

這段代碼的含義是:若是找到了一個平面錨點,那就返回一個和該平面錨點的長寬高分別相同的白色長方體節點。當你移動手機尋找平面時,一旦找到,便會有一個白色平面出如今屏幕上。

手動點擊添加

ARKit 容許用戶在畫面中點擊,來和虛擬世界互動。 好比咱們以前添加了一個 UITapGestureRecognizer,selector 是以下方法:

@objc func didTap(_ sender: UITapGestureRecognizer) {
    let location = sender.location(in: sceneView)
    let hitResults = sceneView.hitTest(location, types: .featurePoint)
    if let result = hitResults.first {
        let box = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0)
        let boxNode = SCNNode(geometry: box)
        boxNode.position = SCNVector3(x: result.worldTransform.columns.3.x,
                                  y: result.worldTransform.columns.3.y,
                                  z: result.worldTransform.columns.3.z)
        sceneView.scene.rootNode.addChildNode(boxNode)
    }
}
複製代碼

這其中用到了一個 ARHitTestResult 類,它能夠檢測用戶手指點擊的地方有沒有通過一些符合要求的點/面,有以下幾種選項:

  • featurePoint 返回當前圖像中 Hit-testing 射線通過的 3D 特徵點。
  • estimatedHorizontalPlane 返回當前圖像中 Hit-testing 射線通過的預估平面。
  • existingPlaneUsingExtent 返回當前圖像中 Hit-testing 射線通過的有大小範圍的平面。
  • existingPlane 返回當前圖像中 Hit-testing 射線通過的無限大小的平面。

上面一段代碼的含義是:首先記錄用戶點擊的位置,而後判斷有沒有點擊到特徵點,並將結果按從近到遠的順序返回。若是有最近的一個結果,就生成一個長寬高都爲0.1米的立方體,並把它放在那個特徵點上。

其中將 ARHitTestResult 信息轉換成三維座標,用到了 result.worldTransform.columns.3.x(y,z)的信息。咱們不深究其中原理,只需知道它的轉換方法就能夠了。

更新物體位置

這時可使用 ARSessionDelegate 的代理方法:

func session(_ session: ARSession, didUpdate frame: ARFrame) {
    if boxNode != nil {
        let mat = frame.camera.transform.columns.3
        boxNode?.position = SCNVector3Make((mat.x) * 3, (mat.y) * 3, (mat.z) * 3 - 0.5)
    }
}
複製代碼

也就是當更新了一個 ARFrame,就把一個以前創建好的 SCNNode 的位置更新爲 frame 的中心點。這裏 * 3 是爲了放大移動的效果。注意,這裏也用到了上面所說的 worldTransform 和 SCNVector3 的轉換方法。

7. ARGitHubCommits 思路

有了以上的知識基礎,咱們能夠用如下思路來構建這個項目:

  1. 獲取 GitHub 的 Commits 數據。
  2. 創建 ARSCNView,開始 ARSession。
  3. 提示用戶移動手機,探測水平面。
  4. 探測成功後,在 ARSCNView 中的 SCNScene 中加入各個 SCNNode。

具體的代碼,歡迎參考GitHub

8. 參考

下面是一些能夠參考的文章/GitHub連接,僅供參考:

  • https://developer.apple.com/cn/arkit/
  • https://developer.apple.com/documentation/arkit
  • http://blog.csdn.net/u013263917/article/details/72903174
  • https://mp.weixin.qq.com/s/DxPHo6j6pJQuhdXM_5K4qw
  • https://github.com/olucurious/Awesome-ARKit
  • https://github.com/songkuixi/ARGitHubCommits

微博:@滑滑雞

GitHub:songkuixi

相關文章
相關標籤/搜索