ARAnchor 到底有什么用?个人一些简单认识

ARAnchor 是 ARKit 中重要类型,也是一个重要概念。可是在使用中却让人有不少问题:ARAnchor 是啥?用起来怎么老出问题?没看出来什么明显好处,不用行不行?node

ARAnchor 是什么

按照苹果官方的说法: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)的一个缘由吧。固然,这只是个人一点猜想,毕竟我还没怎么用过实体组件系统。

相关文章
相关标签/搜索