0. 前言
作為一名剛入門的 iOS 開發者,前陣子稍稍研究了一下最新發布的 ARKit,然後結合幾個其他開源專案做成了一個 ARGitHubCommits。前天在上海第 8 次 T 沙龍上分享了一個《ARKit 初探》的 topic,現在將它寫成文章,以便瀏覽。
1. ARKit 簡介
下面是蘋果開發者官網 ARKit 頁面的一段介紹:
iOS 11 引入了新的 ARKit 框架,讓您輕鬆建立無可比擬的 iPhone 和 iPad 擴增實境體驗。 通過將數字物件和資訊與您周圍的環境相融合,ARKit 為 App 解開了螢幕之縛,帶領著它們跨越螢幕的界限,讓它們以全新的方式與現實世界交流互動。
可見,蘋果在 AR 的市場上應該是做了很多準備。不僅有本文要介紹的 ARKit,在最新發布的 iPhone X 中也對攝像頭做了優化,配備了前置景深攝像頭,將 AR 和麵部識別結合起來。所以,AR 可能將會是未來幾年內的一個重要發展方向。
2. 裝置要求
蘋果在硬體上也做了一些努力。我們可以從官網的介紹中得出以下幾個資訊:
- 建立在優秀的硬體設施和演算法上,ARKit 採集的現實世界資料相對來說比較精準(比 Google 的 ARCore 要好些)。
- 可以利用 ARKit 來探測水平面,然後可以在水平面上放置小的物體。
- ARKit 會根據周圍光線的亮度自動調節虛擬物體的亮度和陰影、紋理等資訊。
當然,要執行 ARKit,在硬體上也有一些要求。一定是要具備 A9 及以上的處理器(iPhone 6s 為 A9 處理器)的裝置才可以執行 AR。軟體上,如果要開發 ARKit App,那麼要有 Xcode 9 和 iOS 11 SDK。
當你做好了一切準備,那就讓我們進入 ARKit 的世界!
3. AR 工作流程
上圖解釋的是 ARKit 的工作流程。其中藍色表示 ARKit 負責的部分,綠色表示 SceneKit 負責的部分。當然,建立虛擬世界也可以使用其他的框架,比如 SpriteKit、Metal,本文將以 SceneKit 為例子進行講解。
- 首先,ARKit 利用攝像頭拍攝現實場景的畫面,然後 SceneKit 用來建立虛擬世界。
- 建立好了以後,ARKit 負責將現實世界和虛擬世界的資訊融合,並渲染出一個 AR 世界。
- 在渲染的同時,ARKit 要負責以下三件事:
- 維持世界追蹤 指的是當你移動攝像頭,要去獲取新的現實世界的資訊。
- 進行場景解析 指的是解析現實世界中有無特徵點、平面等關鍵資訊。
- 處理與虛擬世界的互動 指的是當使用者點選或拖動螢幕時,處理有沒有點選到虛擬物體或者要不要進行新增/刪除物體的操作。
由此可見,ARKit 主要做的事是:捕捉現實世界資訊、將現實和虛擬世界混合渲染、並且時刻處理新的資訊或者進行互動。
理解了 AR 的工作流程後,讓我們來看看 ARKit 中一些重要的類的職責。
4. 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 思路
有了以上的知識基礎,我們可以用以下思路來構建這個專案:
- 獲取 GitHub 的 Commits 資料。
- 建立 ARSCNView,開始 ARSession。
- 提示使用者移動手機,探測水平面。
- 探測成功後,在 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