iOS ARKit 教程:不觸摸屏幕,用空氣中的手勢(shì)作畫

iOS ARKit 教程:不觸摸屏幕,用空氣中的手勢(shì)作畫



本文翻譯自 iOS ARKit Tutorial: Drawing in the Air with Bare Fingers,原作者是 Osama AbdelKarim AboulHassan。

最近,Apple 發(fā)布了名為 ARKit 的全新增強(qiáng)現(xiàn)實(shí)(AR)庫(kù)。在許多人看來(lái),這只是另一個(gè)好的 AR 庫(kù)而已,而不是什么值得關(guān)注的革命性技術(shù)。但如果你了解過(guò)去幾年 AR 的發(fā)展,就不會(huì)如此草率地下結(jié)論。

在本文中會(huì)用 iOS ARKit 創(chuàng)建一個(gè)好玩的項(xiàng)目。用戶把手指放在桌子上,就好像握著一只筆,點(diǎn)擊拇指甲就可以開始繪畫。完成后,用戶還可以把畫作轉(zhuǎn)成 3D 對(duì)象,就像下面的動(dòng)圖展示的那樣。此項(xiàng)目的完整源碼可以在 GitHub 上下載。

動(dòng)圖

為何現(xiàn)在要關(guān)注 ARKit?

每個(gè)有經(jīng)驗(yàn)的開發(fā)者應(yīng)該都知道 AR 不是什么新概念了。AR 的第一次大規(guī)模開發(fā)要追溯到網(wǎng)絡(luò)攝像頭剛開始應(yīng)用的時(shí)期。那時(shí)的 app 通常用于對(duì)臉做一些變化。然而,人們很快就發(fā)現(xiàn)把臉變成兔子并不是什么迫切的需求,很快這波勢(shì)頭就降下去了!

我相信 AR 一直以來(lái)都有兩個(gè)關(guān)鍵技術(shù)沒(méi)有實(shí)現(xiàn),導(dǎo)致它沒(méi)那么實(shí)用:可用性和沉浸性。如果你觀察過(guò)其它有關(guān) AR 的不實(shí)鼓吹,就會(huì)發(fā)現(xiàn)這兩點(diǎn)。舉個(gè)例子,當(dāng)開發(fā)者可以訪問(wèn)手機(jī)攝像頭的時(shí)候,就出現(xiàn)了一波對(duì) AR 的鼓吹。除了強(qiáng)勢(shì)回歸的偉大的變兔子工具之外,還有一波 app 可以把 3D 對(duì)象放到打印的二維碼上。但這個(gè)概念從來(lái)從來(lái)都沒(méi)有火過(guò)。這并不是增強(qiáng)現(xiàn)實(shí),只是增強(qiáng)的二維碼而已。

然后 Google 用一次科技神話震驚了我們,Google Glass。兩年過(guò)去,這個(gè)神奇的產(chǎn)品本應(yīng)來(lái)到了我們的生活,但現(xiàn)實(shí)卻是已經(jīng)死掉了!許多批評(píng)家分析 Google Glass 失敗的原因,歸咎于從社會(huì)角度到 Google 發(fā)布產(chǎn)品時(shí)的無(wú)聊方式等等方面。但在本文中,我們只關(guān)心一個(gè)原因 —— 在環(huán)境中的沉浸性。雖然 Google Glass 解決了可用性問(wèn)題,但它仍然只是在空氣中繪制 2D 圖像而已。

像微軟、Facebook 和 Apple 這樣的科技泰斗都從這次深刻的教訓(xùn)中吸取了經(jīng)驗(yàn)。2017 年七月,Apple 發(fā)布了美妙的 iOS ARKit 庫(kù),制造沉浸性成為了它的優(yōu)先任務(wù)。需要舉著手機(jī)使用對(duì)用戶體驗(yàn)仍然有很大的影響,但 Google Glass 的教訓(xùn)告訴我們,硬件不是問(wèn)題。

我相信很快就要進(jìn)入一波新的 AR 熱潮,并在在這個(gè)關(guān)鍵節(jié)點(diǎn)上,它可能會(huì)最終找到的合適的市場(chǎng)。歷史課就上到這里,下面開始寫代碼,實(shí)際了解 Apple 的增強(qiáng)現(xiàn)實(shí)!

ARKit 的沉浸功能

ARKit 提供了兩個(gè)主要功能;第一個(gè)是 3D 空間里的相機(jī)位置,第二個(gè)是水平面檢測(cè)。前者的意思是,ARKit 假定用戶的手機(jī)是在真實(shí)的 3D 空間里移動(dòng)的攝像機(jī),所以在任意位置放置 3D 虛擬對(duì)象都會(huì)錨定在真實(shí) 3D 空間中對(duì)應(yīng)的點(diǎn)上。對(duì)于后者來(lái)說(shuō),ARKit 可以檢測(cè)諸如桌子這樣的水平面,然后就可以在上面放置對(duì)象。

那么 ARKit 是怎么做到的呢?這是一項(xiàng)叫做視覺(jué)慣性里程計(jì)(VIO)的技術(shù)。不要擔(dān)心,就像創(chuàng)業(yè)者樂(lè)于人們發(fā)現(xiàn)他們的創(chuàng)業(yè)公司名稱背后的秘密一樣,研究人員也會(huì)樂(lè)于人們破譯他們命名的發(fā)明中的所有術(shù)語(yǔ)——所以讓他們開心吧,我們繼續(xù)往前看。

VIO 這項(xiàng)技術(shù)融合了攝像頭幀畫面和運(yùn)動(dòng)傳感器來(lái)追蹤設(shè)備在 3D 空間里的位置。從攝像頭幀畫面中追蹤運(yùn)動(dòng)是通過(guò)檢測(cè)特征點(diǎn)實(shí)現(xiàn)的,也可以說(shuō)是高對(duì)比度圖像中的邊緣點(diǎn)——就像藍(lán)色花瓶和白色桌子之間的邊緣。通過(guò)檢測(cè)兩幀畫面間特征點(diǎn)的相對(duì)移動(dòng)距離,就可以估算出設(shè)備在 3D 空間里的位置。所以如果用戶面對(duì)一面缺少特征點(diǎn)的白墻,或者設(shè)備移動(dòng)過(guò)快導(dǎo)致畫面模糊,ARKit 都會(huì)無(wú)法正常工作。

上手 iOS 中的 ARKit

寫作本文時(shí),ARKit 是 iOS 11 的一部分,仍然在 beta 版本。所以要上手的話,你需要在 iPhone 6s 或更新的設(shè)備上下載 iOS 11 Beta,當(dāng)然還有新的 Xcode Beta。我們可以用 New > Project > Augmented Reality App 來(lái)新建一個(gè) ARKit 項(xiàng)目。但是我發(fā)現(xiàn)使用官方 Apple ARKit 示例開始會(huì)更方便,它提供了一些必要的代碼塊,尤其對(duì)于平面檢測(cè)很有幫助。所以,從這個(gè)示例代碼開始吧,我會(huì)首先解析里面的關(guān)鍵點(diǎn),然后將其修改為我們自己的項(xiàng)目。

首先,我們要確定使用哪個(gè)引擎。ARKit 可用于 Sprite SceneKit 或 Meta。在 Apple ARKit 示例里,我們是用的是 iOS SceneKit,由 Apple 提供的 3D 引擎。接下來(lái),我們需要設(shè)置用于渲染 3D 對(duì)象的視圖。添加一個(gè) ARSCNView 類型的視圖即可。

ARSCNView 是 SceneKit 主視圖 SCNView 的子類,但它擴(kuò)展了一些有用的功能。它會(huì)將設(shè)備攝像頭的實(shí)時(shí)視頻流渲染為場(chǎng)景背景,并會(huì)自動(dòng)匹配 SceneKit 空間和真實(shí)世界,假定設(shè)備是這個(gè)世界里的移動(dòng) camera。

ARSCNView 本身不會(huì)做 AR 處理,但它需要 AR session 對(duì)象來(lái)管理設(shè)備攝像頭和運(yùn)動(dòng)處理。所以,從賦值一個(gè)新的 session 開始:

self.session = ARSession()

sceneView.session = session

sceneView.delegate = self

setupFocusSquare()

上面的最后一行代碼添加了一個(gè)視覺(jué)指示,讓用戶直觀地了解平面檢測(cè)狀態(tài)。Focus Square 是示例代碼提供的,而不是 ARKit 庫(kù),這也是我們用示例代碼上手的重要原因之一。在示例代碼里的 readme 文件里可以找到更多信息。下面這張圖顯示了映射在桌子上的 focus square:

下一步是啟動(dòng) ARKit session。每次 view appears 時(shí)都要重啟 session,因?yàn)橥V棺粉櫽脩艉螅暗?session 信息就沒(méi)有價(jià)值了。所以,在 viewDidAppear 里啟動(dòng) session:

override func viewDidAppear(_ animated: Bool) {

let configuration = ARWorldTrackingSessionConfiguration()

configuration.planeDetection = .horizontal

session.run(configuration, options: [.resetTracking, .removeExistingAnchors])

}

在上面的代碼里,設(shè)置了 ARKit session configuration 來(lái)檢測(cè)平面。寫作本文時(shí),Apple 沒(méi)有提供除此以外的選項(xiàng)。但很明顯,這暗示了未來(lái)可以檢測(cè)到更復(fù)雜的對(duì)象。然后,開始運(yùn)行 session 并確保重置了追蹤。

最后,我們需要在攝像頭位置(即實(shí)際的設(shè)備角度和位置)改變時(shí)更新 Focus Square。可以在 SCNView 的 renderer delegate 函數(shù)里實(shí)現(xiàn),每次 3D 引擎將要渲染新的幀時(shí)都會(huì)調(diào)用:

func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {

updateFocusSquare()

}

此時(shí)運(yùn)行 app,就可以看見攝像頭視頻流中位于檢測(cè)到的水平面上的 focus square 了。在下一個(gè)部分,我們解釋平面是如何被檢測(cè)到的,以及如何對(duì)應(yīng)放置 focus square。

平面檢測(cè)

ARKit 可以檢測(cè)新平面,更新現(xiàn)有平面,或是移除它們。為了便于處理平面,我們會(huì)創(chuàng)建一些虛擬的 SceneKit node 來(lái)管理平面的位置信息以及對(duì) focus square 的引用。平面是定義在 X 和 Z 方向上的,Y 則是表面的法線,也就是說(shuō),如果想在平面上繪制一個(gè) node 的話,應(yīng)保持該 node 的 Y 值與平面相同。

平面檢測(cè)是通過(guò) ARKit 提供的回調(diào)函數(shù)來(lái)完成的。舉個(gè)例子,下面的回調(diào)函數(shù)會(huì)在每次檢測(cè)到新平面時(shí)調(diào)用:

var planes = [ARPlaneAnchor: Plane]()

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {

if let planeAnchor = anchor as? ARPlaneAnchor {

serialQueue.async {

self.addPlane(node: node, anchor: planeAnchor)

self.virtualObjectManager.checkIfObjectShouldMoveOntoPlane(anchor: planeAnchor, planeAnchorNode: node)

}

}

}

func addPlane(node: SCNNode, anchor: ARPlaneAnchor) {

let plane = Plane(anchor)

planes[anchor] = plane

node.addChildNode(plane)

}

...

class Plane: SCNNode {

var anchor: ARPlaneAnchor

var focusSquare: FocusSquare?

init(_ anchor: ARPlaneAnchor) {

self.anchor = anchor

super.init()

}

...

}

回調(diào)函數(shù)給我們提供了兩個(gè)參數(shù),anchor 和 node。node 是一個(gè)普通的 SceneKit node,角度和位置與平面完全相同。它沒(méi)有幾何體,所以是可不見的。我們用它來(lái)添加自己的平面 node,同樣也是不可見的,但會(huì)管理 anchor 里有關(guān)平面角度和位置的信息。

所以位置和角度是如何存儲(chǔ)在 ARPlaneAnchor 中的呢?位置、角度和比例都被編碼在 4x4 矩陣中。如果我可以讓你學(xué)會(huì)一個(gè)數(shù)學(xué)概念的話,毫無(wú)疑問(wèn)就是矩陣了。不過(guò)沒(méi)關(guān)系,可以把 4x4 矩陣想象為:一個(gè)包含 4x4 浮點(diǎn)數(shù)字的 2D 智能 2D 數(shù)組。用某種特定的方式將這些數(shù)字乘以它在局部空間中的 3D 頂點(diǎn) v1 就會(huì)得到新的 3D 頂點(diǎn) v2,即 v1 在世界空間中的表示。所以,如果局部空間里的 v1 = (1, 0, 0),并且希望把它放在世界空間中 x = 100 的位置,相對(duì)于世界空間的 v2 就會(huì)等于 (101, 0, 0)。當(dāng)然,如果還要添加繞軸旋轉(zhuǎn),背后的數(shù)學(xué)就會(huì)變得更加復(fù)雜,但好消息是我們沒(méi)必要理解這背后的原理(我強(qiáng)烈建議看看這篇文章中的相關(guān)部分,里面有關(guān)于此概念的深入解釋)。

checkIfObjectShouldMoveOntoPlane 會(huì)檢查是否已經(jīng)繪制了對(duì)象,以及有沒(méi)有對(duì)象的 y 坐標(biāo)匹配新檢測(cè)到的平面。

現(xiàn)在,回到上一部分描述的 updateFocusSquare()。我們想要保證 focus square 在屏幕中心,并映射到檢測(cè)到的距離最近的平面上。使用如下代碼實(shí)現(xiàn):

func updateFocusSquare() {

let worldPos = worldPositionFromScreenPosition(screenCenter, self.sceneView)

self.focusSquare?.simdPosition = worldPos

}

func worldPositionFromScreenPosition(_ position: CGPoint, in sceneView: ARSCNView) -> float3? {

let planeHitTestResults = sceneView.hitTest(position, types: .existingPlaneUsingExtent)

if let result = planeHitTestResults.first {

return result.worldTransform.translation

}

return nil

}

sceneView.hitTest 會(huì)搜索對(duì)應(yīng)屏幕上的 2D 點(diǎn)的真實(shí)世界平面,方式是映射這個(gè) 2D 點(diǎn)到下方最近的平面上。result.worldTransform 是一個(gè) 4x4 矩陣,具有檢測(cè)到的平面的所有 transform 信息,而 result.worldTransform.translation 則用于只取出位置。

現(xiàn)在我們已經(jīng)具備所需的全部信息,以便根據(jù)屏幕上的 2D 點(diǎn)向檢測(cè)到的平面上放置 3D 對(duì)象。所以下面開始繪制吧。

繪圖

首先解釋一下如何利用計(jì)算機(jī)視覺(jué)跟隨人的手指來(lái)繪制圖形。繪制是通過(guò)檢測(cè)手指移動(dòng)的每個(gè)位置完成的,在對(duì)應(yīng)的位置放置一個(gè)頂點(diǎn),并將每個(gè)頂點(diǎn)與前面的頂點(diǎn)相連。頂點(diǎn)可以通過(guò)一條簡(jiǎn)單的線連接,如果需要平滑的輸出的話,則可以通過(guò) Bezier 曲線完成。

為了簡(jiǎn)單起見,我們會(huì)使用一些原生的繪圖方法。對(duì)于手指的新位置,我們會(huì)在被檢測(cè)到的平面上放置一個(gè)非常小的圓角 box,高度幾乎為零。看起來(lái)就像一個(gè)點(diǎn)一樣。用戶完成繪制并點(diǎn)擊 3D 按鈕后,則會(huì)根據(jù)用戶手指的移動(dòng)改變放置對(duì)象的高度。

下面的代碼展示了用于表示點(diǎn)的 PointNode 類:

let POINT_SIZE = CGFloat(0.003)

let POINT_HEIGHT = CGFloat(0.00001)

class PointNode: SCNNode {

static var boxGeo: SCNBox?

override init() {

super.init()

if PointNode.boxGeo == nil {

PointNode.boxGeo = SCNBox(width: POINT_SIZE, height: POINT_HEIGHT, length: POINT_SIZE, chamferRadius: 0.001)

// 設(shè)置點(diǎn)的材質(zhì)

let material = PointNode.boxGeo!.firstMaterial

material?.lightingModel = SCNMaterial.LightingModel.blinn

material?.diffuse.contents? = UIImage(named: "wood-diffuse.jpg")

material?.normal.contents? = UIImage(named: "wood-normal.png")

material?.specular.contents = UIImage(named: "wood-specular.jpg")

}

let object = SCNNode(geometry: PointNode.boxGeo!)

object.transform = SCNMatrix4MakeTranslation(0.0, Float(POINT_HEIGHT) / 2.0, 0.0)

self.addChildNode(object)

}

. . .

在上面的代碼把幾何體沿 y 軸移動(dòng)了高度的一半。這樣做是為了確保對(duì)象的底部總是處于 y = 0 的位置,這樣看起來(lái)就像在平面上一樣。

下面,在 SceneKit 的 renderer 回調(diào)函數(shù)中,使用 PointNode 類繪制一個(gè)指示來(lái)表示筆尖。如果開啟了繪圖的話,就會(huì)在那個(gè)位置放一個(gè)點(diǎn)下去,如果開啟的是 3D 模式,則會(huì)將繪圖抬高,變成 3D 結(jié)構(gòu)體:

func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {

updateFocusSquare()

// 設(shè)置表示虛擬筆尖的點(diǎn)

if (self.virtualPenTip == nil) {

self.virtualPenTip = PointNode(color: UIColor.red)

self.sceneView.scene.rootNode.addChildNode(self.virtualPenTip!)

}

// 繪圖

if let screenCenterInWorld = worldPositionFromScreenPosition(self.screenCenter, self.sceneView) {

// 更新虛擬筆尖位置

self.virtualPenTip?.isHidden = false

self.virtualPenTip?.simdPosition = screenCenterInWorld

// 繪制新的點(diǎn)

if (self.inDrawMode && !self.virtualObjectManager.pointNodeExistAt(pos: screenCenterInWorld)){

let newPoint = PointNode()

self.sceneView.scene.rootNode.addChildNode(newPoint)

self.virtualObjectManager.loadVirtualObject(newPoint, to: screenCenterInWorld)

}

// 將繪圖轉(zhuǎn)為 3D

if (self.in3DMode ) {

if self.trackImageInitialOrigin != nil {

DispatchQueue.main.async {

let newH = 0.4 *? (self.trackImageInitialOrigin!.y - screenCenterInWorld.y) / self.sceneView.frame.height

self.virtualObjectManager.setNewHeight(newHeight: newH)

}

}

else {

self.trackImageInitialOrigin = screenCenterInWorld

}

}

}

檢測(cè)用戶指尖

Apple 在 iOS 11 發(fā)布的另一個(gè)牛逼閃閃的庫(kù)是 Vision 框架。它以一種相當(dāng)方便和有效的方式提供可一些計(jì)算機(jī)視覺(jué)技術(shù)。我們會(huì)使用其中的對(duì)象追蹤技術(shù)。對(duì)象追蹤的工作原理如下:首先需要提供一張圖像,以及圖像中被追蹤的對(duì)象的正方形邊界坐標(biāo)。然后調(diào)用幾個(gè)函數(shù)來(lái)初始化追蹤。最后,為其提供一個(gè)新的圖像以及之前操作獲得的分析結(jié)果,在新圖像里該對(duì)象的位置發(fā)生了改變。如果我們給定了這些信息,它就會(huì)返回對(duì)象的新位置。

下面采用一種巧妙的方式。讓用戶把手放在桌上,就像在握著一支筆,然后確保指甲蓋面向攝像頭,然后點(diǎn)擊屏幕上的指甲蓋。這里需要說(shuō)明兩點(diǎn)。第一,指甲蓋應(yīng)該具有足夠的獨(dú)特性,以便在白色指甲蓋、皮膚和桌子之間實(shí)現(xiàn)追蹤。也就是說(shuō)深色皮膚會(huì)讓追蹤更加可靠。第二,因?yàn)橛脩羰前咽址旁谧郎系模偌由衔覀円呀?jīng)檢測(cè)到了桌子的平面,所以將指甲蓋的位置從 2D 視圖映射到 3D 環(huán)境中的話,位置就會(huì)和手指在桌子上的位置極為接近。

下面這張圖顯示了 Vision 庫(kù)檢測(cè)到的特征點(diǎn):

然后用一個(gè)觸摸手勢(shì)來(lái)初始化指甲蓋追蹤:

// MARK: 對(duì)象追蹤

fileprivate var lastObservation: VNDetectedObjectObservation?

var trackImageBoundingBox: CGRect?

let trackImageSize = CGFloat(20)

@objc private func tapAction(recognizer: UITapGestureRecognizer) {

lastObservation = nil

let tapLocation = recognizer.location(in: view)

// 用視圖坐標(biāo)空間設(shè)置 image 中的 rect 以便用于追蹤

let trackImageBoundingBoxOrigin = CGPoint(x: tapLocation.x - trackImageSize / 2, y: tapLocation.y - trackImageSize / 2)

trackImageBoundingBox = CGRect(origin: trackImageBoundingBoxOrigin, size: CGSize(width: trackImageSize, height: trackImageSize))

let t = CGAffineTransform(scaleX: 1.0 / self.view.frame.size.width, y: 1.0 / self.view.frame.size.height)

let normalizedTrackImageBoundingBox = trackImageBoundingBox!.applying(t)

// 將 rect 從視圖坐標(biāo)控件轉(zhuǎn)換為圖片空間

guard let fromViewToCameraImageTransform = self.sceneView.session.currentFrame?.displayTransform(withViewportSize: self.sceneView.frame.size, orientation: UIInterfaceOrientation.portrait).inverted() else {

return

}

var trackImageBoundingBoxInImage =? normalizedTrackImageBoundingBox.applying(fromViewToCameraImageTransform)

trackImageBoundingBoxInImage.origin.y = 1 - trackImageBoundingBoxInImage.origin.y? // Image space uses bottom left as origin while view space uses top left

lastObservation = VNDetectedObjectObservation(boundingBox: trackImageBoundingBoxInImage)

}

上面最棘手的部分就是如何把點(diǎn)擊位置從 UIView 坐標(biāo)控件轉(zhuǎn)換到圖片坐標(biāo)空間。ARKit 只為我們提供了從圖像坐標(biāo)空間轉(zhuǎn)換為 viewport 坐標(biāo)控件的 displayTransform 矩陣。所以如何實(shí)現(xiàn)相反的操作呢?只要使用逆矩陣即可。我在這篇文章里已經(jīng)嘗試盡量少用數(shù)學(xué),但在 3D 世界里有時(shí)就是難以避免。

下面。在 renderer 中提供一個(gè)新圖像來(lái)追蹤手指的新位置:

func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {

// 追蹤指甲蓋

guard let pixelBuffer = self.sceneView.session.currentFrame?.capturedImage,

let observation = self.lastObservation else {

return

}

let request = VNTrackObjectRequest(detectedObjectObservation: observation) { [unowned self] request, error in

self.handle(request, error: error)

}

request.trackingLevel = .accurate

do {

try self.handler.perform([request], on: pixelBuffer)

}

catch {

print(error)

}

. . .

}

對(duì)象追蹤完成后,會(huì)調(diào)用一個(gè)回調(diào)函數(shù),用它來(lái)更新指甲蓋的位置。基本就是上面在觸摸手勢(shì)里相反的代碼:

fileprivate func handle(_ request: VNRequest, error: Error?) {

DispatchQueue.main.async {

guard let newObservation = request.results?.first as? VNDetectedObjectObservation else {

return

}

self.lastObservation = newObservation

var trackImageBoundingBoxInImage = newObservation.boundingBox

// 從圖像空間轉(zhuǎn)換到視圖空間

trackImageBoundingBoxInImage.origin.y = 1 - trackImageBoundingBoxInImage.origin.y

guard let fromCameraImageToViewTransform = self.sceneView.session.currentFrame?.displayTransform(withViewportSize: self.sceneView.frame.size, orientation: UIInterfaceOrientation.portrait) else {

return

}

let normalizedTrackImageBoundingBox = trackImageBoundingBoxInImage.applying(fromCameraImageToViewTransform)

let t = CGAffineTransform(scaleX: self.view.frame.size.width, y: self.view.frame.size.height)

let unnormalizedTrackImageBoundingBox = normalizedTrackImageBoundingBox.applying(t)

self.trackImageBoundingBox = unnormalizedTrackImageBoundingBox

// 獲取追蹤的圖像在圖像空間的位置在距離最近的檢測(cè)到的平面上的映射

if let trackImageOrigin = self.trackImageBoundingBox?.origin {

self.lastFingerWorldPos = self.virtualObjectManager.worldPositionFromScreenPosition(CGPoint(x: trackImageOrigin.x - 20.0, y: trackImageOrigin.y + 40.0), in: self.sceneView)

}

}

}

最后,繪圖時(shí)使用 self.lastFingerWorldPos 而不是屏幕中心,這樣就全部結(jié)束了。

談一談 ARKit 和未來(lái)

在這篇文章里,我們感受到了 AR 如何通過(guò)與用戶的手指和現(xiàn)實(shí)生活中的桌子交互來(lái)實(shí)現(xiàn)沉浸式體驗(yàn)。隨著計(jì)算機(jī)視覺(jué)的發(fā)展,以及新增加的對(duì) AR 友好的硬件(如深度攝像頭),我們可以就可以更多地獲取身邊對(duì)象的 3D 結(jié)構(gòu)。

盡管微軟的 Hololens 設(shè)備還沒(méi)有向大眾發(fā)布,但微軟已經(jīng)決心要贏得這場(chǎng) AR 競(jìng)賽,這個(gè)設(shè)備組合了 AR 定制的硬件并帶有高級(jí) 3D 環(huán)境識(shí)別技術(shù)。你可以靜靜看著誰(shuí)會(huì)贏得這場(chǎng)比賽,也可以現(xiàn)在就加入開發(fā)沉浸式 AR app 的大軍!但是一定要做點(diǎn)對(duì)人類有意義的事,而不是把我們變成兔子。

附錄

Apple 的 ARKit 為開發(fā)者提供了哪些功能?

ARKit 可以讓開發(fā)者在 iPhone 和 iPad 上構(gòu)建沉浸式增強(qiáng)現(xiàn)實(shí) app,通過(guò)分析攝像頭視圖展示的場(chǎng)景并找出房間里的水平面。

如何用 Apple 的 Vision 庫(kù)來(lái)追蹤對(duì)象?

Apple 的 Vision 庫(kù)可以讓開發(fā)者追蹤視頻流中的對(duì)象。開發(fā)者提供初始圖像幀中待追蹤對(duì)象的矩形坐標(biāo),然后提供視頻幀,這個(gè)庫(kù)就會(huì)返回該對(duì)象的最新位置。

如何上手 Apple 的 ARKit?

要上手 Apple 的 ARKit,在 iPhone 6s 或更高的設(shè)備上下載 iOS 11 并用 New > Project > Augmented Reality App 創(chuàng)建一個(gè)新的 ARKit 項(xiàng)目。同時(shí)也可以看看蘋果在這里提供的 AR 示例代碼:https://developer.apple.com/arkit/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,179評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,444評(píng)論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,948評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,185評(píng)論 0 287
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,717評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,602評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,794評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,045評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評(píng)論 1 281
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,414評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,750評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容