直擊蘋果 ARKit 技術

蘋果在 WWDC2017 中推出了 ARKit,經過這個新框架能夠看出蘋果將來會在 AR 方向不斷髮展,本着學習興趣,對此項新技術進行了學習,並在團隊進行了一次分享,利用業餘時間把幾周前分享的內容整理成文檔供你們交流學習。html

本文並非簡單的介紹 ARKit 中的 API 如何使用,而是在介紹 ARKit API 的同時附上了一些理論知識,因此有些內容可能會不太容易理解。筆者能力有限,文章如有誤請指出。團隊分享(本次分享爲網易杭研院前端技術部的內部分享)時進行了一次直播,若文中有不理解的內容,歡迎觀看下錄播:ARKit 分享直播 。本文也附上分享的 PPT 地址:ARKit-keynote前端

如需轉載請註明做者和原文地址。node

1、什麼是 AR?

AR 全稱 Augmented Reality(加強現實),是一種在攝像機捕捉到的真實世界中加入計算機程序創造的虛擬世界的技術。下圖是一個簡單的 AR 的 Demo:git

ARChair.gif

在上圖中,椅子是計算機程序建立的虛擬世界的物體,而背景則是攝像機捕捉到的真實世界,AR 系統將二者結合在一塊兒。咱們從上圖能夠窺探出 AR 系統由如下幾個基礎部分組成:github

  1. 捕捉真實世界:上圖中的背景就是真實世界,通常由攝像機完成。
  2. 虛擬世界:例如上圖中的椅子就是虛擬世界中的一個物體模型。固然,能夠有不少物體模型,從而組成一個複雜的虛擬世界。
  3. 虛擬世界與現實世界相結合:將虛擬世界渲染到捕捉到的真實世界中。
  4. 世界追蹤:當真實世界變化時(如上圖中移動攝像機),要能追蹤到當前攝像機相對於初始時的位置、角度變化信息,以便實時渲染出虛擬世界相對於現實世界的位置和角度。
  5. 場景解析:例如上圖中能夠看出椅子是放在地面上的,這個地面實際上是 AR 系統檢測出來的。
  6. 與虛擬世界互動:例如上圖中縮放、拖動椅子。(其實也屬於場景解析的範疇)

根據上面的描述,咱們能夠得出 AR 系統的大體結構圖:swift

ARKitSystem.png

Note:這裏只介紹基於計算機顯示器的 AR 系統實現方案,此外還有光學透視式和視頻透視式方案。可參考加強現實-組成形式segmentfault

2、ARKit 簡介

ARKitLogo.png

ARKit 是蘋果 WWDC2017 中發佈的用於開發iOS平臺 AR 功能的框架。ARKit 爲上一節中提到的 AR 系統架構中各個部分都提供了實現方案,而且爲開發者提供了簡單便捷的 API,使得開發者更加快捷的開發 AR 功能。數組

ARKit 的使用須要必定的軟硬件設施:session

  • 軟件:
    • 開發工具:Xcode9
    • iOS11
    • MacOS 10.12.4 及以上版本(爲了支持 Xcode9)
  • 硬件:
    • 處理器爲 A9 及以上的 iPhone 或 iPad 設備(iPhone 6s 爲 A9 處理器)

下面幾節中,咱們將逐步介紹 ARKit 中 AR 系統的各個組成部分。架構

3、ARKit 架構

ARKit 定義了一套簡單易用的 API,API 中引入了多個類,爲了更加清晰的理解 ARKit,咱們從 AR 系統組成的角度對 ARKit API 進行了分類,以下圖:

ARKitArchitecture

上圖列出了 ARKit API 中的幾個主要的類,如 ARSession、ARSessionConfiguration、ARFrame、ARCamera 等。並依據各個類的功能進行了模塊劃分:紅色(World Tracking)、藍色(Virtual World)、土色(Capture Real Wrold)、紫色(Scene Understanding)、綠色(Rendering)。

對於上圖,ARSession 是核心整個ARKit系統的核心,ARSession 實現了世界追蹤、場景解析等重要功能。而 ARFrame 中包含有 ARSession 輸出的全部信息,是渲染的關鍵數據來源。雖然 ARKit 提供的 API 較爲簡單,但看到上面整個框架後,對於初識整個體系的開發者來講,仍是會覺着有些龐大。不要緊,後面幾節會對每一個模塊進行單獨的介紹,當讀完最後時,再回頭來看這個架構圖,或許會更加明瞭一些。

4、構建虛擬世界

咱們將 Virtual World 從 ARKit 架構圖中抽出:

VirtualWorld.png

ARKit 自己並不提供建立虛擬世界的引擎,而是使用其餘 3D/2D 引擎進行建立虛擬世界。iOS 系統上可以使用的引擎主要有:

  • Apple 3D Framework - SceneKit.
  • Apple 2D Framework - SpriteKit.
  • Apple GPU-accelerated 3D graphics Engine - Metal.
  • OpenGl
  • Unity3D
  • Unreal Engine

ARKit 並無明確要求開發者使用哪一種方式構建虛擬世界,開發者能夠利用 ARKit 輸出的真實世界、世界追蹤以及場景解析的信息(存在於 ARFrame 中),本身將經過圖形引擎建立的虛擬世界渲染到真實世界中。值得一提的是,ARKit 提供了 ARSCNView 類,該類基於 SceneKit 爲 3D 虛擬世界渲染到真實世界提供了很是簡單的 API,關於 ARSCNView 會在最後的渲染部分進行介紹,因此下面咱們來介紹下 SceneKit。

SceneKit 簡介

這裏不對 SceneKit 進行深刻探討,只簡單介紹下基礎概念。讀者只須要理解 SceneKit 裏虛擬世界的構成就能夠了。

SceneKit 的座標系

咱們知道 UIKit 使用一個包含有 x 和 y 信息的 CGPoint 來表示一個點的位置,可是在 3D 系統中,須要一個 z 參數來描述物體在空間中的深度,SceneKit 的座標系能夠參考下圖:

SceneKitCoordinate.png

這個三維座標系中,表示一個點的位置須要使用(x,y,z)座標表示。紅色方塊位於 x 軸,綠色方塊位於 y 軸,藍色方塊位於 z 軸,灰色方塊位於原點。在 SceneKit 中咱們能夠這樣建立一個三維座標:

let position = SCNVector3(x: 0, y: 5, z: 10)
複製代碼

SceneKit 中的場景和節點

咱們能夠將 SceneKit 中的場景(SCNScene)想象爲一個虛擬的 3D 空間,而後能夠將一個個的節點(SCNNode)添加到場景中。SCNScene 中有惟一一個根節點(座標是(x:0, y:0, z:0)),除了根節點外,全部添加到 SCNScene 中的節點都須要一個父節點。

下圖中位於座標系中心的就是根節點,此外還有添加的兩個節點 NodeA 和 NodeB,其中 NodeA 的父節點是根節點,NodeB 的父節點是 NodeA:

SceneKitNode.png

SCNScene 中的節點加入時能夠指定一個三維座標(默認爲(x:0, y:0, z:0)),這個座標是相對於其父節點的位置。這裏說明兩個概念:

  • 本地座標系:以場景中的某節點(非根節點)爲原點創建的三維座標系
  • 世界座標系:以根節點爲原點建立的三維座標系稱爲世界座標系。

上圖中咱們能夠看到 NodeA 的座標是相對於世界座標系(因爲 NodeA 的父節點是根節點)的位置,而 NodeB 的座標表明了 NodeB 在 NodeA 的本地座標系位置(NodeB 的父節點是 NodeA)。

SceneKit 中的攝像機

有了 SCNScene 和 SCNNode 後,咱們還須要一個攝像機(SCNCamera)來決定咱們能夠看到場景中的哪一塊區域(就比如現實世界中有了各類物體,但還須要人的眼睛才能看到物體)。攝像機在 SCNScene 的工做模式以下圖:

SceneKitCamera.png

上圖中包含如下幾點信息:

  • SceneKit 中 SCNCamera 拍攝的方向始終爲 z 軸負方向。
  • 視野(Field of View)是攝像機的可視區域的極限角度。角度越小,視野越窄,反之,角度越大,視野越寬。
  • 視錐體(Viewing Frustum)決定着攝像頭可視區域的深度(z 軸表示深度)。任何不在這個區域內的物體將被剪裁掉(離攝像頭太近或者太遠),不會顯示在最終的畫面中。

在 SceneKit 中咱們可使用以下方式建立一個攝像機:

let scene = SCNScene()
let cameraNode = SCNNode()
let camera = SCNCamera()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 0)
scene.rootNode.addChildNode(cameraNode)
複製代碼

SCNView

最後,咱們須要一個 View 來將 SCNScene 中的內容渲染到顯示屏幕上,這個工做由 SCNView 完成。這一步其實很簡單,只須要建立一個 SCNView 實例,而後將 SCNView 的 scene 屬性設置爲剛剛建立的 SCNScene,而後將 SCNView 添加到 UIKit 的 view 或 window 上便可。示例代碼以下:

let scnView = SCNView()
scnView.scene = scene
vc.view.addSubview(scnView)
scnView.frame = vc.view.bounds
複製代碼

5、捕捉真實世界(Capture Real World)

捕捉真實世界就是爲了將咱們現實世界的場景做爲 ARKit 顯示場景的背景。爲了方便閱讀,咱們首先將 Capture Real World 從 ARKit 架構圖中抽取出:

CaptureRealWorld.png

ARSession

若是咱們想要使用 ARKit,咱們必需要建立一個 ARSession 對象並運行 ARSession。基本步驟以下:

// 建立一個 ARSessionConfiguration.
// 暫時無需在乎 ARWorldTrackingSessionConfiguration.
let configuration = ARWorldTrackingSessionConfiguration()
// Create a session.
let session = ARSession()
// Run.
session.run(configuration)
複製代碼

從上面的代碼看,運行一個 ARSession 的過程是很簡單的,那麼 ARSession 的底層如何捕捉現實世界場景的呢?

  • 首先,ARSession 底層使用了 AVCaputreSession 來獲取攝像機拍攝的視頻(一幀一幀的圖像序列)。
  • 而後,ARSession 將獲取的圖像序列進行處理,最後輸出 ARFrame,ARFrame 中就包含有現實世界場景的全部信息。

ARFrame

從上一步驟得知 ARFrame 中包含了現實世界場景的全部信息,那麼 ARFrame 中與現實世界場景有關的信息有哪些?

  • var capturedImage: CVPixelBuffer 該屬性是攝像機捕捉到的圖像信息,就是構成咱們現實世界場景中的一幀圖像。順便說明下,對於攝像機捕捉的每一幀圖像,都會生成一個 ARFrame。

  • var timestamp: TimeInterval 該屬性是攝像機捕捉到的對應於 capturedImage 的一幀圖像的時間。

  • var camera: ARCamera 獲取現實世界的相機信息。詳細介紹見下。

ARCamera

ARCamera 是 ARFrame 中的一個屬性,之由於單獨拿出來講,是由於這裏有必要介紹下相機的一些特性,ARCamera 中與現實世界場景有關的信息有兩個:

  • var imageResolution: CGSize 該屬性表示了相機捕捉到的圖像的長度和寬度(以像素爲單位),能夠理解成捕捉到的圖像的分辨率。

  • var intrinsics: matrix_float3x3 intrinsics 是一個 3x3 矩陣,這個矩陣將咱們現實世界中三維座標系的點映射到相機捕捉的圖像中。有興趣可看下面的詳述。

Intrinsic Matrix

Intrinsic Matrix 是相機的一個固有屬性,也就是說每一個相機都會有 Intrinsic Matrix,由於全部的相機都須要將現實世界中三維空間的點映射到捕捉的圖像中二維空間的點。

那麼這個矩陣是如何工做的呢?咱們先來看一個圖片:

IntrinsicMatrix.png

上圖包含以下基本信息:

  • 一個三維座標系(紅色 x 軸,綠色 y 軸,藍色 z 軸)。
  • 空間中的一個點(藍色的點 N,座標爲(x', y', z'))。
  • 相機的成像平面(紫色的平行四邊形)
  • 成像平面與 z 軸的交點(點 M)
  • 成像平面的原點(黃色的點 O),也就是捕捉的二維圖像的二維座標系的原點。

如今咱們須要將三維空間的點(x', y', z')映射到成像平面中的一個點(N')。下面咱們來看下映射過程。

Intrinsic Matrix 通常是下圖所示的樣子:

IntrinsicMatrixValue.png

上圖中,fx 和 fy 是攝像機鏡頭的焦距,這裏不作深究,ox 和 oy 則是點 M(成像平面與 z 軸交點)相對於點 O(成像平面二維座標系原點)的 x 與 y 方向的偏移。

下圖展現了利用 Intrinsic Matrix 將 N 映射 N' 的過程:

IntrinsicMatrixMap.png

上圖中,Intrinsic Matrix 與表示點 N 的向量相乘後,再除以 z',就獲得了一個 z 座標爲 1 的三維向量, 咱們丟棄掉 z 座標信息就獲得了 N' 的座標:((x' * fx)/z' + ox, (y' * fy)/z' + oy)。

這就是 Intrinsic Matrix 的做用過程,至於爲什麼這麼映射,則是相機原理的內容了,因爲水平有限,就不作介紹了。若是不太好理解,咱們這樣簡單理解爲相機使用這個矩陣就能夠將空間中的某個點映射到二維成像平面的一個點。

6、世界追蹤(World Tracking)

在第一部分 AR 系統介紹時,咱們看到虛擬椅子是放在地面上的,當咱們移動時能夠看到不一樣角度,咱們也能夠移動椅子,這些功能的實現都離不開世界追蹤。總結來講,世界追蹤用來爲真實世界與虛擬世界結合提供有效信息,以便咱們能在真實世界中看到一個更加真實的虛擬世界。

爲了方便閱讀,咱們首先將 World Tracking 從 ARKit 架構圖中抽取出:

WorldTracking.png

下面咱們分析一下 ARKit 中與世界追蹤相關的技術以及類。

ARSession

若是咱們想要使用 ARKit,咱們必需要建立一個 ARSession 對象並運行 ARSession。基本步驟以下:

// 建立一個 ARSessionConfiguration.
// 暫時無需在乎 ARWorldTrackingSessionConfiguration.
let configuration = ARWorldTrackingSessionConfiguration()
// Create a session.
let session = ARSession()
// Run.
session.run(configuration)
複製代碼

從上面的代碼看,運行一個 ARSession 的過程是很簡單的,那麼 ARSession 的底層如何進行世界追蹤的呢?

  • 首先,ARSession 底層使用了 AVCaputreSession 來獲取攝像機拍攝的視頻(一幀一幀的圖像序列)。
  • 其次,ARSession 底層使用了 CMMotionManager 來獲取設備的運動信息(好比旋轉角度、移動距離等)
  • 最後,ARSession 根據獲取的圖像序列以及設備的運動信息進行分析,最後輸出 ARFrame,ARFrame 中就包含有渲染虛擬世界所需的全部信息。

追蹤什麼?

那麼世界追蹤到底追蹤了哪些信息?下圖給出了 AR-World 的座標系,當咱們運行 ARSession 時設備所在的位置就是 AR-World 的座標系原點。

WorldTrackingCoordinate.png

在這個 AR-World 座標系中,ARKit 會追蹤如下幾個信息:

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

世界追蹤如何工做?

蘋果文檔中對世界追蹤過程是這麼解釋的:ARKit 使用視覺慣性測距技術,對攝像頭採集到的圖像序列進行計算機視覺分析,而且與設備的運動傳感器信息相結合。ARKit 會識別出每一幀圖像中的特徵點,而且根據特徵點在連續的圖像幀之間的位置變化,而後與運動傳感器提供的信息進行比較,最終獲得高精度的設備位置和偏轉信息。

咱們經過一個 gif 圖來理解上面這段話:

WorldTrackingProcess.gif

  • 上圖中劃出曲線的運動的點表明設備,能夠看到以設備爲中心有一個座標系也在移動和旋轉,這表明着設備在不斷的移動和旋轉。這個信息是經過設備的運動傳感器獲取的。
  • 動圖中右側的黃色點是 3D 特徵點。3D 特徵點就是處理捕捉到的圖像獲得的,能表明物體特徵的點。例如地板的紋理、物體的邊邊角角均可以成爲特徵點。上圖中咱們看到當設備移動時,ARKit 在不斷的追蹤捕捉到的畫面中的特徵點。
  • ARKit 將上面兩個信息進行結合,最終獲得了高精度的設備位置和偏轉信息。

Configuration

在運行 ARSession 時,咱們必需要有一個 Configuration。Configuration 告訴 ARKit 應該如何追蹤設備的運動。ARKit 爲咱們提供了兩種類型的 Configuration:

  • ARSessionConfiguration:提供 3DOF 追蹤。
  • ARWorldTrackingSessionConfiguration:提供 6DOF 追蹤。

上面兩個 Configuration 的差異就是 DOF 不同,那麼什麼是 DOF?

  1. DOF

    自由度(DOF,Degree Of Freedom)表示描述系統狀態的獨立參數的個數。6DOF 主要包括以下 6 個參數:

    DOF.png

    • 平移:

      1. 上下移動
      2. 左右移動
      3. 先後移動
    • 旋轉: 4. Yawing 2. Pitching 3. Rolling

    其中 3DOF 中只包含旋轉中的三個參數,平移的幾個參數其實很好理解,爲了更形象的理解旋轉參數,咱們看下面幾個動圖:

    • Yawing 效果:

      yaw.gif

    • Pitching 效果:

      pitch.gif

    • Rolling 效果:

      roll.gif

  2. 3DOF 與 6DOF 追蹤的效果差別

    ARWorldTrackingSessionConfiguration 使用 3 個旋轉參數和 3 個平移參數來追蹤物理系統的狀態, ARSessionConfiguration 使用 3 個旋轉參數來追蹤物理系統的狀態。那麼二者有什麼效果差異?

    咱們下面看兩個圖,下圖一使用 3DOF 的 ARSessionConfiguration 進行世界追蹤,下圖二使用 6DOF 的 ARWorldTrackingSessionConfiguration 進行世界追蹤:

    3DOF.gif

    6DOF.gif

    上面兩個圖,都是先對設備進行旋轉,再對設備進行平移。

    那麼,對於 3DOF 追蹤,咱們旋轉設備時能夠看到虛擬的飛機視角有所變化;但當平移時,咱們能夠看到飛機是隨着設備進行移動的。

    對於 6DOF 追蹤,咱們旋轉設備時能夠看到虛擬的飛機視角有所變化(這點與 3DOF 追蹤沒有區別);平移時,咱們能夠看到飛機的不一樣位置,例如向上平移看到了飛機的上表面,圍着飛機平移能夠看到飛機的四周,而 3DOF 沒有提供這種平移的追蹤。若是仍是不理解二者區別,能夠看動圖的後半段,效果差別實際上是很是明顯的。

  3. 判斷當前設備是否支持某類 SessionConfiguration

    class var isSupported: Bool 複製代碼

    示例代碼以下:

    if ARWorldTrackingSessionConfiguration.isSupported {
        configuration = ARWorldTrackingSessionConfiguration()
    } else {
        configuration = ARSessionConfiguration()
    }
    複製代碼

    關於 ARSessionConfiguration 咱們就介紹到這裏,下面咱們看一下 ARFrame。

ARFrame

ARFrame 中包含有世界追蹤過程獲取的全部信息,ARFrame 中與世界追蹤有關的信息主要是:anchors 和 camera:

  • camera: 含有攝像機的位置、旋轉以及拍照參數等信息。

    var camera: [ARCamera]
    複製代碼
  • ahchors: 表明了追蹤的點或面。

    var anchors: [ARAnchor]
    複製代碼

至於 ARCamera 和 ARAnchor 是什麼?下面分別進行介紹。

ARAnchor

ARAnchor.png

咱們能夠把 ARAnchor(AR 錨點) 理解爲真實世界中的某個點或平面,anchor 中包含位置信息和旋轉信息。拿到 anchor 後,能夠在該 anchor 處放置一些虛擬物體。對於 ARAnchor 有以下幾種操做:

  • 咱們可使用 ARSession 的 add/remove 方法進行手動添加或刪除 Anchor。例如,咱們添加了一個虛擬物體到 ARKit 中,在以後的某個時候咱們想要在剛纔的虛擬物體上面再放置一個東西,那麼咱們能夠爲這個虛擬物體添加一個 anchor 到 ARSession 中,這樣在後面能夠經過 ARSession 獲取到這個虛擬物體的錨點信息。

  • 經過 ARSession 獲取當前幀的全部 anchors:

    let anchors = session.currentFrame.anchors
    複製代碼
  • ARKit 自動添加 anchors。例如,ARKit 檢測到了一個平面,ARKit 會爲該平面建立一個 ARPlaneAnchor 並添加到 ARSession 中。

  • ARSessionDelegate 能夠監聽到添加、刪除和更新 ARAnchor 的通知。

ARAnchor 中的主要參數是 transform,這個參數是一個 4x4 的矩陣,矩陣中包含了 anchor 偏移、旋轉和縮放信息。

var transform: matrix_float4x4
複製代碼

這裏可能存在的一個疑問就是,爲什麼是一個 4x4 的矩陣,三維座標系表示一個點不是用三個座標就能夠了嗎?

4x4 矩陣?

物體在三維空間中的運動一般分類兩類:平移和旋轉,那麼表達一個物體的變化就應該可以包含兩類運動變化。

  • 平移

4x4Translation.png

首先看上圖,假設有一個長方體(黃色虛線)沿 x 軸平移Δx、沿 y 軸平移Δy、沿 z 軸平移Δz 到了另外一個位置(紫色虛線)。長方體的頂點 P(x1, y1, z1)則平移到了 P'(x2, y2, z2),使用公式表示以下:

4x4TranslationExpression.png

  • 旋轉

4x4Rotation.png

在旋轉以前,上圖中包含如下信息:

  • 黃色虛線的長方體
  • P(x1, y1, z1)是長方體的一個頂點
  • P 點在 xy 平面的投影點 Q(x1, y1, 0)
  • Q 與座標原點的距離爲 L
  • Q 與座標原點連線與 y 軸的夾角是α

那麼在旋轉以前,P 點座標能夠表示爲:

x1 = L * sinα
    y1 = L * cosα
    z1 = z1
複製代碼

下面咱們讓長方體繞着 z 軸逆時針旋轉β角度,那麼看圖能夠獲得如下信息:

  • P 點會繞着 z 軸逆時針旋轉β角度到達 P'(x2, y2, z2)
  • P' 在 xy 平面投影點 Q'(x2, y2, 0)
  • Q' 與 Q 在以 xy 平面原點爲圓心,半徑爲 L 的圓上
  • Q' 與原點連線與 Q 與原點連線之間的夾角爲 β
  • Q'與原點連線與 y 軸的角度是 α-β。

那麼在旋轉以後,P' 點的座標能夠表示爲:

4x4RotationExpression.png

使用矩陣來表示:

4x4RotationExpression2.png

從上面的分析能夠看出,爲了表達旋轉信息,咱們須要一個 3x3 的矩陣,在表達了旋轉信息的 3x3 矩陣中,咱們沒法表達平移信息,爲了同時表達平移和旋轉信息,在 3D 計算機圖形學中引入了齊次座標系,在齊次座標系中,使用四維矩陣表示一個點或向量:

HomogeneousPoint.png

加入一個變化是先繞着 z 軸旋轉 β 角度,再沿 x 軸平移Δx、沿 y 軸平移Δy、沿 z 軸平移Δz,咱們能夠用如下矩陣變化表示:

Homogeneous.png

最後,還有一種變化是縮放,在齊次座標系中只須要在前三列矩陣中某個位置添加一個係數便可,比較簡單,這裏不在展現矩陣變換。從上面能夠看出,爲了完整的表達一個物體在 3D 空間的變化,須要一個 4x4 矩陣。

ARCamera

ARCamera 中的主要參數是:

  • transform: 表示攝像頭相對於起始時的位置和旋轉信息。至於爲什麼是 4x4 矩陣,上面已經解釋過了。

    var transform: matrix_float4x4
    複製代碼
  • eulerAngles: 歐拉角,另外一種表示攝像頭的偏轉角度信息的方式,與咱們以前介紹的 3DOF 有關, 歐拉證實了一個物體的任何旋轉均可以分解爲 yaw、pitch、roll 三個方向的旋轉。

    var eulerAngles: vector_float3
    複製代碼
  • projectionMatrix: 投影矩陣,其實這個很相似於上面介紹的 Intrinsic Matrix,但不一樣點是,投影矩陣是將 AR-world 中的物體投影到屏幕上,因爲 AR-world 中採用的是齊次座標,因此這裏是一個 4x4 矩陣,投影矩陣除了決定 AR-world 中點應該映射到屏幕哪一個點以外,還決定了哪些範圍的點是不須要的,咱們看下圖:

ProjectionMatrix.png

上圖中 Field-of-View 和 View-Frustum 影響了投影矩陣的部分參數,對於超過 Field-of-View 或者超出 View-Frustum 範圍的點,ARKit 不會對其進行投影映射到屏幕。

此外,ARKit 還提供了一個接口讓咱們自定義 Field-of-View 和 View-Frustum:

func projectionMatrix(withViewportSize: CGSize, orientation: UIInterfaceOrientation, zNear: CGFloat, zFar: CGFloat)
複製代碼

追蹤質量:

世界追蹤須要必定的條件才能達到較好的效果,若是達不到所需的條件要求,那麼世界追蹤的質量會下降,甚至會沒法追蹤。較好的世界追蹤質量主要有如下三個依賴條件:

  • 運動傳感器不能中止工做。若是運動傳感器中止了工做,那麼就沒法拿到設備的運動信息。根據咱們以前提到的世界追蹤的工做原理,毫無疑問,追蹤質量會降低甚至沒法工做。

  • 真實世界的場景須要有必定特徵點可追蹤。世界追蹤須要不斷分析和追蹤捕捉到的圖像序列中特徵點,若是圖像是一面白牆,那麼特徵點很是少,那麼追蹤質量就會降低。

  • 設備移動速度不能過快。若是設備移動太快,那麼 ARKit 沒法分析出不一樣圖像幀之中的特徵點的對應關係,也會致使追蹤質量降低。

追蹤狀態

世界追蹤有三種狀態,咱們能夠經過 camera.trackingState 獲取當前的追蹤狀態。

TrackingState.png

從上圖咱們看到有三種追蹤狀態:

  • Not Available:世界追蹤正在初始化,還未開始工做。
  • Normal: 正常工做狀態。
  • Limited:限制狀態,當追蹤質量受到影響時,追蹤狀態可能會變爲 Limited 狀態。

與 TrackingState 關聯的一個信息是 ARCamera.TrackingState.Reason,這是一個枚舉類型:

  • case excessiveMotion:設備移動過快,沒法正常追蹤。
  • case initializing:正在初始化。
  • case insufficientFeatures:特徵過少,沒法正常追蹤。
  • case none:正常工做。

咱們能夠經過 ARSessionObserver 協議去獲取追蹤狀態的變化,比較簡單,能夠直接查看接口文檔,這裏不作深刻介紹。

到這裏,ARKit 中有關於世界追蹤的知識基本介紹完了,世界追蹤算是 ARKit 中核心功能了,若是理解了本部份內容,相信去看蘋果的接口文檔也會覺着很是容易理解。若是沒有看懂,能夠去看一下分享的錄播(本文開頭有連接)。

7、場景解析(Scene Understanding)

爲了方便閱讀,咱們首先將 Scene Understanding 從 ARKit 架構圖中抽取出:

SceneUnderstanding.png

場景解析主要功能是對現實世界的場景進行分析,解析出好比現實世界的平面等信息,可讓咱們把一些虛擬物體放在某些實物處。ARKit 提供的場景解析主要有平面檢測場景交互以及光照估計三種,下面逐個分析。

平面檢測(Plane detection)

主要功能:

  • ARKit 的平面檢測用於檢測出現實世界的水平面。

    PlaneDetection.png

    上圖中能夠看出,ARkit 檢測出了兩個平面,圖中的兩個三維座標系是檢測出的平面的本地座標系,此外,檢測出的平面是有一個大小範圍的。

  • 平面檢測是一個動態的過程,當攝像機不斷移動時,檢測到的平面也會不斷的變化。下圖中能夠看到當移動攝像機時,已經檢測到的平面的座標原點以及平面範圍都在不斷的變化。

    MultipleFramePlane.gif

  • 此外,隨着平面的動態檢測,不一樣平面也可能會合併爲一個新的平面。下圖中能夠看到已經檢測到的平面隨着攝像機移動合併爲了一個平面。

    PlaneMerge.gif

開啓平面檢測

開啓平面檢測很簡單,只須要在 run ARSession 以前,將 ARSessionConfiguration 的 planeDetection 屬性設爲 true 便可。

// Create a world tracking session configuration.
let configuration = ARWorldTrackingSessionConfiguration()
configuration.planeDetection = .horizontal
// Create a session.
let session = ARSession()
// Run.
session.run(configuration)
複製代碼

平面的表示方式

當 ARKit 檢測到一個平面時,ARKit 會爲該平面自動添加一個 ARPlaneAnchor,這個 ARPlaneAnchor 就表示了一個平面。

ARPlaneAnchor 主要有如下屬性:

  • alignment: 表示該平面的方向,目前只有 horizontal 一個可能值,表示這個平面是水平面。ARKit 目前沒法檢測出垂直平面。

    var alignment: ARPlaneAnchor.Alignment
    複製代碼
  • center: 表示該平面的本地座標系的中心點。以下圖中檢測到的平面都有一個三維座標系,center 所表明的就是座標系的原點:var center: vector_float3

    PlaneDetection.png

  • extent: 表示該平面的大小範圍。如上圖中檢測到的屏幕都有一個範圍大小。

    var extent: vector_float3
    複製代碼

ARSessionDelegate

當 ARKit 系統檢測到新平面時,ARKit 會自動添加一個 ARPlaneAnchor 到 ARSession 中。咱們能夠經過 ARSessionDelegate 獲取當前 ARSession 的 ARAnchor 改變的通知,主要有如下三種狀況:

  • 新加入了 ARAnchor

    func session(_ session: ARSession, didAdd anchors: [ARAnchor])
    複製代碼

    對於平面檢測來講,當新檢測到某平面時,咱們會收到該通知,通知中的 ARAnchor 數組會包含新添加的平面,其類型是 ARPlaneAnchor,咱們能夠像下面這樣使用:

    func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
        for anchor in anchors {
            if let anchor = anchor as? ARPlaneAnchor {
                print(anchor.center)
                print(anchor.extent)
            }
        }
    }
    複製代碼
  • ARAnchor 更新

    func session(_ session: ARSession, didUpdate anchors: [ARAnchor])
    複製代碼

    從上面咱們知道當設備移動時,檢測到的平面是不斷更新的,當平面更新時,會回調這個接口。

  • 刪除 ARAnchor

    func session(_ session: ARSession, didRemove anchors: [ARAnchor])
    複製代碼

    當手動刪除某個 Anchor 時,會回調此方法。此外,對於檢測到的平面來講,若是兩個平面進行了合併,則會刪除其中一個,此時也會回調此方法。

場景交互(Hit-testing)

Hit-testing 是爲了獲取當前捕捉到的圖像中某點擊位置有關的信息(包括平面、特徵點、ARAnchor 等)。

工做原理

先看一下原理圖:

Hit-testing.png

當點擊屏幕時,ARKit 會發射一個射線,假設屏幕平面是三維座標系中的 xy 平面,那麼該射線會沿着 z 軸方向射向屏幕裏面,這就是一次 Hit-testing 過程。這次過程會將射線遇到的全部有用信息返回,返回結果以離屏幕距離進行排序,離屏幕最近的排在最前面。

ResultType

ARFrame 提供了 Hit-testing 的接口:

func hitTest(_ point: CGPoint, types: ARHitTestResult.ResultType) -> [ARHitTestResult]
複製代碼

上述接口中有一個 types 參數,該參數表示這次 Hit-testing 過程須要獲取的信息類型。ResultType 有如下四種:

  • featurePoint 表示這次 Hit-testing 過程但願返回當前圖像中 Hit-testing 射線通過的 3D 特徵點。以下圖:

    HitFeaturePoint.gif

  • estimatedHorizontalPlane 表示這次 Hit-testing 過程但願返回當前圖像中 Hit-testing 射線通過的預估平面。預估平面表示 ARKit 當前檢測到一個多是平面的信息,但當前還沒有肯定是平面,因此 ARKit 尚未爲此預估平面添加 ARPlaneAnchor。以下圖:

    HitEstimatedPlane.gif

  • existingPlaneUsingExtent 表示這次 Hit-testing 過程但願返回當前圖像中 Hit-testing 射線通過的有大小範圍的平面。

    HitExistingPlaneUseExtent.gif

    上圖中,若是 Hit-testing 射線通過了有大小範圍的綠色平面,則會返回此平面,若是射線落在了綠色平面的外面,則不會返回此平面。

  • existingPlane 表示這次 Hit-testing 過程但願返回當前圖像中 Hit-testing 射線通過的無限大小的平面。

    HitExistingPlane.gif

    上圖中,平面大小是綠色平面所展現的大小,但 exsitingPlane 選項表示即便 Hit-testing 射線落在了綠色平面外面,也會將此平面返回。換句話說,將全部平面無限延展,只要 Hit-testing 射線通過了無限延展後的平面,就會返回該平面。

使用方法

下面給出使用 Hit-testing 的示例代碼:

// Adding an ARAnchor based on hit-test
let point = CGPoint(x: 0.5, y: 0.5)  // Image center

// Perform hit-test on frame.
let results = frame. hitTest(point, types: [.featurePoint, .estimatedHorizontalPlane])

// Use the first result.
if let closestResult = results.first {
    // Create an anchor for it.
    anchor = ARAnchor(transform: closestResult.worldTransform)
    // Add it to the session.
    session.add(anchor: anchor)
}
複製代碼

上面代碼中,Hit-testing 的 point(0.5, 0.5)表明屏幕的中心,屏幕左上角爲(0, 0),右下角爲(1, 1)。 對於 featurePoint 和 estimatedHorizontalPlane 的結果,ARKit 沒有爲其添加 ARAnchor,咱們可使用 Hit-testing 獲取信息後本身爲 ARSession 添加 ARAnchor,上面代碼就顯示了此過程。

光照估計(Light estimation)

LightEstimation.png

上圖中,一個虛擬物體茶杯被放在了現實世界的桌子上。

當週圍環境光線較好時,攝像機捕捉到的圖像光照強度也較好,此時,咱們放在桌子上的茶杯看起來就比較貼近於現實效果,如上圖最左邊的圖。可是當週圍光線較暗時,攝像機捕捉到的圖像也較暗,如上圖中間的圖,此時茶杯的亮度就顯得跟現實世界格格不入。

針對這種狀況,ARKit 提供了光照估計,開啓光照估計後,咱們能夠拿到當前圖像的光照強度,從而可以以更天然的光照強度去渲染虛擬物體,如上圖最右邊的圖。

光照估計基於當前捕捉到的圖像的曝光等信息,給出一個估計的光照強度值(單位爲 lumen,光強單位)。默認的光照強度爲 1000lumen,當現實世界較亮時,咱們能夠拿到一個高於 1000lumen 的值,相反,當現實世界光照較暗時,咱們會拿到一個低於 1000lumen 的值。

ARKit 的光照估計默認是開啓的,固然也能夠經過下述方式手動配置:

configuration.isLightEstimationEnabled = true
複製代碼

獲取光照估計的光照強度也很簡單,只須要拿到當前的 ARFrame,經過如下代碼便可獲取估計的光照強度:

let intensity = frame.lightEstimate?.ambientIntensity
複製代碼

8、渲染(Rendering)

Rendering.png

渲染是呈現 AR world 的最後一個過程。此過程將建立的虛擬世界、捕捉的真實世界、ARKit 追蹤的信息以及 ARKit 場景解析的的信息結合在一塊兒,渲染出一個 AR world。渲染過程須要實現如下幾點才能渲染出正確的 AR world:

  • 將攝像機捕捉到的真實世界的視頻做爲背景。
  • 將世界追蹤到的相機狀態信息實時更新到 AR world 中的相機。
  • 處理光照估計的光照強度。
  • 實時渲染虛擬世界物體在屏幕中的位置。

若是咱們本身處理這個過程,能夠看到仍是比較複雜的,ARKit 爲簡化開發者的渲染過程,爲開發者提供了簡單易用的使用 SceneKit(3D 引擎)以及 SpriteKit(2D 引擎)渲染的視圖ARSCNView以及ARSKView。固然開發者也可使用其餘引擎進行渲染,只須要將以上幾個信息進行處理融合便可。這裏只介紹下ARSCNView,對於使用其餘引擎渲染,可參考 WWDC2017-ARKit 最後的 Metal 渲染示例。

ARSCNView 的功能

ARSCNView 幫咱們作了以下幾件事情:

  • 將攝像機捕捉到的真實世界的視頻做爲背景。
  • 處理光照估計信息,不斷更新畫面的光照強度。
  • 將 SCNNode 與 ARAnchor 綁定,也就是說當添加一個 SCNNode 時,ARSCNView 會同時添加一個 ARAnchor 到 ARKit 中。
  • 不斷更新 SceneKit 中的相機位置和角度。
  • 將 SceneKit 中的座標系結合到 AR world 的座標系中,不斷渲染 SceneKit 場景到真實世界的畫面中。

關於 ARSCNView 的各個屬性這裏再也不進行一一介紹了,若是已經掌握了以前章節的內容,相信直接看 ARSCNView 的接口文檔不會有什麼問題。下面對 ARSCNViewDelegate 作一下簡單介紹。

ARSCNViewDelegate

咱們在介紹場景解析時,已經介紹過了 ARSessionDelegate,而 ARSCNViewDelegate 其實與 ARSessionDelegate 是有關係的,下面咱們再來看下 ARSessionDelegate 的三個回調:

  • 新加入了 ARAnchor

    func session(_ session: ARSession, didAdd anchors: [ARAnchor])
    複製代碼

    當 ARKit 新添加一個 ARAnchor 時,ARSessionDelegate 會收到上述回調。此時,ARKit 會回調 ARSCNViewDelegate 的下面一個方法詢問須要爲此新加的 ARAnchor 添加 SCNNode。

    func renderer(_ renderer: SCNSceneRenderer, nodeFor: ARAnchor) -> SCNNode?
    複製代碼

    當調用完上個方法以後,ARKit 會回調 ARSCNViewDelegate 的下面一個方法告知 delegate 已爲新添加的 ARAnchor 添加了一個 SCNNode。

    func renderer(_ renderer: SCNSceneRenderer, didAdd: SCNNode, for: ARAnchor)
    複製代碼
  • ARAnchor 更新

    func session(_ session: ARSession, didUpdate anchors: [ARAnchor])
    複製代碼

    當某個 ARAnchor 更新時,ARSessionDelegate 會收到上述回調,此時,ARSCNViewDelegate 會收到如下回調:

    func renderer(_ renderer: SCNSceneRenderer, willUpdate: SCNNode, for: ARAnchor)
    func renderer(_ renderer: SCNSceneRenderer, didUpdate: SCNNode, for: ARAnchor)
    複製代碼
  • 刪除 ARAnchor

    func session(_ session: ARSession, didRemove anchors: [ARAnchor])
    複製代碼

    當某個 ARAnchor 刪除時,ARSessionDelegate 會收到上述回調,此時,ARSCNViewDelegate 會收到如下回調:

    func renderer(_ renderer: SCNSceneRenderer, didRemove: SCNNode, for: ARAnchor)
    複製代碼

本章節沒有對渲染進行太過深刻的介紹,主要考慮到渲染過程當中用到的知識點,在以前大部分已經介紹過了,剩下的只是一些接口的使用方法,相信使用過蘋果各類 Kit 的開發者在掌握以上章節內容後,直接查看開發者文檔,並參考蘋果的官方 demo,使用起來應該不會遇到太多困難。

介紹完渲染以後,本文也就算是結束了。文中的內容涉及到了多個圖形學的有關知識,有些不太好理解的歡迎交流,或者查看分享的錄播視頻:ARKit 分享直播

歡迎轉載本文,請聲明原文地址及做者,多謝。

9、參考文檔

相關文章
相關標籤/搜索