擴增實境(Argument Reality)來囉!記得寶可夢(Pokemon Go)嗎?它當然也是擴增實境的代表之一!Apple終於在iOS11將擴增實境帶進來,也因為iOS11,未來將會有數不清的iPhones和iPads就會搭載AR功能,這將會讓 ARKit成為世界最大的平台,是的,如果你對建置擴增實境的Apps有興趣,讓你就來對地方了。
目標
本教學主要會開發一個ARkit Demo App,並應用SceneKit來協助你熟悉基礎的ARKit。
是時候讓你開始沉浸在本篇教學內,並讓你了解如何一步一步建構出ARKit App,且透過你手上的裝置與AR世界互動。
本篇教學的想法主要是學習AR與利用API來建置一個APP,藉由教學的步驟,你將會一步步了解ARKit在實體裝置上是如何與神奇的3D物件來互動的。
在開始前,請了解本篇教學僅是以基礎功能應用為主。
你需要準備的
進入本篇教學前,建議你已有對iOS的基礎開發的能力,這屬於中階程度的教學,並且,我們將需要Xcode9以上的版本。
為了測試你的ARKit App,你得需要一個可兼容Apple的ARkit的裝置,建議有Apple A9處理器以上等級的裝置。
現在請確認你已具備上述需求,並準備開始進行,以下是我將會帶你走過:
- 建立一個新的ARKit apps專案
- 設定ARKit SceneKit View
- 將ARSCNView與View Controller結合
- 連接IBOutlet
- 設定ARSCNView Session
- 允許相機使用權限
- 將3D物件加到ARSCNView
- 加入手勢判斷功能到ARSCNView
- 從ARSCNView移除物件
- 加入多樣物件到ARSCNView
建立一個新的ARKit apps專案
再來,打開Xcode,在Xcode的菜單中,選擇File > New > Project… ,然後選擇Single View App並按下next
,其實Xcode也有內鍵ARKit的範例App,但你仍可以使用Single View App來開發AR app。
你可以自行命名你想要的專案名稱,我是命名為ARKitDemo,再按下next來完成新的專案。
設定ARKit SceneKit View
現在請打開Storyboard,請在右下角的Object Library找到ARKit SceneKit View,將它拖拉至你的View Controller。
然後將你的ARKit SceneKit View的尺寸拉滿整個View Controller,它應該會呈現如下方:
讚喔!這樣的話,ARKit SceneKit View就是我們要呈現擴增實境的SceneKit內容的位置。
連接IBOutlet
我們目前仍在Main.storyboard位置,請往介面右上方找到toolbar
,並開啟Assistant Editor
,現在將ARKit
匯入到ViewController.swift檔位置:
import ARKit
接著請按住control並在ARKit ScenKit的View上拖到至ViewController.swift
檔,當連接到時,請指定為IBOutlet,並命名為sceneView
,對了,請放心地將didReceiveMemoryWarning()
這個方法刪除,我們不會在本篇教學使用到它。
設定ARKit SceneKit View
想看看,若想要我們的App在一開始執行時就能透過相機看到真實世界,並能偵測我們的週遭環境,這其實是相當驚人的科技!如今Apple已經幫開發者建立一套擴增實境的功能,我們並不需要再多花時間從無到有的重新設計,所以,謝謝Apple!讓我們能擁抱ARKit。
好的!現在是時候來設定ARkit SceneKit View了,請在ViewController的類別下插入下列程式碼:
override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) let configuration = ARWorldTrackingConfiguration() sceneView.session.run(configuration) }
並在viewWillAppear(_:)
方法內,我們將初始化AR configuration,它稱為ARWorldTrackingConfiguration
,這是一個可以執行world tracking的功能設定,等等!你一定會問什麼是world tracking?來看一下Apple的官方文件說明:
“World tracking可提供裝置上六個自由度軌跡,來找到場景所需的特徵點,world tracking也會啟用performing hit-tests against the frame. 當這個單元暫停後,Tracking將不會再執行。”
“World tracking provides 6 degrees of freedom tracking of the device. By finding feature points in the scene, world tracking enables performing hit-tests against the frame.
Tracking can no longer be resumed once the session is paused.”-Apple官方文件
所以簡單說明world tracking可以追踨裝置的方位與位置,它也可以經由裝置的相機來偵測真實世界的地平面。
最後一段程式碼,AR單元( Session)主要是管理動作追踨與相機影像處理內容。我們需要執行這個configuration
接下來,我們來加入另一個方法到ViewController
內:
override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) sceneView.session.pause() }
在viewWillDisappear(_:)
方法中,我們主要做的是當view在關閉時,設定AR單元就會同時停止追踨動作與處理圖像內容。
##允許相機使用權限
在我們要執行我們的App之前,我們需要告知我們使用者,我們得使用相機來進行擴增實境的應用,這是一個從iOS10就開始的必要詢問告知動作,也因此,請打開info.plist。然後在空白區域點選右鍵,並選擇Add row,在key下選用Privacy – Camera Usage Description,然後在Value下寫下For Augmented Reality。
過來,請確認此時你已經做好剛剛所教的一切。
請拿起你的裝置,並連線到你的Mac,來第一次建立與執行在Xcode的專案,此時這個App將會詢問你能否允許有打開相機的權限。請點按OK。若選擇Don’t allow,代表App不能使用相機來做想要執行的事情。
現在你應該能夠看到你相機畫面了。
我們也算是設定好我們的sceneView單元,並能執行world tracking,是時候進入令人興奮的階段了!擴增實境!
##將3D物件加到ARSCNView
話不多說,直接進入擴增實境,我們將要一個立方體(box),那我們先將下列程式碼加到你的ViewController
類別。
func addBox() { let box = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0) let boxNode = SCNNode() boxNode.geometry = box boxNode.position = SCNVector3(0, 0, -0.2) let scene = SCNScene() scene.rootNode.addChildNode(boxNode) sceneView.scene = scene }
現在來解釋一下我們做了些什麼。
我們先要來建立一個立方體box
的外型,1個Float = 1公尺。
接下來,我們建立一個點位boxNode
物件,這個點位可代表位置與一個物件在3D空間的座標,但對它自己而言,他本身不會有可以看到的內容,需要協助它添加資訊。
所以我們需要在這個點位來建立一個形狀,並給予一些可視化的內容。先將立方體box
的參數設為點位boxNode
的幾何資訊,我們再給我們的點位一個位置,然而這個位置和相機有關係,以正x軸而言,是右邊;負x軸是左邊,正Y軸是上方,負Y軸是下方,而正Z軸是往後,負Z軸是往前。
接著,我們要來建立一個場景,這是一個應用SceneKit的場景功能來顯示在視圖上,過來加入我們的boxNode
做為場景的初始根點位,然而初始根點位在一個場景中,是SceneKit用來定義與真實世界的座標系統的方式。
正常來說,我們的場景現在會有了一個立方體了,這個立方體會位在相機畫面的正中間,和相機的距離會有0.2公尺。
最後,讓我們的sceneView來顯示我們剛建立的場景。
現在請在viewDidLoad()
加入addBox()
的方法:
override func viewDidLoad() { super.viewDidLoad() addBox() }
建立並執行這個App,你應該可以看見一個飄浮在空中的立方體囉!
你現在也可以重新簡化addBox()
的方法:
func addBox() { let box = SCNBox(width: 0.05, height: 0.05, length: 0.05, chamferRadius: 0) let boxNode = SCNNode() boxNode.geometry = box boxNode.position = SCNVector3(0, 0, -0.2) sceneView.scene.rootNode.addChildNode(boxNode) }
這會更容易了解在做些什麼事了吧。
好的!要繼續加入手勢了!
##加入手勢辨識方法到ARSCNView
在addBox()
的方法下,請加入下列程式碼:
func addTapGestureToSceneView() { let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(ViewController.didTap(withGestureRecognizer:))) sceneView.addGestureRecognizer(tapGestureRecognizer) }
在這裡,我們先初始化點擊手勢辨識方法物件,並將target
設為ViewController
,也就是self
,在action
內設為didTap(withGestureRecognizer:)
,然後我們再將點擊手勢辨識這個物件也加入在scenView內。
是時候來做些點擊手勢辨識方法物件內的呼叫方法
#從ARSCNView移除物件
在ViewController.swift
加入下列程式碼:
@objc func didTap(withGestureRecognizer recognizer: UIGestureRecognizer) { let tapLocation = recognizer.location(in: sceneView) let hitTestResults = sceneView.hitTest(tapLocation) guard let node = hitTestResults.first?.node else { return } node.removeFromParentNode() }
在這裡,我們建立了didTap(withGestureRecognizer:)
的方法,目的是我們要獲得使用者在sceneView的點擊位置,並可看得到我們觸擊的node。
然後,我們將從hitTestResults
中移除掉第一個點位,如果hitTestResults
內沒得到任何一個點位,我們將會當初第一個點擊的點位,也是做為parent node的,就移除。
在我們測試物件移除時,請更新viewDidLoad()
的方法,並加入一個呼叫addTapGestureToSceneView()
的方法:
override func viewDidLoad() { super.viewDidLoad() addBox() addTapGestureToSceneView() }
現在如果你若能建置與執行你的專案,你應該可以點擊box node並能從scene view移除它。
不過我們感覺回到了起點。
沒關係!那我們來加多點物件。
加入多樣物件到ARSCNView
現在我們的立方體感覺有點孤獨,我們也來多做一點立方體吧,我們將在一些特徵點上加入物件。
所以什麼是特徵點呢?
根據Apple官方說明,對特徵點的定義:
此點由ARKit自動從一個連續的表面中自動辨識,但不會有另一相對的依靠點。
它其實是依真實世界的實物表面上偵測特徵點,所以,我們回到如何實現增加立方體呢,在我們開始前,在ViewController類別的程式碼最下方建立一個extension。
extension float4x4 { var translation: float3 { let translation = self.columns.3 return float3(translation.x, translation.y, translation.z) } }
這個exetension建立了一個float3
的矩陣,它可同時加入x, y和z三個參數。
因此,我們下一步要修改addBox()
:
func addBox(x: Float = 0, y: Float = 0, z: Float = -0.2) { let box = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0) let boxNode = SCNNode() boxNode.geometry = box boxNode.position = SCNVector3(x, y, z) sceneView.scene.rootNode.addChildNode(boxNode) }
基本上,我們加入了參數來初始化addBox()
的方法,同時也給它一個初始值,這代表我們可以不用在viewDidLoad()
呼叫addBox()
的方法時,就得寫入特定x, y和z座標值。
好的!
現在我們需要修改didTap(withGestureRecognizer:)
的方法,我們想要當真實世界的某一點被偵測到時,我們就能加入一個物件。
所以回到我們的guard let
的程式碼描述,在else
之後,並在return
之前,請加入下列程式碼:
{ let hitTestResultsWithFeaturePoints = sceneView.hitTest(tapLocation, types: .featurePoint) if let hitTestResultWithFeaturePoints = hitTestResultsWithFeaturePoints.first { let translation = hitTestResultWithFeaturePoints.worldTransform.translation addBox(x: translation.x, y: translation.y, z: translation.z) } }
來解釋我們想要達成的方式。
首先,我們先要有一個hit test,很像是我們第一次測試,除了這個,我們清楚定義.featurePoint
屬於types
參數。types
參數要求hit test經由AR單元的相機圖像來搜尋真實世界的實體物或是表面。它內含許多類型,但本教學目前只針對特徵點。
經由特徵點的hit test後,我們可以安全地移除第一次hit test的結果,這觀念很重要,因為不會一直都有特徵點,ARKit並不會一會偵測真實世界的實體物與表面。
如果第一次hit test能成功移除,然後我們就將轉換矩陣類型matrix_float4x4
到float3
,因為我們之前已增加了一個extension來完成此功能,有興趣的話,我們也可以自行修正x, y和z實際世界座標。
然後,我們在一特徵點上輸入x, y和z來加入一個立方體。
你的didTap(withGestureRecognizer:)
方法應如下所示:
@objc func didTap(withGestureRecognizer recognizer: UIGestureRecognizer) { let tapLocation = recognizer.location(in: sceneView) let hitTestResults = sceneView.hitTest(tapLocation) guard let node = hitTestResults.first?.node else { let hitTestResultsWithFeaturePoints = sceneView.hitTest(tapLocation, types: .featurePoint) if let hitTestResultWithFeaturePoints = hitTestResultsWithFeaturePoints.first { let translation = hitTestResultWithFeaturePoints.worldTransform.translation addBox(x: translation.x, y: translation.y, z: translation.z) } return } node.removeFromParentNode() }
測試最終版App
現在是時候按 Run 鍵執行項目以測試最終版的 App!
總結
恭喜你,和我們走了這麼久 完成本篇教學,ARKit還有還有許多功能需要我們繼續挑戰,我們只是學了一些表面功夫。
我希望你享受本篇ARKit的介紹,我也期待你會建構出屬於你的ARKit App。
關於完整的範例專案,你可以在GitHub找到。
如果你還想學習更多有關ARKit的開發,請分享此教學給你朋友並讓我知道。
原文:Building a Simple ARKit Demo with SceneKit in Swift 4 and Xcode 9