ARAnchor 是 ARKit 中重要類型,也是一個重要概念。可是在使用中卻讓人有不少問題:ARAnchor 是啥?用起來怎麼老出問題?沒看出來什麼明顯好處,不用行不行?node
按照蘋果官方的說法:bash
ARAnchor:可用於將對象放置在AR場景中的現實世界的位置和方向。
要追蹤實體或虛擬對象相對於相機的位置和方向,請建立錨點對象,並使用add(anchor:)方法將其添加到AR會話中。
複製代碼
蘋果工程師也曾經說過:Anchor 是虛擬和現實之間的鏈接點。session
不過這個說法仍是讓人不明因此,這個錨點 Anchor 究竟是個啥?測試
在開發中我也思考過這個問題:先手動添加 Anchor 再爲其綁定 SCNNode 對象,與直接將 Node 添加到 scene.rootNode 下有什麼不一樣麼?ui
剛好,蘋果在 WWDC2019 上講多人 AR 時,也講到了這個問題。再結合我在開發過程當中的一些經驗,總算造成了一點粗淺的認識。spa
首先,要作 AR,必須先觀察現實世界,並用數學/計算機視覺方式找到特徵點。其次要在正確的位置放上 3D 物體,而 Anchor 就是與周圍的特徵點相關聯的一個虛擬位置,用於正確放置 3D 物體。代理
那麼直接用世界座標原點呢?也就是 rootNode 的位置不行麼?實際上是差很少的,但又有一些不一樣,由於世界座標的原點是綜合了全部視覺特徵點以及手機陀螺儀數據來肯定並不斷調整的。而 ARAnchor 會自動和 附近的 特徵點相關聯,根據附近的特徵點和世界座標原點來決定本身的位置並不斷輕微調整。以下圖所示(其實 World 可能也在抖動): code
ARAnchor 類只與附近的特徵點相關聯,而 ARImageAnchor、ARPlaneAnchor、ARObjectAnchor 除了距離因素,還要考慮形狀和紋理等。orm
因此理論上,即使世界座標原點發生了抖動和調整,這些 Anchor 也會由於有本身相關的特徵點,表現得更爲穩定。cdn
那麼實際開發中,放置虛擬物體時,誰的穩定性更好呢?通過個人初步實踐,我認爲穩定性(與現實緊密結合的程度)從高到低是:ARImageAnchor(靜態) > ARPlaneAnchor >> ARAnchor ≈ WorldOrigin(rootNode 的原點) >> ARObjectAnchor。其中>>
表示高出不少。ARFaceAnchor 由於很差對比,因此沒測試過。
在開發中,我發現手動建立 ARAnchor 再爲其綁定 Node 物體,與直接添加到 rootNode 中,區別其實很小。當識別穩定時,表現都很好,至關接近;當不穩定時,都不怎麼好,ARAnchor 稍微好一點點。
而識別靜態的 ARImageAnchor 表現最爲穩定,ARPlaneAnchor 在平面有花紋並平整時也很是穩定,ARObjectAnchor 最不穩定,甚至在識別後還會不停扭動或抖動,以致於咱們爲了更平穩,在識別出 3D 物體後,把物體檢測給中止掉,這樣更平穩一些。
在實際開發中,我發現使用 ARAnchor 過程當中,爲其綁定 Node 時,調整 Node 的 scale 和 position 無效,必需要調整 transform 或者 simdTransform 才能夠。
// 加載 SCNScene 和 SCNNode
self.sceneView.scene = [SCNScene sceneNamed:@"art.scnassets/ship.scn"];
self.shipNode = [self.sceneView.scene.rootNode childNodeWithName:@"ship" recursively:YES];
//設置位置和縮放,這裏注意:提早設置 scale 和 position 是無效的,因此只能設置 transfrom/simdTramsfrom,具體緣由不明
simd_float4x4 trans = simd_diagonal_matrix(simd_make_float4(1,1,1,1));//對角線都是 1 其他是 0,說明無縮放(1 倍縮放),無平移(平移 0)
trans.columns[3][2] -= 0.5;//矩陣的第 4 列就是平移向量,第 3 個元素是 z 份量,此處沿 z 軸反方向移動 0.5 米
self.shipNode.simdTransform = trans;
//建立 Anchor 並添加到 session 中
ARAnchor *anchor = [[ARAnchor alloc] initWithName:@"ship" transform:trans];
[self.sceneView.session addAnchor:anchor];
複製代碼
// 代理方法中,將 Anchor 與 Node 關聯起來
- (SCNNode *)renderer:(id<SCNSceneRenderer>)renderer nodeForAnchor:(ARAnchor *)anchor {
if ([anchor.name isEqualToString:@"ship"]) {
// 在這裏設置 scale 和 position 也是無效的
// 注意:這個 node 會被強制添加 rootNode 下面,無論它之前在什麼結點下面
return self.shipNode;
}
return nil;
}
-(void)renderer:(id<SCNSceneRenderer>)renderer didUpdateNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor {
// 這個方法說明 anchor 的位置相對於世界座標原點發生了調整,實踐中發現並不常常調用,通常也沒有太明顯的變動
// 這裏其實有兩種可能:一是世界座標發生了抖動,可是 anchor 和周圍物體緊密結合,形成了相對移動;二是世界座標沒動,但 anchor 位置發生了調整,也是相對移動。
if ([anchor.name isEqualToString:@"ship"]) {
NSLog(@"anchor 發生了調整");
}
//PS.在這個方法裏設置 node 的 scale 和 position 是有效果的
}
複製代碼
要獲取 ARAnchor 和 SCNNode 對象的對應關係,能夠用下面 ARSCNView 的方法:
// 獲取 node 對應的 anchor
- (nullable ARAnchor *)anchorForNode:(SCNNode *)node;
// 獲取 anchor 對應的 node
- (nullable SCNNode *)nodeForAnchor:(ARAnchor *)anchor;
複製代碼
實踐了一圈下來,發現最貼切的說法仍是:Anchor 是虛擬和現實之間的鏈接點。
按照蘋果的推薦,咱們在放置任何虛擬物體時,都應該先建立 Anchor 再爲其關聯 Node。可是實際開發中,除了 ARImageAnchor 和 ARPlaneAnchor 明顯更穩定外,其他感受沒太大區別。
另外一個麻煩的是,Node 與 Anchor 關聯起來後,會被強制放到 rootNode 下面,也就是直接放在世界座標系中(即 Node 的 transform == worldTransform),這會形成當咱們想要將若干個 Node 做爲一個總體進行平移或旋轉時,很是困難。由於蘋果要求,除非有特殊要求,最好將每一個 Node 單獨與 Anchor 關聯起來,而且不要放得離 Anchor 太遠。
我想,這也是蘋果在 AR 開發中推薦使用實體組件系統(Entity Component System)的一個緣由吧。固然,這只是個人一點猜想,畢竟我還沒怎麼用過實體組件系統。