如何以编程方式移动 ARAnchor?

How do I programmatically move an ARAnchor?

我正在试用新的 ARKit 来替换我拥有的另一个类似解决方案。太棒了!但我似乎无法弄清楚如何以编程方式移动 ARAnchor。我想把锚点慢慢移到用户的左边。

将锚点创建在用户前方 2 米处:

        var translation = matrix_identity_float4x4
        translation.columns.3.z = -2.0
        let transform = simd_mul(currentFrame.camera.transform, translation)

        let anchor = ARAnchor(transform: transform)
        sceneView.session.add(anchor: anchor)

稍后,将对象移动到用户的 left/right(x 轴)...

anchor.transform.columns.3.x = anchor.transform.columns.3.x + 0.1

每 50 毫秒(或其他)重复一次。

上面的方法不起作用,因为转换是只获取 属性。

我需要一种方法来改变 AR 对象在 space 中相对于用户的位置,以保持 AR 体验的完整性 - 也就是说,如果您移动设备,AR 对象将移动但也不会 "stuck" 像简单地画在相机上一样,而是移动就像你看到一个人在你走过时移动 - 他们在移动,你也在移动,它看起来很自然。

请注意,此问题的范围仅涉及如何相对于用户 (ARAnchor) 在 space 中移动对象,而不涉及平面 (ARPlaneAnchor) 或另一个检测到的表面 ( ARHitTestResult).

谢谢!

您不需要移动锚点。 (挥手)那不是你要找的API...

ARAnchor 个对象添加到会话实际上是关于 "labeling" 现实世界中的一个点 space 以便您稍后可以参考。 (1,1,1) 点(例如)始终是 (1,1,1) 点——你不能将它移动到其他地方,因为那样它就不再是 (1,1,1) 点了。

做一个二维类比:锚点是有点像视图边界的参考点。系统(或你的另一段代码)告诉视图它的边界在哪里,视图根据这些边界绘制它的内容。 AR 中的锚点为您提供参考点,您可以使用这些参考点来绘制 3D 内容。

您真正要问的是关于在两点之间移动(并动画化)虚拟内容。 ARKit 本身并不是关于显示或动画化虚拟内容的——那里有很多很棒的图形引擎,所以 ARKit 不需要重新发明那个轮子。 ARKit 所做的是为您提供一个真实世界的参考框架,以便您使用 SceneKit 或 SpriteKit(或 Unity 或 Unreal,或使用 Metal 或 GL 构建的自定义引擎)等现有图形技术来显示或动画内容。


既然你提到了尝试用 SpriteKit 来做这件事...当心,它会变得混乱。 SpriteKit 是一个 2D 引擎,虽然 ARSKView 提供了一些方法来插入三维空间,但这些方法有其局限性。

ARSKView 自动更新与 ARAnchor 关联的每个精灵的 xScaleyScalezRotation,提供 3D 透视的错觉。但这仅适用于附加到锚点的节点,并且如上所述,锚点是静态的。

但是,您可以将其他节点添加到场景中,并使用这些相同的属性使这些节点与 ARSKView 管理的节点相匹配。这里有一些代码,您可以在 ARKit/SpriteKit Xcode 模板项目中 add/replace 执行此操作。我们将从一些基本逻辑开始 运行 第三次点击时的弹跳动画(在使用前两次点击放置锚点之后)。

var anchors: [ARAnchor] = []
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

    // Start bouncing on touch after placing 2 anchors (don't allow more)
    if anchors.count > 1 {
        startBouncing(time: 1)
        return
    }
    // Create anchor using the camera's current position
    guard let sceneView = self.view as? ARSKView else { return }
    if let currentFrame = sceneView.session.currentFrame {

        // Create a transform with a translation of 30 cm in front of the camera
        var translation = matrix_identity_float4x4
        translation.columns.3.z = -0.3
        let transform = simd_mul(currentFrame.camera.transform, translation)

        // Add a new anchor to the session
        let anchor = ARAnchor(transform: transform)
        sceneView.session.add(anchor: anchor)
        anchors.append(anchor)
    }
}

然后,使用 SpriteKit 制作动画的乐趣:

var ballNode: SKLabelNode = {
    let labelNode  = SKLabelNode(text: "")
    labelNode.horizontalAlignmentMode = .center
    labelNode.verticalAlignmentMode = .center
    return labelNode
}()
func startBouncing(time: TimeInterval) {
    guard
        let sceneView = self.view as? ARSKView,
        let first = anchors.first, let start = sceneView.node(for: first),
        let last = anchors.last, let end = sceneView.node(for: last)
        else { return }

    if ballNode.parent == nil {
        addChild(ballNode)
    }
    ballNode.setScale(start.xScale)
    ballNode.zRotation = start.zRotation
    ballNode.position = start.position

    let scale = SKAction.scale(to: end.xScale, duration: time)
    let rotate = SKAction.rotate(toAngle: end.zRotation, duration: time)
    let move = SKAction.move(to: end.position, duration: time)

    let scaleBack = SKAction.scale(to: start.xScale, duration: time)
    let rotateBack = SKAction.rotate(toAngle: start.zRotation, duration: time)
    let moveBack = SKAction.move(to: start.position, duration: time)

    let action = SKAction.repeatForever(.sequence([
        .group([scale, rotate, move]),
        .group([scaleBack, rotateBack, moveBack])
        ]))
    ballNode.removeAllActions()
    ballNode.run(action)
}

Here's a video 这样您就可以看到这段代码的实际效果。你会注意到,只有当你不移动相机时,这种错觉才会起作用——这对 AR 来说不是很好。使用SKAction时,我们无法在动画的同时调整动画的start/end状态,所以小球一直在它原来的(screen-space)[=89=之间来回弹跳].

您可以通过直接为球设置动画来做得更好,但这需要大量工作。您需要在每一帧(或每个 view(_:didUpdate:for:) 委托回调):

  1. 保存动画每一端基于锚点的节点的更新位置、旋转和缩放值。每个 didUpdate 回调需要执行两次,因为每个节点都会收到一个回调。

  2. 根据当前时间在两个端点值之间进行插值,计算出动画节点的位置、旋转和缩放值。

  3. 在节点上设置新属性。 (或者也许可以在很短的时间内将其动画化为这些属性,这样它就不会在一帧中跳跃太多?)

将虚假的 3D 错觉硬塞进 2D 图形工具包需要做大量工作——因此我对 SpriteKit 的评论并不是进入 ARKit 的良好第一步。


如果您想为 AR 叠加层提供 3D 定位和动画,使用 3D 图形工具包会容易得多。这是前面示例的重复,但改用 SceneKit。从 ARKit/SceneKit Xcode 模板开始,取出 space 输出,然后将上面相同的 touchesBegan 函数粘贴到 ViewController 中。 (也将 as ARSKView 转换为 as ARSCNView。)

然后,一些用于放置 2D 广告牌精灵的快速代码,通过 SceneKit 匹配 ARKit/SpriteKit 模板的行为:

// in global scope
func makeBillboardNode(image: UIImage) -> SCNNode {
    let plane = SCNPlane(width: 0.1, height: 0.1)
    plane.firstMaterial!.diffuse.contents = image
    let node = SCNNode(geometry: plane)
    node.constraints = [SCNBillboardConstraint()]
    return node
}

// inside ViewController
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
    // emoji to image based on 
    let billboard = makeBillboardNode(image: "⛹️".image())
    node.addChildNode(billboard)
}

最后,为弹跳球添加动画:

let ballNode = makeBillboardNode(image: "".image())
func startBouncing(time: TimeInterval) {
    guard
        let sceneView = self.view as? ARSCNView,
        let first = anchors.first, let start = sceneView.node(for: first),
        let last = anchors.last, let end = sceneView.node(for: last)
        else { return }

    if ballNode.parent == nil {
        sceneView.scene.rootNode.addChildNode(ballNode)
    }

    let animation = CABasicAnimation(keyPath: #keyPath(SCNNode.transform))
    animation.fromValue = start.transform
    animation.toValue = end.transform
    animation.duration = time
    animation.autoreverses = true
    animation.repeatCount = .infinity
    ballNode.removeAllAnimations()
    ballNode.addAnimation(animation, forKey: nil)
}

这次的动画代码比SpriteKit版本短了很多。 Here's how it looks in action.

因为我们一开始是在 3D 中工作,所以我们实际上是在两个 3D 位置之间制作动画 — 与 SpriteKit 版本不同,动画停留在它应该在的位置。 (并且无需额外的直接插值和动画属性的工作。)