【问题标题】:How do I programmatically move an ARAnchor?如何以编程方式移动 ARAnchor?
【发布时间】:2017-11-08 03:06:30
【问题描述】:

我正在试用新的 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 是一个 get-only 属性。

我需要一种方法来更改 AR 对象在空间中相对于用户的位置,以保持 AR 体验不变 - 这意味着,如果您移动设备,AR 对象将移动但也不会被“卡”在相机上,就像它只是简单地画在上面一样,但是移动就像你会看到一个人在你走过时移动——他们在移动,你也在移动,看起来很自然。

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

谢谢!

【问题讨论】:

    标签: swift arkit


    【解决方案1】:

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

    ARAnchor 对象添加到会话中实际上是在“标记”现实世界空间中的一个点,以便您以后可以参考它。点(1,1,1)(例如)总是点(1,1,1)——你不能把它移到别的地方,因为那样它就不再是(1,1,1)了。

    做一个 2D 类比:锚点是类似于视图边界的参考点。系统(或您的另一段代码)告诉视图它的边界在哪里,并且视图相对于这些边界绘制其内容。 AR 中的锚点为您提供了可用于绘制 3D 内容的参考点。

    您要问的实际上是关于在两点之间移动虚拟内容(和动画移动)。 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)
    }
    

    Here's a video 所以你可以看到这段代码的运行。你会注意到,这种错觉只有在你不移动相机的情况下才会起作用——对于 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。)

    然后,一些用于放置 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 https://stackoverflow.com/a/41021662/957768
        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 版本不同,动画停留在它应该在的位置。 (并且无需额外的工作来直接插入和动画属性。)

    【讨论】:

    • 感谢您抽出宝贵时间回复。我使用的是 2d 版本,Spritekit 场景,只是为了简单起见。如果我将 SKSpriteNode 移动到 ARAnchor 的左侧,当我移动 iPhone 时它​​看起来......不自然。 SpriteNode 随手机移动,向左移动 X 量。但是,如果我有办法移动实际的锚点,看起来会更自然,因为当您移动设备时,对象会相对于设备移动。这本质上就是 Wikitude 等 AR 框架所做的,目的是让 ar 元素看起来不错。
    • 啊。要在 SpriteKit 中移动东西,您需要一种不同的方法,但核心思想是相同的:不要将内容(SKNodes)放在锚点处,而是使用锚点来获取可以应用的位置/比例/等您直接定位和设置动画的其他节点。
    • 顺便说一句,我不确定 SpriteKit 在开始使用 ARKit 时是否保持简单。 AR 本质上是 3D,因此使用 2D 图形技术意味着一些额外的概念恶作剧来挤压额外的维度。
    • @RyanPfister 如果您想在 3D 中移动物体,无论这些物体是 3D 模型还是 2D 精灵,使用 3D 图形工具包都会更轻松。编辑了答案以扩大和澄清。
    • 哇,真正令人惊叹的工作和努力证明了不同之处。 ARKit 是一个真正令人惊叹的框架。哇。
    猜你喜欢
    • 2016-09-28
    • 2014-09-05
    • 2017-01-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多