ARKit
ARKit框架通過(guò)集成iOS設(shè)備攝像頭和運(yùn)動(dòng)功能袱饭,在您的應(yīng)用程序或游戲中產(chǎn)生增強(qiáng)現(xiàn)實(shí)體驗(yàn)虑乖。
概述
增強(qiáng)現(xiàn)實(shí)(AR)描述了將2D或3D元素添加到設(shè)備相機(jī)中的實(shí)時(shí)視圖的用戶體驗(yàn),使得這些元素看起來(lái)就存在于現(xiàn)實(shí)世界中疹味。ARKit框架結(jié)合了設(shè)備運(yùn)動(dòng)追蹤、相機(jī)場(chǎng)景捕捉诫咱、高級(jí)場(chǎng)景處理和便利顯示洪灯,簡(jiǎn)化了AR體驗(yàn)的任務(wù)。
重要
ARkit需要A9或更高版本處理器的iOS設(shè)備。
要使應(yīng)用程序僅在支持ARKit的設(shè)備上可用凯亮,請(qǐng)?jiān)趹?yīng)用程序的info.plist這種添加UIRequiredDeviceCapabilities-arkit鍵值對(duì)哄尔。若增強(qiáng)現(xiàn)實(shí)知識(shí)應(yīng)用程序中的一個(gè)輔助功能,請(qǐng)?jiān)陧?xiàng)目中使用isSupported屬性來(lái)確定當(dāng)前設(shè)備是否支持你要使用的會(huì)話配置岭接。
<br />
關(guān)于增強(qiáng)現(xiàn)實(shí)與ARKit
發(fā)現(xiàn)構(gòu)建強(qiáng)大的AR體驗(yàn)的支持概念鸣戴、功能和最佳實(shí)踐方式。
概述
任何AR體驗(yàn)的基本要求以及ARKit的定義特征是創(chuàng)建和追蹤用戶所在的現(xiàn)實(shí)世界空間與可視化內(nèi)容建模的虛擬空間之間的對(duì)應(yīng)關(guān)系的能力窄锅。當(dāng)您的應(yīng)用程序顯示實(shí)時(shí)攝像頭圖像的內(nèi)容時(shí),用戶體驗(yàn)到增強(qiáng)現(xiàn)實(shí):虛擬內(nèi)容是真實(shí)世界的一部分的錯(cuò)覺(jué)追驴。
在所有ARKit體驗(yàn)中疏之,ARKit使用世界和相機(jī)坐標(biāo)系,并遵循右手定則:y軸正向向上锋爪,z軸正向指向觀察者,x軸正向指向觀察者的右側(cè)亏镰。
會(huì)話配置可以改變相對(duì)于現(xiàn)實(shí)世界的坐標(biāo)系的起點(diǎn)和方向(參見(jiàn)worldAlignment)年栓。AR會(huì)話中的每個(gè)錨定義了自己的本地坐標(biāo)系,也遵循右手定則纸兔,例如:ARFaceAnchor類定義了一個(gè)用于定位面部特征的系統(tǒng)否副。
世界追蹤的工作原理
ARKit使用視覺(jué)慣性測(cè)距的技術(shù)來(lái)創(chuàng)建真實(shí)空間和虛擬空間之間的一個(gè)對(duì)應(yīng)關(guān)系。該過(guò)程將來(lái)自iOS設(shè)備的運(yùn)動(dòng)傳感器硬件的信息與設(shè)備相機(jī)可見(jiàn)的場(chǎng)景的計(jì)算機(jī)視覺(jué)分析相結(jié)合备禀。ARKit識(shí)別場(chǎng)景圖像中的顯著特征奈揍,追蹤視頻幀中這些特征位置的差異赋续,并將該信息與運(yùn)動(dòng)傳感器數(shù)據(jù)進(jìn)行比較,之后生成設(shè)備的位置和運(yùn)動(dòng)的高精度模型蛾绎。
世界追蹤還分析和了解場(chǎng)景的內(nèi)容鸦列。使用碰撞測(cè)試方法(參見(jiàn)TestResult類)來(lái)查找與攝像機(jī)圖像中的點(diǎn)相對(duì)應(yīng)的真實(shí)世界凸面。如果在會(huì)話中啟用planeDetection設(shè)置顽爹,ARKit會(huì)檢測(cè)攝像機(jī)圖像中的曲面骆姐,并報(bào)告其位置和大小〔M剩可以使用碰撞測(cè)試結(jié)果或檢測(cè)到的平面,放置在場(chǎng)景中或與虛擬內(nèi)容交互。
最佳實(shí)踐與限制
世界追蹤是一個(gè)不精確的科學(xué)稚矿。這個(gè)過(guò)程經(jīng)常會(huì)產(chǎn)生令人印象深刻的準(zhǔn)確性,從而導(dǎo)致現(xiàn)實(shí)的AR體驗(yàn)桥爽。然而昧识,它依賴于設(shè)備的物理環(huán)境的細(xì)節(jié),這些細(xì)節(jié)并不總是一致的跪楞,或者難以實(shí)時(shí)測(cè)量(帶有一定程度的錯(cuò)誤)甸祭。要建立高品質(zhì)的AR體驗(yàn)缕碎,注意一下注意事項(xiàng)和建議池户。
設(shè)計(jì)可預(yù)見(jiàn)的照明條件的AR體驗(yàn)。世界追蹤涉及圖像分析赊抖,這需要清晰的圖像。當(dāng)相機(jī)無(wú)法看到細(xì)節(jié)房匆,例如當(dāng)相機(jī)指向空白墻或場(chǎng)景太暗時(shí)注暗,追蹤質(zhì)量會(huì)降低。
使用追蹤質(zhì)量信息提供用戶反饋捆昏。世界追蹤將圖像分析與設(shè)備運(yùn)動(dòng)相關(guān)聯(lián)。如果設(shè)備正在移動(dòng)宠页,即時(shí)只是微妙移動(dòng)寇仓,ARKit可以更好地了解場(chǎng)景。過(guò)多的動(dòng)作--太遠(yuǎn)遍烦、太快或劇烈晃動(dòng)--會(huì)導(dǎo)致圖像模糊或追蹤視頻幀之間的特征距離過(guò)遠(yuǎn),從而降低追蹤質(zhì)量供填。ARCamera類提供追蹤狀態(tài)原因信息罢猪,你可以使用它來(lái)開(kāi)發(fā)UI,告訴用戶如何解決低質(zhì)量的追蹤情況粘捎。
給予平面檢測(cè)一定的時(shí)間以產(chǎn)生清晰的結(jié)果危彩,并在獲得所需結(jié)果時(shí)禁用平面檢測(cè)。平面檢測(cè)結(jié)果隨時(shí)間而變化--當(dāng)首次檢測(cè)到平面時(shí)汤徽,其位置和范圍可能不準(zhǔn)確。隨著時(shí)間的推移漆羔,該平面仍在場(chǎng)景中,ARKit對(duì)其位置和范圍的估計(jì)作了細(xì)化演痒。當(dāng)一個(gè)較大的平面在場(chǎng)景中時(shí),ARKit可能會(huì)在已經(jīng)放置好平面后繼續(xù)改變平面的位置鸟顺、范圍和轉(zhuǎn)換。
<br />
創(chuàng)建第一個(gè)AR體驗(yàn)
創(chuàng)建運(yùn)行AR會(huì)話的應(yīng)用程序蹦锋,并使用平面檢測(cè)通過(guò)SceneKit框架放置3D內(nèi)容欧芽。
概述
此示例app運(yùn)行一個(gè)ARKit世界追蹤會(huì)話,并顯示在SceneKit視圖內(nèi)容千扔。為演示平面檢測(cè),應(yīng)用程序僅放置一個(gè)SCNPlane對(duì)象來(lái)可視化每個(gè)檢測(cè)到的ARPlaneAnchor對(duì)象厘唾。
配置并運(yùn)行AR會(huì)話
ARSCNView類是一個(gè)SceneKit視圖龙誊,它包含一個(gè)ARSession對(duì)象,用于管理和創(chuàng)建增強(qiáng)現(xiàn)實(shí)體驗(yàn)所需的運(yùn)動(dòng)追蹤和圖像處理讯柔。但是,要運(yùn)行會(huì)話,必須提供會(huì)話配置惋耙。
ARWorldTrackingConfiguration類提供高精度的運(yùn)動(dòng)追蹤功能熊昌,可以幫助你將虛擬內(nèi)容放置在真實(shí)世界的表面。要啟動(dòng)AR會(huì)話婿屹,使用所需的選項(xiàng)(如平面檢測(cè))創(chuàng)建會(huì)話配置對(duì)象,然后在ARSCNView實(shí)例的會(huì)話對(duì)象上調(diào)用run(_:options:)方法:
let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = .horizontal
sceneView.session.run(configuration)
只有當(dāng)顯示它的視圖在屏幕上時(shí)届腐,才能運(yùn)行會(huì)話。
重要提示:如果您的應(yīng)用程序需要ARKit的核心功能硬萍,請(qǐng)使用應(yīng)用程序的Info.plist文件部分中的arkit鍵使您的應(yīng)用程序僅在支持ARKit的設(shè)備上可用围详。 如果AR是應(yīng)用程序的輔助功能,請(qǐng)使用isSupported屬性來(lái)確定是否提供基于AR的功能助赞。
在檢測(cè)到的平面上放置3D內(nèi)容
設(shè)置AR會(huì)話后,可使用SceneKit在視圖中防止虛擬內(nèi)容畜普。
當(dāng)啟用平面檢測(cè)時(shí)婉徘,ARKit會(huì)為每個(gè)檢測(cè)到的平面添加和更新錨點(diǎn)。默認(rèn)情況下盖呼,ARSCNView類為每個(gè)錨點(diǎn)的SceneKit場(chǎng)景添加一個(gè)SCNNode對(duì)象。視圖委托可以實(shí)現(xiàn)renderer(_:didAdd:for:)方法來(lái)向場(chǎng)景添加內(nèi)容约炎。
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
// Place content only for anchors found by plane detection.
guard let planeAnchor = anchor as? ARPlaneAnchor else { return }
// Create a *SceneKit* plane to visualize the plane anchor using its position and extent.
let plane = SCNPlane(width: CGFloat(planeAnchor.extent.x), height: CGFloat(planeAnchor.extent.z))
let planeNode = SCNNode(geometry: plane)
planeNode.simdPosition = float3(planeAnchor.center.x, 0, planeAnchor.center.z)
/*
`SCNPlane` is vertically oriented in its local coordinate space, so
rotate the plane to match the horizontal orientation of `ARPlaneAnchor`.
*/
planeNode.eulerAngles.x = -.pi / 2
// Make the plane visualization semitransparent to clearly show real-world placement.
planeNode.opacity = 0.25
/*
Add the plane visualization to the ARKit-managed node so that it tracks
changes in the plane anchor as plane estimation continues.
*/
node.addChildNode(planeNode)
}
如果將內(nèi)容添加為與錨點(diǎn)對(duì)應(yīng)的節(jié)點(diǎn)的子節(jié)點(diǎn)蟹瘾,則ARSCNView類將自動(dòng)移動(dòng)該內(nèi)容,因?yàn)锳RKit會(huì)優(yōu)化其對(duì)平面位置和范圍的估計(jì)狸捕。為了現(xiàn)實(shí)估計(jì)平面的完整范圍众雷,此示例應(yīng)用程序還實(shí)現(xiàn)了renderer(_:didUpdate:for:)方法更新SCNPlane對(duì)象的大小以反映ARKit提供的預(yù)估值。
func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
// Update content only for plane anchors and nodes matching the setup created in `renderer(_:didAdd:for:)`.
guard let planeAnchor = anchor as? ARPlaneAnchor,
let planeNode = node.childNodes.first,
let plane = planeNode.geometry as? SCNPlane
else { return }
// Plane estimation may shift the center of a plane relative to its anchor's transform.
planeNode.simdPosition = float3(planeAnchor.center.x, 0, planeAnchor.center.z)
/*
Plane estimation may extend the size of the plane, or combine previously detected
planes into a larger one. In the latter case, `ARSCNView` automatically deletes the
corresponding node for one plane, then calls this method to update the size of
the remaining plane.
*/
plane.width = CGFloat(planeAnchor.extent.x)
plane.height = CGFloat(planeAnchor.extent.z)
}
<br />
處理增強(qiáng)現(xiàn)實(shí)中的3D交互和UI控件
遵循AR體驗(yàn)中的視覺(jué)反饋鸡岗、手勢(shì)交互和現(xiàn)實(shí)渲染的最佳實(shí)踐编兄。
概述
增強(qiáng)現(xiàn)實(shí)為用戶提供了與你的應(yīng)用程序中的現(xiàn)實(shí)和虛擬3D內(nèi)容進(jìn)行交互的新方式。然而揣苏,人機(jī)接口設(shè)計(jì)的許多基本原理仍然有效。令人信服的AR幻想需要尤其注意3D資源設(shè)計(jì)和渲染舒岸。iOS人機(jī)接口指南包括有關(guān)AR接口設(shè)計(jì)原則的建議。該項(xiàng)目展現(xiàn)了應(yīng)用這些原則的方法俄认,并輕松創(chuàng)建沉浸式洪乍、直觀的AR體驗(yàn)。
此示例應(yīng)用程序提供了簡(jiǎn)單的AR體驗(yàn)岂贩,允許用戶將一個(gè)或多個(gè)逼真的虛擬對(duì)象放置在現(xiàn)實(shí)環(huán)境中巷波,然后使用直觀的手勢(shì)來(lái)排列這些對(duì)象。該應(yīng)用程序提供了用戶界面提示抹镊,以幫助用戶了解AR體驗(yàn)的狀態(tài)及其交互選項(xiàng)垮耳。
以下部分對(duì)應(yīng)于iOS Human Interface Guidelines > Augmented Reality的部分內(nèi)容,并提供有關(guān)此示例應(yīng)用程序如何實(shí)施這些原則的詳細(xì)信息终佛。
放置虛擬對(duì)象
幫助人們了解何時(shí)找到一個(gè)表面并放置一個(gè)對(duì)象。FocusSquare類在AR視圖中繪制一個(gè)方形輪廓绍豁,為用戶提供關(guān)于ARKit世界追蹤狀態(tài)的提示牙捉。
該方塊通過(guò)改變大小和方向以反映預(yù)估的場(chǎng)景深度,并用突出的動(dòng)畫切換開(kāi)啟和關(guān)閉狀態(tài),以現(xiàn)實(shí)ARKit是否檢測(cè)到適合放置對(duì)象的表面霜浴。在用戶放置虛擬對(duì)象后蓝纲,焦點(diǎn)方塊消失晌纫,保持隱藏狀態(tài)永丝,直到用戶將相機(jī)指向另一個(gè)表面。
用戶放置對(duì)象時(shí)作適當(dāng)響應(yīng)哥牍。當(dāng)用戶選擇要防止的虛擬對(duì)象時(shí)喝检,示例應(yīng)用程序的setPosition(_:relativeTo:smoothMovement) 方法使用FocusSquare對(duì)象的簡(jiǎn)單啟發(fā)式方法將對(duì)象放置在屏幕中間大致逼真的位置,即使ARKit尚未在該位置檢測(cè)到一個(gè)平面挠说。
guard let cameraTransform = session.currentFrame?.camera.transform,
let focusSquarePosition = focusSquare.lastPosition else {
statusViewController.showMessage("CANNOT PLACE OBJECT\nTry moving left or right.")
return
}
virtualObjectInteraction.selectedObject = virtualObject
virtualObject.setPosition(focusSquarePosition, relativeTo: cameraTransform, smoothMovement: false)
updateQueue.async {
self.sceneView.scene.rootNode.addChildNode(virtualObject)
}
這個(gè)位置可能不是用戶想要放置虛擬對(duì)象的真實(shí)表面的準(zhǔn)確預(yù)估,但是它足夠接近以快速獲得對(duì)象损俭。
隨著時(shí)間的推移,ARKit會(huì)檢測(cè)到平面并調(diào)整其位置的預(yù)估雁仲,調(diào)用renderer(_:didAdd:for:)和renderer(_:didUpdate:for:)代理方法來(lái)報(bào)告結(jié)果拧咳。在這些方法中,示例應(yīng)用程序調(diào)用其adjustOntoPlaneAnchor(_:using:)方法來(lái)確定先前放置的虛擬對(duì)象是否接近檢測(cè)到的平面祭衩。如果是這樣阅签,該方法使用微妙的動(dòng)畫將虛擬對(duì)象移動(dòng)到該平面,從而使對(duì)象看起來(lái)處于用戶選擇的位置路克。
// Move onto the plane if it is near it (within 5 centimeters).
let verticalAllowance: Float = 0.05
let epsilon: Float = 0.001 // Do not update if the difference is less than 1 mm.
let distanceToPlane = abs(planePosition.y)
if distanceToPlane > epsilon && distanceToPlane < verticalAllowance {
SCNTransaction.begin()
SCNTransaction.animationDuration = CFTimeInterval(distanceToPlane * 500) // Move 2 mm per second.
SCNTransaction.animationTimingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
position.y = anchor.transform.columns.3.y
SCNTransaction.commit()
}
用戶與虛擬對(duì)象的交互
允許人們使用標(biāo)準(zhǔn)熟悉的手勢(shì)直接與虛擬對(duì)象進(jìn)行交互养交。示例應(yīng)用程序使用單指點(diǎn)擊、單指和雙指拖拽以及雙指旋轉(zhuǎn)手勢(shì)識(shí)別器來(lái)讓用戶定位和定向虛擬對(duì)象灰羽。示例代碼的VirtualObjectInteraction類管理這些手勢(shì)。
一般來(lái)說(shuō)廉嚼,保持交互的簡(jiǎn)單性怠噪。當(dāng)拖拽虛擬對(duì)象時(shí),示例應(yīng)用程序?qū)?duì)象的移動(dòng)限制為放置在其上的二維平面傍念,類似地,由于虛擬對(duì)象依賴于水平平面口四,旋轉(zhuǎn)手勢(shì)僅繞其垂直軸旋轉(zhuǎn)對(duì)象秦陋,以使對(duì)象保留在平面上。
在交互式虛擬物體的合理接近范圍內(nèi)回應(yīng)手勢(shì)赤嚼。示例代碼的objectInteracting(with:in:)方法實(shí)用手勢(shì)識(shí)別器提供的接觸位置來(lái)執(zhí)行碰撞測(cè)試顺又。該方式通過(guò)虛擬對(duì)象的邊界框進(jìn)行碰撞測(cè)試,使得用戶接觸更可能影響物體蹂空,即使接觸位置不在對(duì)象具有可見(jiàn)內(nèi)容的點(diǎn)上果录。該方法通過(guò)多點(diǎn)觸控手勢(shì)執(zhí)行多次碰撞測(cè)試,使得用戶接觸更可能應(yīng)用預(yù)估對(duì)象:
for index in 0..<gesture.numberOfTouches {
let touchLocation = gesture.location(ofTouch: index, in: view)
// Look for an object directly under the `touchLocation`.
if let object = sceneView.virtualObject(at: touchLocation) {
return object
}
}
// As a last resort look for an object under the center of the touches.
return sceneView.virtualObject(at: gesture.center(in: view))
考慮用戶啟動(dòng)的對(duì)象縮放是否必要弱恒。這個(gè)放置逼真的虛擬物體的AR體驗(yàn)可能會(huì)自然地出現(xiàn)在用戶環(huán)境中返弹,因此對(duì)象的內(nèi)在大小有助于實(shí)現(xiàn)現(xiàn)實(shí)。因此义起,示例應(yīng)用程序不會(huì)添加手勢(shì)或其他UI來(lái)啟用對(duì)象縮放默终。另外椅棺,通過(guò)不啟用縮放手勢(shì),可防止用戶對(duì)于手勢(shì)是調(diào)整對(duì)象大小還是改變對(duì)象距相機(jī)的距離而困惑。(如果選擇在應(yīng)用程序中啟用對(duì)象縮放肴熏,請(qǐng)使用捏合手勢(shì)識(shí)別器。)
警惕潛在的手勢(shì)沖突源哩。示例代碼的ThresholdPanGesture類是一個(gè)UIPanGestureRecognizer子類鸦做,它提供一種延遲手勢(shì)識(shí)別器效果的方法泼诱,直到正在進(jìn)行的手勢(shì)通過(guò)指定的移動(dòng)閥值。示例代碼的touchesMoved(with:)方法使用此類讓用戶在拖拽對(duì)象之間平滑過(guò)渡并在單個(gè)雙指手勢(shì)中旋轉(zhuǎn)對(duì)象:
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesMoved(touches, with: event)
let translationMagnitude = translation(in: view).length
// Adjust the threshold based on the number of touches being used.
let threshold = ThresholdPanGesture.threshold(forTouchCount: touches.count)
if !isThresholdExceeded && translationMagnitude > threshold {
isThresholdExceeded = true
// Set the overall translation to zero as the gesture should now begin.
setTranslation(.zero, in: view)
}
}
確保虛擬對(duì)象的順利移動(dòng)屉栓。示例代碼的setPosition(_:relativeTo:smoothMovement)方法在導(dǎo)致拖拽對(duì)象觸摸手勢(shì)位置和該對(duì)象的最近位置的歷史記錄之間插值耸袜。該方法通過(guò)根據(jù)距離攝像機(jī)的距離平均最近的位置,可以產(chǎn)生平滑的拖拽運(yùn)動(dòng)域滥,而不會(huì)使拖拽的對(duì)象滯后于用戶的手勢(shì):
if smoothMovement {
let hitTestResultDistance = simd_length(positionOffsetFromCamera)
// Add the latest position and keep up to 10 recent distances to smooth with.
recentVirtualObjectDistances.append(hitTestResultDistance)
recentVirtualObjectDistances = Array(recentVirtualObjectDistances.suffix(10))
let averageDistance = recentVirtualObjectDistances.average!
let averagedDistancePosition = simd_normalize(positionOffsetFromCamera) * averageDistance
simdPosition = cameraWorldPosition + averagedDistancePosition
} else {
simdPosition = cameraWorldPosition + positionOffsetFromCamera
}
探索更有吸引力的交互方式蜈抓。在AR體驗(yàn)中资昧,拖拽手勢(shì)--即將手指移動(dòng)到設(shè)備屏幕上--并不是將虛擬內(nèi)容拖到新位置的唯一自然方式。用戶還可以直觀地嘗試在移動(dòng)設(shè)備的同時(shí)將手指保持在屏幕上格带,在AR場(chǎng)景中有效地拖拽觸摸點(diǎn)叽唱。
示例應(yīng)用程序通過(guò)在拖拽手勢(shì)正在進(jìn)行時(shí)持續(xù)調(diào)用updateObjectToCurrentTrackingPosition()方法來(lái)支持這種手勢(shì),即時(shí)手勢(shì)的觸摸位置沒(méi)有改變棺亭。如果設(shè)備在拖拽期間移動(dòng),則該方法會(huì)計(jì)算與觸摸位置相對(duì)應(yīng)的新世界位置嗽桩,并相應(yīng)地移動(dòng)虛擬對(duì)象碌冶。
進(jìn)入增強(qiáng)現(xiàn)實(shí)
指示正在進(jìn)行的初始化并提示用戶。示例應(yīng)用程序顯示有關(guān)AR會(huì)話狀態(tài)的文本提示以及使用浮動(dòng)文本視圖與AR體驗(yàn)進(jìn)行交互的說(shuō)明扑庞。示例代碼的StatusViewController類管理此視圖罐氨,顯示允許用戶讀取它們之后淡出的臨時(shí)指令或重要的狀態(tài)信息,直到用戶更正為止塔嬉。
處理問(wèn)題
當(dāng)不符合用戶期望時(shí)租悄,允許重置體驗(yàn)。示例應(yīng)用程序具有始終在UI右上角可見(jiàn)的重置按鈕记盒,無(wú)論當(dāng)前狀態(tài)如何允許用戶重新啟動(dòng)AR體驗(yàn)外傅。參閱示例代碼的restartExperience()方法。
僅在兼容的設(shè)備上提供AR功能碾盟。示例應(yīng)用程序需要ARKit的核心功能技竟,需在項(xiàng)目的Info.plist文件中設(shè)置UIRequiredDeviceCapabilities - arkit鍵值對(duì)。部署內(nèi)建項(xiàng)目時(shí)熙尉,此鍵可防止在不支持ARKit的設(shè)備上安裝應(yīng)用程序搓扯。
若app僅將AR作為次要功能,請(qǐng)使用isSupported方法來(lái)確定是否隱藏需要ARKit的功能铅歼。
<br />
創(chuàng)建基于面部的AR體驗(yàn)
使用iPhone X上的TrueDepth攝像頭,放置和動(dòng)畫追蹤用戶臉部的3D內(nèi)容并匹配面部表情厦幅。
概述
此示例應(yīng)用程序提供了一個(gè)簡(jiǎn)單的界面慨飘,可讓你在具有TrueDepth前置攝像頭的設(shè)備上選擇一下四個(gè)增強(qiáng)現(xiàn)實(shí)可視化(參閱iOS設(shè)備兼容性參考)。
- 單獨(dú)的相機(jī)視圖,沒(méi)有任何AR內(nèi)容
- ARKit提供的面部網(wǎng)格堤瘤,可以自動(dòng)估計(jì)真實(shí)的定向照明環(huán)境
- 附加到用戶真實(shí)面孔的虛擬3D內(nèi)容(并被部分遮蔽)
- 一個(gè)可動(dòng)簡(jiǎn)單的機(jī)器人角色浆熔,其表情與用戶匹配
使用示例應(yīng)用程序的“+”按鈕在這些模式之間切換,如下所示:
在SceneKit視圖中啟動(dòng)面部追蹤會(huì)話
與ARKit其他用途一樣慎皱,面部追蹤需要配置和運(yùn)行會(huì)話(ARSession對(duì)象)叶骨,并將視頻圖像與虛擬內(nèi)容一起渲染忽刽。有關(guān)會(huì)話和視圖設(shè)置的更詳細(xì)的說(shuō)明,請(qǐng)參閱關(guān)于增強(qiáng)現(xiàn)實(shí)與ARKit和創(chuàng)建第一個(gè)AR體驗(yàn)跪帝。此示例使用SceneKit顯示AR體驗(yàn)伞剑,但也可以使用SpriteKit或使用Metal創(chuàng)建自己的渲染器(參閱ARSKView和使用Metal顯示AR體驗(yàn)**)。
面部追蹤與用于配置會(huì)話的類中的ARKit的其他用途不同黎泣。要啟用面部追蹤抒倚,請(qǐng)創(chuàng)建一個(gè)ARFaceTrackingConfiguration實(shí)例,配置其屬性献起,并將其傳遞給與視圖關(guān)聯(lián)的AR會(huì)話的run(_:options:)方法,如下所示:
guard ARFaceTrackingConfiguration.isSupported else { return }
let configuration = ARFaceTrackingConfiguration()
configuration.isLightEstimationEnabled = true
session.run(configuration, options: [.resetTracking, .removeExistingAnchors])
在提供需要面部追蹤AR會(huì)話的用戶功能之前姻政,請(qǐng)檢查ARFaceTrackingConfiguration.isSupported屬性以確定當(dāng)前設(shè)備是否支持ARKit面部追蹤岂嗓。
面部追蹤的位置和方向
當(dāng)面部追蹤處于活動(dòng)狀態(tài)時(shí),ARKit會(huì)自動(dòng)將ARFaceAnchor對(duì)象添加到正在運(yùn)行的AR會(huì)話中食绿,其中包含有關(guān)用戶面部的信息公罕,包括其位置和方向。
注意
ARKit會(huì)檢測(cè)并提供有關(guān)用戶面部的信息铲汪。如果攝像機(jī)圖像中存在多個(gè)面部罐柳,則ARKit會(huì)選擇最大或最清晰可識(shí)別的面部。
在基于SceneKit的AR體驗(yàn)中齿梁,可以在renderer(_:didAdd:for:)方法中添加與面部錨點(diǎn)對(duì)應(yīng)的3D內(nèi)容肮蛹。ARKit為錨點(diǎn)添加了一個(gè)SceneKit節(jié)點(diǎn),并更新了每個(gè)frame上節(jié)點(diǎn)的位置和方向酵幕,因此你添加到該節(jié)點(diǎn)的任何SceneKit內(nèi)容都會(huì)自動(dòng)跟隨用戶面部的位置和方向缓苛。
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
// Hold onto the `faceNode` so that the session does not need to be restarted when switching masks.
faceNode = node
serialQueue.async {
self.setupFaceNodeContent()
}
}
在此示例中未桥,renderer(_:didAdd:for:)方法將SceneKit內(nèi)容添加到faceNode。例如舌菜,如果更改示例代碼中的showsCoordinateOrigin變量亦镶,則應(yīng)用程序?qū)/y/z軸的可視化添加到該節(jié)點(diǎn)袱瓮,指示面部錨點(diǎn)坐標(biāo)系的起點(diǎn)爱咬。
使用面部幾何體給用戶面部建模
ARKit提供了一個(gè)與用戶面部的大小、形狀燎斩、拓?fù)渑c當(dāng)前面部表情相匹配的粗略3D網(wǎng)格幾何體蜂绎。還提供了ARSCNFaceGeometry類师枣,它是一種在SceneKit中可視化此網(wǎng)格的簡(jiǎn)單方法。
AR體驗(yàn)可以使用此網(wǎng)格放置或繪制看起來(lái)附加到面部的內(nèi)容坛吁。例如拨脉,通過(guò)將半透明紋理應(yīng)用于此幾何體宣增,可以將虛擬紋身或化妝畫到用戶的皮膚上。
要?jiǎng)?chuàng)建SceneKit面部幾何體帖旨,請(qǐng)使用SceneKit視圖用于渲染的Metal設(shè)備初始化一個(gè)ARSCNFaceGeometry對(duì)象:
// This relies on the earlier check of `ARFaceTrackingConfiguration.isSupported`.
let device = sceneView.device!
let maskGeometry = ARSCNFaceGeometry(device: device)!
示例代碼的setupFaceNodeContent方法將包含面部幾何體的節(jié)點(diǎn)添加到場(chǎng)景中灵妨。通過(guò)使該節(jié)點(diǎn)成為由面部錨點(diǎn)提供的節(jié)點(diǎn)的子節(jié)點(diǎn)泌霍,面部模型會(huì)自動(dòng)追蹤用戶面部的位置和方向。
為了使屏幕上的面部模型符合用戶面部的形狀朱转,即使用戶眨眼藤为、說(shuō)話與進(jìn)行各種面部表情,你需要在renderer(_:didUpdate:for:)代理回調(diào)中檢索更新的面部網(wǎng)格缅疟。
func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
guard let faceAnchor = anchor as? ARFaceAnchor else { return }
virtualFaceNode?.update(withFaceAnchor: faceAnchor)
}
然后,通過(guò)將新的面部網(wǎng)格傳遞到update(from:)方法茎杂,更新場(chǎng)景中的ARSCNFaceGeometry對(duì)象:
func update(withFaceAnchor anchor: ARFaceAnchor) {
let faceGeometry = geometry as! ARSCNFaceGeometry
faceGeometry.update(from: anchor.geometry)
}
在用戶面部放置3D內(nèi)容
ARKit提供的面部網(wǎng)格的另一個(gè)用途是在場(chǎng)景中創(chuàng)建遮擋幾何體煌往。遮擋幾何體是不會(huì)呈現(xiàn)任何可見(jiàn)內(nèi)容(允許相機(jī)圖像顯示)的3D模型,但會(huì)阻礙相機(jī)對(duì)場(chǎng)景中其他虛擬內(nèi)容的視圖羞海。
這種技術(shù)創(chuàng)造出真實(shí)面部與虛擬對(duì)象交互的錯(cuò)覺(jué)曲管,即使面部是2D攝像機(jī)圖像,虛擬內(nèi)容是渲染的3D對(duì)象奸鬓。例如钓试,如果將遮擋幾何體圖形和虛擬眼鏡放置在用戶的面部上恢恼,則面部可能會(huì)遮擋眼睛的frame。
要?jiǎng)?chuàng)建面部的遮擋幾何體漓踢,請(qǐng)先創(chuàng)建一個(gè)ARSCNFaceGeometry對(duì)象漏隐,如上例所示,但是薯酝,不要使用可見(jiàn)的外觀來(lái)配置該對(duì)象的SceneKit材質(zhì)爽柒,而是在渲染期間將材質(zhì)設(shè)置為渲染深度而不是顏色:
geometry.firstMaterial!.colorBufferWriteMask = []
occlusionNode = SCNNode(geometry: geometry)
occlusionNode.renderingOrder = -1
由于材質(zhì)呈現(xiàn)深度浩村,所以SceneKit渲染的其他對(duì)象正確地出現(xiàn)在它的前面或后面。但是由于材質(zhì)不會(huì)呈現(xiàn)顏色酿矢,相機(jī)圖像會(huì)出現(xiàn)在其位置上。示例應(yīng)用程序?qū)⒋思夹g(shù)與位于用戶眼睛前方的SceneKit對(duì)象相結(jié)合瘫筐,創(chuàng)建一個(gè)效果策肝,其中對(duì)象被用戶的鼻子實(shí)際遮擋。
用混合形狀動(dòng)畫化一個(gè)角色
除了上述示例中所示的面部網(wǎng)格之外拙毫,ARKit還提供了一個(gè)更為抽象的用戶面部表情模型棺禾,其形式為blendShapes字典∪鼻埃可以使用該字典中的命名系數(shù)值來(lái)控制自己的2D或3D資源的動(dòng)畫參數(shù)悬襟,創(chuàng)建遵循用戶真實(shí)面部動(dòng)作和表情的角色(如頭像或木偶)。
作為混合形狀動(dòng)畫的基本演示肆良,此示例包含使用ScenceKit原始形狀創(chuàng)建的機(jī)器人角色頭部的簡(jiǎn)單模型逸绎。(參閱源代碼中的robotHead.scn文件棺牧。)
要獲取用戶當(dāng)前的面部表情朗儒,請(qǐng)從renderer(_:didUpdate:for:)代理回調(diào)中讀取面部錨點(diǎn)的blendShapes字典:
func update(withFaceAnchor faceAnchor: ARFaceAnchor) {
blendShapes = faceAnchor.blendShapes
}
然后,檢查該字典中的鍵值對(duì)以計(jì)算模型的動(dòng)畫參數(shù)乏悄。有52個(gè)獨(dú)特的ARFaceAnchor.BlendShapeLocation系數(shù)恳不。app可以使用盡可能少的或很多的必要條件來(lái)創(chuàng)建想要的藝術(shù)效果。在此示例中规求,RobotHead類執(zhí)行此計(jì)算,將eyeBlinkLeft和eyeBlinkRight參數(shù)映射到機(jī)器人眼睛的比例因子的一個(gè)軸線瓦戚,并將jawOpen參數(shù)映射到機(jī)器人頜骨的位置丛塌。
var blendShapes: [ARFaceAnchor.BlendShapeLocation: Any] = [:] {
didSet {
guard let eyeBlinkLeft = blendShapes[.eyeBlinkLeft] as? Float,
let eyeBlinkRight = blendShapes[.eyeBlinkRight] as? Float,
let jawOpen = blendShapes[.jawOpen] as? Float
else { return }
eyeLeftNode.scale.z = 1 - eyeBlinkLeft
eyeRightNode.scale.z = 1 - eyeBlinkRight
jawNode.position.y = originalJawY - jawHeight * jawOpen
}
}
<br />
使用Metal顯示AR體驗(yàn)
通過(guò)渲染相機(jī)圖像和使用位置追蹤信息顯示疊加內(nèi)容來(lái)構(gòu)建自定義AR視圖姨伤。
概述
ARKit包括用于使用SceneKit或SpriteKit輕松現(xiàn)實(shí)AR體驗(yàn)的視圖類。但是当编,如果建立自己的渲染引擎(或集成第三方引擎)徒溪,則ARKit還提供必要的支持以現(xiàn)實(shí)具有自定義視圖的AR體驗(yàn)。
在任何AR體驗(yàn)中鲤桥,第一步是配置一個(gè)ARSession對(duì)象來(lái)管理攝像頭捕獲和運(yùn)動(dòng)處理渠概。會(huì)話定義和維護(hù)設(shè)備所在的真實(shí)世界與你為AR內(nèi)容建模的虛擬空間之間的對(duì)應(yīng)關(guān)系播揪。要在自定義視圖中顯示AR體驗(yàn),需要:
- 從會(huì)話中檢索視頻幀和追蹤信息猪狈。
- 將這些frame圖像作為視圖的背景渲染雇庙。
- 使用追蹤信息在相機(jī)圖像上方定位和繪制AR內(nèi)容。
注意
下面介紹了Xcode項(xiàng)目模版中的代碼寒跳。有關(guān)完整的示例代碼竹椒,請(qǐng)使用Augmented Realitytemplate模版創(chuàng)建一個(gè)新的iOS應(yīng)用程序,然后從Content Technology彈出菜單中選擇Metal康愤。
從會(huì)話中獲取視頻幀和追蹤數(shù)據(jù)
創(chuàng)建和維護(hù)自己的ARSession實(shí)例,并使用適合你想要支持的AR體驗(yàn)類型的會(huì)話配置運(yùn)行它择膝。會(huì)話從相機(jī)捕獲視頻检激,在建模的3D空間中追蹤設(shè)備的位置和方向愛(ài)哪個(gè),并提供ARFrame對(duì)象齿穗。每個(gè)這樣的對(duì)象從捕獲幀的時(shí)刻都包含單獨(dú)的視頻幀圖像和位置追蹤信息饺律。
有兩種方法來(lái)訪問(wèn)由AR會(huì)話產(chǎn)生的ARFrame對(duì)象复濒,這取決于你的應(yīng)用是否偏好pull或push設(shè)計(jì)模式。
如果你偏重控制幀定時(shí)(pull設(shè)計(jì)模式)巧颈,請(qǐng)使用會(huì)話的currentFrame屬性來(lái)獲取當(dāng)前幀圖像和每次重繪視圖內(nèi)容時(shí)的追蹤信息砸泛。ARKit Xcode模版使用這種方法:
// in Renderer class, called from MTKViewDelegate.draw(in:) via Renderer.update()
func updateGameState() {
guard let currentFrame = session.currentFrame else {
return
}
updateSharedUniforms(frame: currentFrame)
updateAnchors(frame: currentFrame)
updateCapturedImageTextures(frame: currentFrame)
if viewportSizeDidChange {
viewportSizeDidChange = false
updateImagePlane(frame: currentFrame)
}
}
或者,如果你的應(yīng)用程序偏好push模式勾栗,則實(shí)現(xiàn)session(_:didUpdate:)代理方法垒迂,會(huì)話將為其捕獲的每個(gè)視頻幀調(diào)用一次(默認(rèn)為60幀/秒)妒蛇。
獲得一個(gè)frame后绣夺,你需要繪制相機(jī)圖像,并更新和渲染你的AR體驗(yàn)中包含的任何覆蓋內(nèi)容奋蔚。
繪制相機(jī)圖像
每個(gè)ARFrame對(duì)象的capturedImage屬性都包含從設(shè)備攝像頭捕獲到的像素緩沖區(qū)。要將此圖像繪制為自定義視圖的背景坤按,需要從圖像內(nèi)容創(chuàng)建紋理馒过,并提交使用這些紋理的GPU渲染命令。
像素緩沖區(qū)的內(nèi)容被編碼為雙曲面YCbCr(也稱為YUV)數(shù)據(jù)格式来累;要渲染圖像窘奏,需要將此像素?cái)?shù)據(jù)轉(zhuǎn)換為可繪制的RGB格式。對(duì)于使用Metal渲染领猾,可以在GPU著色器代碼中最有效地執(zhí)行此轉(zhuǎn)換骇扇。使用CVMetalTextureCache API從像素緩沖區(qū)創(chuàng)建兩個(gè)Metal紋理--每個(gè)用于緩沖區(qū)的亮度(Y)和色度(CbCr)平面:
func updateCapturedImageTextures(frame: ARFrame) {
// Create two textures (Y and CbCr) from the provided frame's captured image
let pixelBuffer = frame.capturedImage
if (CVPixelBufferGetPlaneCount(pixelBuffer) < 2) {
return
}
capturedImageTextureY = createTexture(fromPixelBuffer: pixelBuffer, pixelFormat:.r8Unorm, planeIndex:0)!
capturedImageTextureCbCr = createTexture(fromPixelBuffer: pixelBuffer, pixelFormat:.rg8Unorm, planeIndex:1)!
}
func createTexture(fromPixelBuffer pixelBuffer: CVPixelBuffer, pixelFormat: MTLPixelFormat, planeIndex: Int) -> MTLTexture? {
var mtlTexture: MTLTexture? = nil
let width = CVPixelBufferGetWidthOfPlane(pixelBuffer, planeIndex)
let height = CVPixelBufferGetHeightOfPlane(pixelBuffer, planeIndex)
var texture: CVMetalTexture? = nil
let status = CVMetalTextureCacheCreateTextureFromImage(nil, capturedImageTextureCache, pixelBuffer, nil, pixelFormat, width, height, planeIndex, &texture)
if status == kCVReturnSuccess {
mtlTexture = CVMetalTextureGetTexture(texture!)
}
return mtlTexture
}
接下來(lái)匠题,使用顏色轉(zhuǎn)換矩陣執(zhí)行YCbCr到RGB轉(zhuǎn)換的部分功能來(lái)編碼這兩個(gè)紋理的渲染命令:
fragment float4 capturedImageFragmentShader(ImageColorInOut in [[stage_in]],
texture2d<float, access::sample> capturedImageTextureY [[ texture(kTextureIndexY) ]],
texture2d<float, access::sample> capturedImageTextureCbCr [[ texture(kTextureIndexCbCr) ]]) {
constexpr sampler colorSampler(mip_filter::linear,
mag_filter::linear,
min_filter::linear);
const float4x4 ycbcrToRGBTransform = float4x4(
float4(+1.0000f, +1.0000f, +1.0000f, +0.0000f),
float4(+0.0000f, -0.3441f, +1.7720f, +0.0000f),
float4(+1.4020f, -0.7141f, +0.0000f, +0.0000f),
float4(-0.7010f, +0.5291f, -0.8860f, +1.0000f)
);
// Sample Y and CbCr textures to get the YCbCr color at the given texture coordinate
float4 ycbcr = float4(capturedImageTextureY.sample(colorSampler, in.texCoord).r,
capturedImageTextureCbCr.sample(colorSampler, in.texCoord).rg, 1.0);
// Return converted RGB color
return ycbcrToRGBTransform * ycbcr;
}
注意
使用displayTransform(for:viewportSize:)方法確保相機(jī)圖像是否覆蓋整個(gè)視圖韭山。例如使用這個(gè)方法,以及完整的Metal管道設(shè)置代碼梦裂,請(qǐng)參閱完整的Xcode模版盖淡。
追蹤并渲染覆蓋內(nèi)容
AR體驗(yàn)通常側(cè)重于渲染3D覆蓋內(nèi)容,使內(nèi)容看起來(lái)是從相機(jī)圖像中看到的真實(shí)世界的一部分冗恨。為了實(shí)現(xiàn)這種幻象味赃,使用ARAnchor類來(lái)模擬自己的3D內(nèi)容相對(duì)于現(xiàn)實(shí)世界空間的位置和方向。錨點(diǎn)對(duì)象提供了可在渲染過(guò)程中引用的轉(zhuǎn)換傲武。
例如,Xcode模版在用戶點(diǎn)擊屏幕時(shí)創(chuàng)建位于設(shè)備前面大約20厘米處的錨點(diǎn):
func handleTap(gestureRecognize: UITapGestureRecognizer) {
// Create anchor using the camera's current position
if let currentFrame = session.currentFrame {
// Create a transform with a translation of 0.2 meters in front of the camera
var translation = matrix_identity_float4x4
translation.columns.3.z = -0.2
let transform = simd_mul(currentFrame.camera.transform, translation)
// Add a new anchor to the session
let anchor = ARAnchor(transform: transform)
session.add(anchor: anchor)
}
}
在渲染引擎中态兴,使用每個(gè)ARAnchor對(duì)象的transform屬性來(lái)放置可視化內(nèi)容诗茎。Xcode模版使用在其handleTap方法添加到會(huì)話中的每個(gè)錨點(diǎn)來(lái)定位簡(jiǎn)單的立體網(wǎng)格:
func updateAnchors(frame: ARFrame) {
// Update the anchor uniform buffer with transforms of the current frame's anchors
anchorInstanceCount = min(frame.anchors.count, kMaxAnchorInstanceCount)
var anchorOffset: Int = 0
if anchorInstanceCount == kMaxAnchorInstanceCount {
anchorOffset = max(frame.anchors.count - kMaxAnchorInstanceCount, 0)
}
for index in 0..<anchorInstanceCount {
let anchor = frame.anchors[index + anchorOffset]
// Flip Z axis to convert geometry from right handed to left handed
var coordinateSpaceTransform = matrix_identity_float4x4
coordinateSpaceTransform.columns.2.z = -1.0
let modelMatrix = simd_mul(anchor.transform, coordinateSpaceTransform)
let anchorUniforms = anchorUniformBufferAddress.assumingMemoryBound(to: InstanceUniforms.self).advanced(by: index)
anchorUniforms.pointee.modelMatrix = modelMatrix
}
}
注意
在更復(fù)雜的AR體驗(yàn)中献汗,可以使用碰撞測(cè)試或平面檢測(cè)來(lái)查找真實(shí)世界曲面的位置罢吃。有關(guān)詳細(xì)信息,請(qǐng)參閱planeDetection屬性和hitTest(_:types:)方法尿招。在這兩種情況下就谜,ARKit都會(huì)提供ARAnchor對(duì)象的結(jié)果,因此你仍然可以使用錨點(diǎn)轉(zhuǎn)換來(lái)放置視覺(jué)內(nèi)容缆瓣。
現(xiàn)實(shí)光照渲染
當(dāng)你配置用于在場(chǎng)景中繪制3D內(nèi)容的著色器時(shí),請(qǐng)使用每個(gè)ARFrame對(duì)象中的預(yù)計(jì)光照信息來(lái)產(chǎn)生更逼真的陰影:
// in Renderer.updateSharedUniforms(frame:):
// Set up lighting for the scene using the ambient intensity if provided
var ambientIntensity: Float = 1.0
if let lightEstimate = frame.lightEstimate {
ambientIntensity = Float(lightEstimate.ambientIntensity) / 1000.0
}
let ambientLightColor: vector_float3 = vector3(0.5, 0.5, 0.5)
uniforms.pointee.ambientLightColor = ambientLightColor * ambientIntensity