ARKit 技術概述
蘋果在 WWDC2017 中推出了 ARKit,通過這個新框架可以看出蘋果未來會在 AR 方向不斷髮展,本着學習興趣,對此項新技術進行了學習,並在團隊進行了一次分享,利用業餘時間把幾周前分享的內容整理成文檔供大家交流學習。
本文並不是簡單的介紹 ARKit 中的 API 如何使用,而是在介紹 ARKit API 的同時附上了一些理論知識,所以有些內容可能會不太容易理解。筆者能力有限,文章若有誤請指出。團隊分享時進行了一次直播,若文中有不理解的內容,歡迎觀看下錄播:ARKit 分享直播。本文也附上分享的 PPT 地址:ARKit-keynote。
一、什麼是 AR?
AR 全稱 Augmented Reality(增強現實),是一種在攝像機捕捉到的真實世界中加入計算機程序創造的虛擬世界的技術。下圖是一個簡單的 AR 的 Demo:
ARChair.gif
在上圖中,椅子是計算機程序創建的虛擬世界的物體,而背景則是攝像機捕捉到的真實世界,AR 系統將兩者結合在一起。我們從上圖可以窺探出 AR 系統由以下幾個基礎部分組成:
捕捉真實世界:上圖中的背景就是真實世界,一般由攝像機完成。
虛擬世界:例如上圖中的椅子就是虛擬世界中的一個物體模型。當然,可以有很多物體模型,從而組成一個複雜的虛擬世界。
虛擬世界與現實世界相結合:將虛擬世界渲染到捕捉到的真實世界中。
世界追蹤:當真實世界變化時(如上圖中移動攝像機),要能追蹤到當前攝像機相對於初始時的位置、角度變化信息,以便實時渲染出虛擬世界相對於現實世界的位置和角度。
場景解析:例如上圖中可以看出椅子是放在地面上的,這個地面其實是 AR 系統檢測出來的。
與虛擬世界互動:例如上圖中縮放、拖動椅子。(其實也屬於場景解析的範疇)
根據上面的描述,我們可以得出 AR 系統的大致結構圖:
ARKitSystem.png
Note:這裏只介紹基於計算機顯示器的 AR 系統實現方案,此外還有光學透視式和視頻透視式方案。可參考增強現實-組成形式。
二、ARKit 簡介
ARKitLogo.png
ARKit 是蘋果 WWDC2017 中發佈的用於開發iOS平臺 AR 功能的框架。ARKit 爲上一節中提到的 AR 系統架構中各個部分都提供了實現方案,並且爲開發者提供了簡單便捷的 API,使得開發者更加快捷的開發 AR 功能。
ARKit 的使用需要一定的軟硬件設施:
軟件:
開發工具:Xcode9
iOS11
MacOS 10.12.4 及以上版本(爲了支持 Xcode9)
硬件:
處理器爲 A9 及以上的 iPhone 或 iPad 設備(iPhone 6s 爲 A9 處理器)
下面幾節中,我們將逐步介紹 ARKit 中 AR 系統的各個組成部分。
三、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 較爲簡單,但看到上面整個框架後,對於初識整個體系的開發者來說,還是會覺着有些龐大。沒關係,後面幾節會對每個模塊進行單獨的介紹,當讀完最後時,再回頭來看這個架構圖,或許會更加明瞭一些。
四、構建虛擬世界
我們將 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 中我們可以這樣創建一個三維座標:
letposition =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 中我們可以使用如下方式創建一個攝像機:
letscene =SCNScene()letcameraNode =SCNNode()letcamera =SCNCamera()cameraNode.camera = cameracameraNode.position =SCNVector3(x:0, y:0, z:0)scene.rootNode.addChildNode(cameraNode)
SCNView
最後,我們需要一個 View 來將 SCNScene 中的內容渲染到顯示屏幕上,這個工作由 SCNView 完成。這一步其實很簡單,只需要創建一個 SCNView 實例,然後將 SCNView 的 scene 屬性設置爲剛剛創建的 SCNScene,然後將 SCNView 添加到 UIKit 的 view 或 window 上即可。示例代碼如下:
letscnView =SCNView()scnView.scene = scenevc.view.addSubview(scnView)scnView.frame = vc.view.bounds
五、捕捉真實世界(Capture Real World)
捕捉真實世界就是爲了將我們現實世界的場景作爲 ARKit 顯示場景的背景。爲了方便閱讀,我們首先將 Capture Real World 從 ARKit 架構圖中抽取出:
CaptureRealWorld.png
ARSession
如果我們想要使用 ARKit,我們必須要創建一個 ARSession 對象並運行 ARSession。基本步驟如下:
// 創建一個 ARSessionConfiguration.// 暫時無需在意 ARWorldTrackingSessionConfiguration.letconfiguration =ARWorldTrackingSessionConfiguration()// Create a session.letsession =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 的作用過程,至於爲何這麼映射,則是相機原理的內容了,由於水平有限,就不做介紹了。如果不太好理解,我們這樣簡單理解爲相機使用這個矩陣就可以將空間中的某個點映射到二維成像平面的一個點。
六、世界追蹤(World Tracking)
在第一部分 AR 系統介紹時,我們看到虛擬椅子是放在地面上的,當我們移動時可以看到不同角度,我們也可以移動椅子,這些功能的實現都離不開世界追蹤。總結來說,世界追蹤用來爲真實世界與虛擬世界結合提供有效信息,以便我們能在真實世界中看到一個更加真實的虛擬世界。
爲了方便閱讀,我們首先將 World Tracking 從 ARKit 架構圖中抽取出:
WorldTracking.png
下面我們分析一下 ARKit 中與世界追蹤相關的技術以及類。
ARSession
如果我們想要使用 ARKit,我們必須要創建一個 ARSession 對象並運行 ARSession。基本步驟如下:
// 創建一個 ARSessionConfiguration.// 暫時無需在意 ARWorldTrackingSessionConfiguration.letconfiguration =ARWorldTrackingSessionConfiguration()// Create a session.letsession =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?
DOF
自由度(DOF,Degree Of Freedom)表示描述系統狀態的獨立參數的個數。6DOF 主要包括如下 6 個參數:
DOF.png
平移:
上下移動
左右移動
前後移動
旋轉:
Yawing
Pitching
Rolling
其中 3DOF 中只包含旋轉中的三個參數,平移的幾個參數其實很好理解,爲了更形象的理解旋轉參數,我們看下面幾個動圖:
Yawing 效果:
yaw.gif
Pitching 效果:
pitch.gif
Rolling 效果:
roll.gif
3DOF 與 6DOF 追蹤的效果差異
ARWorldTrackingSessionConfiguration使用 3 個旋轉參數和 3 個平移參數來追蹤物理系統的狀態,
ARSessionConfiguration使用 3 個旋轉參數來追蹤物理系統的狀態。那麼兩者有什麼效果差別?
我們下面看兩個圖,下圖一使用 3DOF 的ARSessionConfiguration進行世界追蹤,下圖二使用 6DOF 的ARWorldTrackingSessionConfiguration進行世界追蹤:
3DOF.gif
6DOF.gif
上面兩個圖,都是先對設備進行旋轉,再對設備進行平移。
那麼,對於 3DOF 追蹤,我們旋轉設備時可以看到虛擬的飛機視角有所變化;但當平移時,我們可以看到飛機是隨着設備進行移動的。
對於 6DOF 追蹤,我們旋轉設備時可以看到虛擬的飛機視角有所變化(這點與 3DOF 追蹤沒有區別);平移時,我們可以看到飛機的不同位置,例如向上平移看到了飛機的上表面,圍着飛機平移可以看到飛機的四周,而 3DOF 沒有提供這種平移的追蹤。如果還是不理解兩者區別,可以看動圖的後半段,效果差異其實是非常明顯的。
判斷當前設備是否支持某類 SessionConfiguration
classvarisSupported:Bool
示例代碼如下:
ifARWorldTrackingSessionConfiguration.isSupported { configuration =ARWorldTrackingSessionConfiguration()}else{ configuration =ARSessionConfiguration()}
關於ARSessionConfiguration我們就介紹到這裏,下面我們看一下 ARFrame。
ARFrame
ARFrame 中包含有世界追蹤過程獲取的所有信息,ARFrame 中與世界追蹤有關的信息主要是:anchors 和 camera:
camera: 含有攝像機的位置、旋轉以及拍照參數等信息。
varcamera: [ARCamera]
ahchors: 代表了追蹤的點或面。
varanchors: [ARAnchor]
至於 ARCamera 和 ARAnchor 是什麼?下面分別進行介紹。
ARAnchor
ARAnchor.png
我們可以把 ARAnchor(AR 錨點) 理解爲真實世界中的某個點或平面,anchor 中包含位置信息和旋轉信息。拿到 anchor 後,可以在該 anchor 處放置一些虛擬物體。對於 ARAnchor 有如下幾種操作:
我們可以使用 ARSession 的 add/remove 方法進行手動添加或刪除 Anchor。例如,我們添加了一個虛擬物體到 ARKit 中,在之後的某個時候我們想要在剛纔的虛擬物體上面再放置一個東西,那麼我們可以爲這個虛擬物體添加一個 anchor 到 ARSession 中,這樣在後面可以通過 ARSession 獲取到這個虛擬物體的錨點信息。
通過 ARSession 獲取當前幀的所有 anchors:
letanchors = session.currentFrame.anchors
ARKit 自動添加 anchors。例如,ARKit 檢測到了一個平面,ARKit 會爲該平面創建一個 ARPlaneAnchor 並添加到 ARSession 中。
ARSessionDelegate 可以監聽到添加、刪除和更新 ARAnchor 的通知。
ARAnchor 中的主要參數是 transform,這個參數是一個 4x4 的矩陣,矩陣中包含了 anchor 偏移、旋轉和縮放信息。
vartransform: 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 矩陣,上面已經解釋過了。
vartransform: matrix_float4x4
eulerAngles: 歐拉角,另一種表示攝像頭的偏轉角度信息的方式,與我們之前介紹的 3DOF 有關,
歐拉證明了一個物體的任何旋轉都可以分解爲 yaw、pitch、roll 三個方向的旋轉。
vareulerAngles: 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:
funcprojectionMatrix(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 中核心功能了,如果理解了本部分內容,相信去看蘋果的接口文檔也會覺着非常容易理解。如果沒有看懂,可以去看一下分享的錄播(本文開頭有鏈接)。
七、場景解析(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.letconfiguration =ARWorldTrackingSessionConfiguration()configuration.planeDetection = .horizontal// Create a session.letsession =ARSession()// Run.session.run(configuration)
平面的表示方式
當 ARKit 檢測到一個平面時,ARKit 會爲該平面自動添加一個ARPlaneAnchor,這個ARPlaneAnchor就表示了一個平面。
ARPlaneAnchor主要有以下屬性:
alignment: 表示該平面的方向,目前只有 horizontal 一個可能值,表示這個平面是水平面。ARKit 目前無法檢測出垂直平面。
varalignment:ARPlaneAnchor.Alignment
center: 表示該平面的本地座標系的中心點。如下圖中檢測到的平面都有一個三維座標系,center 所代表的就是座標系的原點:var center: vector_float3
PlaneDetection.png
extent: 表示該平面的大小範圍。如上圖中檢測到的屏幕都有一個範圍大小。
varextent: vector_float3
ARSessionDelegate
當 ARKit 系統檢測到新平面時,ARKit 會自動添加一個 ARPlaneAnchor 到 ARSession 中。我們可以通過 ARSessionDelegate 獲取當前 ARSession 的 ARAnchor 改變的通知,主要有以下三種情況:
新加入了 ARAnchor
funcsession(_session: ARSession, didAdd anchors: [ARAnchor])
對於平面檢測來說,當新檢測到某平面時,我們會收到該通知,通知中的 ARAnchor 數組會包含新添加的平面,其類型是 ARPlaneAnchor,我們可以像下面這樣使用:
funcsession(_session: ARSession, didAdd anchors: [ARAnchor]){foranchorinanchors {ifletanchor = anchoras?ARPlaneAnchor{print(anchor.center)print(anchor.extent) } }}
ARAnchor 更新
funcsession(_session: ARSession, didUpdate anchors: [ARAnchor])
從上面我們知道當設備移動時,檢測到的平面是不斷更新的,當平面更新時,會回調這個接口。
刪除 ARAnchor
funcsession(_session: ARSession, didRemove anchors: [ARAnchor])
當手動刪除某個 Anchor 時,會回調此方法。此外,對於檢測到的平面來說,如果兩個平面進行了合併,則會刪除其中一個,此時也會回調此方法。
場景交互(Hit-testing)
Hit-testing 是爲了獲取當前捕捉到的圖像中某點擊位置有關的信息(包括平面、特徵點、ARAnchor 等)。
工作原理
先看一下原理圖:
Hit-testing.png
當點擊屏幕時,ARKit 會發射一個射線,假設屏幕平面是三維座標系中的 xy 平面,那麼該射線會沿着 z 軸方向射向屏幕裏面,這就是一次 Hit-testing 過程。此次過程會將射線遇到的所有有用信息返回,返回結果以離屏幕距離進行排序,離屏幕最近的排在最前面。
ResultType
ARFrame 提供了 Hit-testing 的接口:
funchitTest(_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-testletpoint =CGPoint(x:0.5, y:0.5)// Image center// Perform hit-test on frame.letresults = frame. hitTest(point, types: [.featurePoint, .estimatedHorizontalPlane])// Use the first result.ifletclosestResult = 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,通過以下代碼即可獲取估計的光照強度:
letintensity = frame.lightEstimate?.ambientIntensity
八、渲染(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
funcsession(_session: ARSession, didAdd anchors: [ARAnchor])
當 ARKit 新添加一個 ARAnchor 時,ARSessionDelegate 會收到上述回調。此時,ARKit 會回調 ARSCNViewDelegate 的下面一個方法詢問需要爲此新加的 ARAnchor 添加 SCNNode。
funcrenderer(_renderer: SCNSceneRenderer, nodeFor: ARAnchor)->SCNNode?
當調用完上個方法之後,ARKit 會回調 ARSCNViewDelegate 的下面一個方法告知 delegate 已爲新添加的 ARAnchor 添加了一個 SCNNode。
funcrenderer(_renderer: SCNSceneRenderer, didAdd: SCNNode,for: ARAnchor)
ARAnchor 更新
funcsession(_session: ARSession, didUpdate anchors: [ARAnchor])
當某個 ARAnchor 更新時,ARSessionDelegate 會收到上述回調,此時,ARSCNViewDelegate 會收到以下回調:
funcrenderer(_renderer: SCNSceneRenderer, willUpdate: SCNNode,for: ARAnchor)funcrenderer(_renderer: SCNSceneRenderer, didUpdate: SCNNode,for: ARAnchor)
刪除 ARAnchor
funcsession(_session: ARSession, didRemove anchors: [ARAnchor])
當某個 ARAnchor 刪除時,ARSessionDelegate 會收到上述回調,此時,ARSCNViewDelegate 會收到以下回調:
funcrenderer(_renderer: SCNSceneRenderer, didRemove: SCNNode,for: ARAnchor)
本章節沒有對渲染進行太過深入的介紹,主要考慮到渲染過程中用到的知識點,在之前大部分已經介紹過了,剩下的只是一些接口的使用方法,相信使用過蘋果各種 Kit 的開發者在掌握以上章節內容後,直接查看開發者文檔,並參考蘋果的官方 demo,使用起來應該不會遇到太多困難。
介紹完渲染之後,本文也就算是結束了。文中的內容涉及到了多個圖形學的有關知識,有些不太好理解的歡迎交流,或者查看分享的錄播視頻:ARKit 分享直播
歡迎轉載本文,請聲明原文地址及作者,多謝。
九、參考文檔
Wiki: Degrees of freedom(mechanics)
Raywenderlich Apple Game Frameworks-SceneKit series
作者:程序員鈣片吃多了
鏈接:http://www.jianshu.com/p/7faa4a3af589