如何以编程方式移动ARAnchor?

15

我正在尝试使用新的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)

稍后,将对象移动到用户的左侧/右侧(x轴)...

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

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

上述方法无法实现,因为transform是只读属性。

我需要一种方法来改变AR对象在空间中相对于用户的位置,以保持AR体验的完整性 - 这意味着,如果您移动设备,AR对象将会移动,但也不会像简单地绘制在相机上那样“粘”在相机上,而是像您看到一个人在您走过时移动一样 - 他们在移动,您也在移动,看起来很自然。

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

谢谢!

1个回答

44

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

向会话中添加ARAnchor对象实际上是为了“标记”现实世界中的一个点,以便您之后可以引用它。例如,点(1,1,1)始终是点(1,1,1) - 您不能将其移动到其他地方,因为那么它就不再是点(1,1,1)了。

为了进行二维类比:锚点是参考点,有点像视图的边界。系统(或您代码的另一部分)告诉视图其边界在哪里,视图相对于这些边界绘制其内容。在增强现实中,锚点为您提供了可以用于绘制三维内容的参考点。

你所询问的实际上是关于在两个点之间移动(和动画化移动)虚拟内容。ARKit 本身并不涉及显示或动画化虚拟内容 - 有很多出色的图形引擎,因此 ARKit 不需要重新发明轮子。ARKit 所做的是为您提供一个现实世界的参考框架,以使用现有的图形技术(如 SceneKit 或 SpriteKit(或 Unity 或 Unreal,或使用 Metal 或 GL 构建的自定义引擎)来显示或动画化内容。

既然你提到要使用SpriteKit来实现这个功能...请注意,这会变得很混乱。SpriteKit是一个2D引擎,虽然ARSKView提供了一些方法来将第三个维度塞进去,但这些方法有其限制。

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

不过,你可以向场景中添加其他节点,并使用相同的属性使这些节点与ARSKView管理的节点匹配。下面是一些代码,你可以将其添加/替换到ARKit/SpriteKit Xcode模板项目中以实现这一点。我们将从一些基本逻辑开始,在第三次点击后(在使用前两次点击放置锚点之后)运行一个弹跳动画。

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)
}

这里有一个视频,你可以看到这段代码的实际效果。你会注意到,只要你不移动相机,这个幻觉才能起作用——对于AR来说并不是很好。当使用SKAction时,我们无法在动画过程中调整动画的起始/结束状态,因此球只会在其原始(屏幕空间)位置/旋转/缩放之间来回弹跳。

你可以通过直接对球进行动画来做得更好,但这需要很多工作。你需要在每一帧(或每个view(_:didUpdate:for:)委托回调)上:

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

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

  3. 设置节点的新属性(或者将其动画到这些属性,在非常短的时间内,这样它不会在一帧中跳跃太多)。

为了将一个假的3D视觉效果塞到2D图形工具包中,需要完成很多工作;这也是我关于SpriteKit不是进入ARKit的好第一步的评论所在。


如果你想要在 AR 叠加图层中实现 3D 定位和动画,使用 3D 图形工具包会更加容易。以下是前面例子的重复版,但使用 SceneKit 替代。首先使用 ARKit/SceneKit Xcode 模板,将飞船移除,并将上方相同的 touchesBegan 函数粘贴到 ViewController 中(还需要将 as ARSKView 转换为 as ARSCNView)。

然后,通过一些快速的代码以匹配 ARKit/SpriteKit 模板的行为,在 SceneKit 中放置 2D billboarded 精灵:

// 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 https://dev59.com/RFkT5IYBdhLWcg3wStof#41021662
    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版本要短得多。 这是它的实际效果。

因为我们一开始就在3D中工作,所以我们实际上是在两个3D位置之间进行动画 —— 不像SpriteKit版本,动画会停留在它应该停留的地方。(而且不需要直接插值和动画属性的额外工作。)


谢谢您抽出时间回复。我正在使用2D版本,Spritekit场景,只是为了保持简单。如果我将SKSpriteNode移动到ARAnchor的左侧,当我在移动iPhone时,它看起来...不自然。SpriteNode随着手机一起移动,仅向左移动X量。如果有一种方法可以移动实际锚定点,似乎会更自然,因为当您移动设备时,对象与设备相关移动。这基本上就是AR框架(如Wikitude)所做的,以使ar元素看起来很好。 - Ryan Pfister
啊。在SpriteKit中移动物体需要不同的方法,但核心思想是相同的:不要将内容(SKNodes)放置在锚点上,而是使用锚点获取位置/比例等,然后应用于您直接定位和动画的其他节点。 - rickster
顺便说一句,我不确定SpriteKit在开始使用ARKit时是否保持简单。AR本质上是3D的,因此使用2D图形技术意味着需要进行一些额外的概念折腾来挤入额外的维度。 - rickster
1
@RyanPfister 如果你想在三维空间中移动物体,无论这些物体是三维模型还是二维精灵,使用一个三维图形工具包会更加容易。我编辑了答案以扩展和澄清。 - rickster
1
哇,真是惊人的工作和努力,展示了差异。而 ARKit 真是一个非常惊人的框架。哇。 - Ryan Pfister
显示剩余12条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接