說明
譯者注:本文是Raywenderlich上《ARKit by Tutorials》免費章節的翻譯,是原書第7章.原書7~9章完成了一個時空門app.
官網原文地址www.raywenderlich.com/195361/buil…
本文是我們書籍ARKit by Tutorials中的第7章,“建立你的時空門”.這本書向你展示瞭如何用蘋果的擴增實境框架ARKit,來構建五個沉浸式的,好看的AR應用.開始吧
通過這一系列教程,你將用ARKit和SceneKit實現一個時空門應用.時空門類的app可以用於教育目的,比如一個太陽系虛擬瀏覽應用,或者一些休閒活動,比如享受一場虛擬的沙灘假期.
時空門app
在這個應用中,你將在現實世界中的某個水平面上,放置一個通往充滿未來感的房間的虛擬門.你可以走進走出這個房間,探索裡面有什麼.
在該教程中,你將建立時空門應用的基礎.在本教程中,你將學會如何:
- 建立一個ARSession
- 用ARKit檢測並渲染水平面
你準備好建立通往另一個世界的通道了麼?
開始
在Xcode中,開啟starter工程, Portal.xcodeproj.建立並執行工程,你會看到一個空白螢幕.
啊,是的,一個空白充滿機遇的畫布!
開啟Main.storyboard再展開Portal View Controller Scene
PortalViewController是應用啟動後呈現給使用者的介面.它包含一個ARSCNView來顯示相機預覽畫面.還包含了兩個UILabels來提供說明和反饋給使用者.
現在,開啟PortalViewController.swift.在這個檔案中,你將看到下面的變數,它們代表了storyboard中的元素:
// 1
@IBOutlet var sceneView: ARSCNView?
// 2
@IBOutlet weak var messageLabel: UILabel?
// 3
@IBOutlet weak var sessionStateLabel: UILabel?
複製程式碼
讓我們看看其中的內容:
- sceneView用來顯示3D的SceneKit物體在相機檢視上.
- messageLabel,它是個UILabel,會給使用者展示說明性的訊息.說明,告訴他們如何與你的app互動.
- sessionStateLabel,另一個UILabel,會通知使用者session打斷情況,例如當app進入後臺或環境光不滿足條件.
注意:ARKit會處理所有的感測器和相機資料,但它不會實際去渲染任何虛擬內容.要在你的場景中渲染內容,可以使用與ARKit協同的各種渲染器,比如SceneKit or SpriteKit.
ARSCNView是蘋果提供的一個框架,你可以輕易將ARKit中資料與SceneKit融合在一起.使用ARSCNView會有很多好處,這就是為什麼你要在本教程的專案中用它.
在starter工程中,你將會在Helpers分組下看到很多工具類.你會在app後面的開發中用到它們.
建立ARKit
第一步是用相機來捕捉視訊流.為此,你需要使用ARSCNView物件.
開啟PortalViewController.swift並新增下面的方法:
func runSession() {
// 1
let configuration = ARWorldTrackingConfiguration.init()
// 2
configuration.planeDetection = .horizontal
// 3
configuration.isLightEstimationEnabled = true
// 4
sceneView?.session.run(configuration)
// 5
#if DEBUG
sceneView?.debugOptions = [ARSCNDebugOptions.showFeaturePoints]
#endif
}
複製程式碼
程式碼說明:
- 首先例項化一個ARWorldTrackingConfiguration物件.它為ARSession定義了配置資訊.對ARSession來說可用的配置型別有兩種: ARSessionConfiguration和ARWorldTrackingConfiguration.
使用ARSessionConfiguration是不推薦的,因為它使用了裝置的旋轉資訊,還沒有位置.對於使用A9處理器的設定來說,ARWorldTrackingSessionConfiguration是最佳選擇,因為它追蹤了裝置的所有運動. - configuration.planeDetection是設定為檢測水平面.平面的範圍可能會改變,並且隨著相機的移動多個平面可能會合併成一個.它可以找到任何水平面例如地板,桌子或床.
- 它啟用了燈光估計計算,可以被渲染框架利用來製造出更真實的虛擬物體.
- 使用指定的配置來啟動session的AR程式.這將會啟動ARKit session和視訊捕捉,並顯示在sceneView上.
- 除錯設定,這樣會新增可見的特徵點;覆蓋在相機檢視上.
現在,是時候建立labels的預設設定了.用下面的程式碼替換resetLabels():
func resetLabels() {
messageLabel?.alpha = 1.0
messageLabel?.text =
"Move the phone around and allow the app to find a plane." +
"You will see a yellow horizontal plane."
sessionStateLabel?.alpha = 0.0
sessionStateLabel?.text = ""
}
複製程式碼
這將messageLabel和sessionStateLabel設定好透明度和文字.記住,messageLabel是用於展示給使用者說明,而sessionStateLabel是用於展示錯誤資訊的,以防出錯.
現在,新增runSession()到PortalViewController中的viewDidLoad() 裡面:
override func viewDidLoad() {
super.viewDidLoad()
resetLabels()
runSession()
}
複製程式碼
這將會在app啟動並載入檢視時執行ARKit session.
接著,構建並執行app.不要忘記--你需要給app授於相機訪問許可權.
ARSCNView完成了繁重的相機視訊捕捉和顯示任務.因為是除錯模式,你可以看到渲染出的特徵點,它們形成了點雲,顯示出場景分析的中間結果.
平面探測和渲染
先前,在runSession()中, 你設定了planeDetection為 .horizontal,這意味著你的app能探測水平面.你能夠在ARSCNViewDelegate協議的代理回撥方法中獲得捕捉到的平面資訊.
在PortalViewController中新增類擴充套件,實現ARSCNViewDelegate協議:
extension PortalViewController: ARSCNViewDelegate {
}
複製程式碼
在runSession() 末尾新增下面程式碼:
sceneView?.delegate = self
複製程式碼
這行程式碼將PortalViewController設定為sceneView物件的ARSCNViewDelegate代理.
ARPlaneAnchors會被自動新增到ARSession錨點陣列中,並且ARSCNView自動將ARPlaneAnchor物件轉換為SCNNode節點.
現在,要渲染這些平面,你需要做的是實現ARSCNViewDelegate代理方法:
// 1
func renderer(_ renderer: SCNSceneRenderer,
didAdd node: SCNNode,
for anchor: ARAnchor) {
// 2
DispatchQueue.main.async {
// 3
if let planeAnchor = anchor as? ARPlaneAnchor {
// 4
#if DEBUG
// 5
let debugPlaneNode = createPlaneNode(
center: planeAnchor.center,
extent: planeAnchor.extent)
// 6
node.addChildNode(debugPlaneNode)
#endif
// 7
self.messageLabel?.text =
"Tap on the detected horizontal plane to place the portal"
}
}
}
複製程式碼
程式碼含義:
- 當ARSession探測到新的平面時,renderer(_:didAdd:for:) 方法會被呼叫,並且ARSCNView自動為平面新增一個ARPlaneAnchor.
- 這個回撥是在後臺執行緒.這裡,你需要派發到主執行緒,因為更新UI需要在主執行緒完成.
- 檢查ARAnchor是否是一個ARPlaneAnchor.
- 檢查是否在debug模式.
- 如果是,用ARKit探測到的planeAnchor的中心點和麵積座標來建立平面SCNNode節點.createPlaneNode() 是個幫助類的方法稍後實現.
- node物件是一個空的SCNNode,會被ARSCNView自動新增到場景中;它的座標對準到ARAnchor的位置上.這裡,你新增一個debugPlaneNode作為子節點,這樣它就會被放置在節點的位置上.
- 最後,不管是否在debug模式,我們都為使用者更新說明資訊,以提示使用者app現在已經準備好放置時空門到場景中了.
現在是時候建立幫助類的方法了.
建立一個新的Swift檔案,命名為SCNNodeHelpers.swift.用來盛放渲染SCNNode物件相關的所有工具方法.
匯入SceneKit到檔案中:
import SceneKit
複製程式碼
現在,新增下面的幫助方法:
// 1
func createPlaneNode(center: vector_float3,
extent: vector_float3) -> SCNNode {
// 2
let plane = SCNPlane(width: CGFloat(extent.x),
height: CGFloat(extent.z))
// 3
let planeMaterial = SCNMaterial()
planeMaterial.diffuse.contents = UIColor.yellow.withAlphaComponent(0.4)
// 4
plane.materials = [planeMaterial]
// 5
let planeNode = SCNNode(geometry: plane)
// 6
planeNode.position = SCNVector3Make(center.x, 0, center.z)
// 7
planeNode.transform = SCNMatrix4MakeRotation(-Float.pi / 2, 1, 0, 0)
// 8
return planeNode
}
複製程式碼
程式碼解讀:
- createPlaneNode方法有兩個引數:要渲染平面的center和extent,型別都是vector_float3.這個型別表示點的座標.該函式返回一個SCNNode型別的物件.
- 用指定的寬度和高度建立一個SCNPlane平面.寬度是extent中的x座標,高度是z座標.
- 初始化SCNMaterial物件並賦值漫反射內容.漫反射層顏色設定為半透明的黃色.
- SCNMaterial物件然後被新增到平面的materials陣列中.這定義了平面的紋理和顏色.
- 建立一個帶有plane幾何體的SCNNode節點.SCNPlane繼承於SCNGeometry類,它只提供了SceneKit渲染出的可見物體.通過將幾何體附加到SCNNode物件來指定它的位置和朝向.多個節點可以引用同一個幾何體物件,並允許在一個場景的不同位置出現.
- 設定planeNode的位置.注意,節點是根據ARKit上報的ARPlaneAnchor例項物件的資訊被平移到座標點 (center.x, 0, center.z) 處.
- SceneKit中的平面預設是豎直的,所以你需要旋轉90度以使它呈水平狀態.
- 該步返回前步建立的planeNode物件.
執行一下app,如果ARKit能探測到合適的平面,你就能看到一個黃色的水平面了.
移動一下裝置,你會注意到app有時會顯示多個平面.當它發現更多平面時,它將其加到檢視中.然而,已經存在的平面,卻不會隨著ARKit分析出更多特徵點而更新或改變尺寸.ARKit會根據新發現的特徵點來持續更新平面的位置和尺寸.要想接收這些更新,在PortalViewController.swift中新增下列的renderer(_:didUpdate:for:) 代理方法:
// 1
func renderer(_ renderer: SCNSceneRenderer,
didUpdate node: SCNNode,
for anchor: ARAnchor) {
// 2
DispatchQueue.main.async {
// 3
if let planeAnchor = anchor as? ARPlaneAnchor,
node.childNodes.count > 0 {
// 4
updatePlaneNode(node.childNodes[0],
center: planeAnchor.center,
extent: planeAnchor.extent)
}
}
}
複製程式碼
解釋:
- renderer(_:didUpdate:for:) 將會在相應的ARAnchor更新時被呼叫.
- 在主執行緒更新UI操作.
- 檢查ARAnchor,確保是一個ARPlaneAnchor型別,並且它至少有一個子節點對應於平面的SCNNode.
- updatePlaneNode(_:center:extent:) 方法將會在稍後實現.它根據ARPlaneAnchor中的資訊更新平面的座標和尺寸.
開啟SCNNodeHelpers.swift檔案,新增下列程式碼:
func updatePlaneNode(_ node: SCNNode,
center: vector_float3,
extent: vector_float3) {
// 1
let geometry = node.geometry as? SCNPlane
// 2
geometry?.width = CGFloat(extent.x)
geometry?.height = CGFloat(extent.z)
// 3
node.position = SCNVector3Make(center.x, 0, center.z)
}
複製程式碼
程式碼解釋:
- 檢查節點是否有SCNPlane幾何體.
- 使用傳遞過來的引數更新節點的幾何體.用ARPlaneAnchor中的extent或size來更新平面的寬度和高度.
- 更新平面節點的位置到新位置上.
現在你可以成功地更新平面的位置了,執行一下app.你會看到平面的尺寸和位置會隨著探測到新的特徵點而調整.
還有一個問題需要解決.一旦app檢測到平面,如果你退出app再重新回來,你會看到前一個探測到的平面還在相機檢視上,顯示在其他物體前面;它已經不再匹配先前的平面了.
要修復這個問題,你需要在ARSession被打斷時移除平面節點.我們會在下一章處理這個問題.
下一步做什麼?
你可能還沒意識到,但你已經踏上了建立一個時空門app的漫漫長路!是的,還有很多要做的事,但是你已經在進入虛擬空間的路上了.
本章節簡單總結:
- 探索starter專案,並複習了ARKit基礎.
- 配置一個ARSession來在app中展示相機的輸出.
- 新增平面檢測和其他函式,以便app能使用ARSCNViewDelegate協議來渲染水平面.
在下一章教程中,你將會學習如何處理session的打斷,及在檢視中使用SceneKit來渲染3D物體.點選這裡來繼續本系列教程的第2部分!
如果你喜歡本教程,可以來檢視我們的完整版書籍ARKit by Tutorials.