在上一篇文章里我們用 ARKit 來檢測(cè)真實(shí)生活中的平面低斋,然后給這些平面添加了視覺效果俐筋。在這篇文章中静秆,我會(huì)開始向 AR 體驗(yàn)添加虛擬內(nèi)容,并與檢測(cè)到的平面進(jìn)行交互巡李。
在這篇文章的最后抚笔,我們能夠把小方塊丟到世界里,并在小方塊上應(yīng)用逼真的物理學(xué)侨拦,以便它們彼此相互作用殊橙,同時(shí)還會(huì)制造迷你沖擊波來讓小方塊飛出去。
下面是實(shí)際的演示視頻狱从,可以看到首先獲取了水平面膨蛮,然后添加了一些 3D 小方塊并與場(chǎng)景交互,最后制造了一些迷你沖擊波來讓小方塊飛出去:
和以前一樣矫夯,你可以參照這里的代碼:https://github.com/josephchang10/ARCube/tree/part3
命中測(cè)試
在第一篇教程中鸽疾,我們可以在任意 X,Y,Z 位置插入虛擬 3D 內(nèi)容,并在真實(shí)世界里渲染和追蹤⊙得玻現(xiàn)在我們掌握了平面檢測(cè)制肮,我想添加一些內(nèi)容并與這些平面交互。完成后從 app 中看過去递沪,你的桌子豺鼻、椅子、地板等等上面就像都擺了東西款慨。
在此 app 中儒飒,如果用戶點(diǎn)擊屏幕,就會(huì)執(zhí)行一次命中測(cè)試(hit test)檩奠,即獲取屏幕的 2D 坐標(biāo)桩了,并從攝像頭原點(diǎn)處通過屏幕的 2D 坐標(biāo)(在投影平面上有 3D 位置)發(fā)射一道射線到場(chǎng)景中。如果射線與某個(gè)平面相交埠戳,就會(huì)獲得命中結(jié)果井誉,然后利用射線和平面相交的 3D 坐標(biāo),在此 3D 位置放置內(nèi)容整胃。
這段代碼相當(dāng)簡(jiǎn)單颗圣,ARSCNView 包含一個(gè) hitTest 方法,只要傳入屏幕坐標(biāo)屁使,它會(huì)負(fù)責(zé)從攝像頭原點(diǎn)投影射線在岂,并穿過 3D 環(huán)境中對(duì)應(yīng)屏幕坐標(biāo)的點(diǎn),最后返回結(jié)果:
@objc func handleTapFrom(recognizer: UITapGestureRecognizer) {
// 獲取屏幕空間坐標(biāo)并傳遞給 ARSCNView 實(shí)例的 hitTest 方法
let tapPoint = recognizer.location(in: sceneView)
let result = sceneView.hitTest(tapPoint, types: .existingPlaneUsingExtent)
// 如果射線與某個(gè)平面幾何體相交蛮寂,就會(huì)返回該平面蔽午,以離攝像頭的距離升序排序
// 如果命中多次,用距離最近的平面
if let hitResult = result.first {
insertGeometry(hitResult)
}
}
有了 ARHitTestResult 就可以得到射線/平面相交點(diǎn)的世界坐標(biāo)共郭,并在該位置放置虛擬內(nèi)容祠丝。這篇文章里只會(huì)插入簡(jiǎn)單的小方塊疾呻,后面的文章里則會(huì)讓物體看起來更真實(shí):
func insertGeometry(_ hitResult: ARHitTestResult) {
let dimension: CGFloat = 0.1
let cube = SCNBox(width: dimension, height: dimension, length: dimension, chamferRadius: 0)
let node = SCNNode(geometry: cube)
// physicsBody 會(huì)讓 SceneKit 用物理引擎控制該幾何體
node.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)
node.physicsBody?.mass = 2
node.physicsBody?.categoryBitMask = CollisionCategory.cube.rawValue
// 把幾何體插在用戶點(diǎn)擊的點(diǎn)再稍高一點(diǎn)的位置,以便使用物理引擎來掉落到平面上
let insertionYOffset: Float = 0.5
node.position = SCNVector3Make(hitResult.worldTransform.columns.3.x, hitResult.worldTransform.columns.3.y + insertionYOffset, hitResult.worldTransform.columns.3.z)
sceneView.scene.rootNode.addChildNode(node)
// 將小方塊添加到場(chǎng)景中
boxes.append(node)
}
添加物理學(xué)
AR 理應(yīng)增強(qiáng)現(xiàn)實(shí)世界写半,所以為了讓物體的感覺更加真實(shí)岸蜗,需要添加一些物理學(xué)原理來給予重力感。
上面的代碼里每個(gè)小方塊都給定了 physicsBody叠蝇,以便讓 SceneKit 用物理引擎來控制該幾何體璃岳。每個(gè) ARKit 檢測(cè)的平面我也給定了 physicsBody,以便讓小方塊與平面交互(查看 github repo 中的 Plane.swift 類可以獲得更多細(xì)節(jié))悔捶。
停止平面檢測(cè)
有了一定數(shù)量的平面后铃慷,就不希望 ARKit 再繼續(xù)提供新平面了,因?yàn)檫@可能會(huì)更新目前已存在的平面蜕该,并影響已經(jīng)添加到世界中的幾何體犁柜。
在這個(gè) app 里,如果用戶用兩只手指長(zhǎng)按一秒堂淡,就會(huì)隱藏所有的平面并關(guān)閉平面檢測(cè)馋缅。只需更新 ARSession configuration 的 planeDetection 屬性并再次 run 一遍 session 即可。默認(rèn)情況下绢淀,session 會(huì)保留相同的坐標(biāo)系以及所有 anchor:
// 獲取當(dāng)前的 session configuration
if let configuration = sceneView.session.configuration as? ARWorldTrackingSessionConfiguration{
//關(guān)閉平面檢測(cè)和更新
configuration.planeDetection = .init(rawValue: 0) // ARPlaneDetectionNone
// 再次 run session 以應(yīng)用改變
sceneView.session.run(configuration)
}
示例代碼
所有的示例代碼都在這里:https://github.com/josephchang10/ARCube/tree/part3
接下來
下篇文章里會(huì)強(qiáng)化之前我們寫過的代碼萤悴,并添加一些 UI 控件來開啟/關(guān)閉某些功能。還會(huì)使用光線和紋理皆的,以便讓插入的幾何體看起來更加真實(shí)覆履。