ARKit 3D头部跟踪场景

时间:2018-01-04 12:50:15

标签: ios scenekit arkit

我正在使用ARKit来创建增强型相机应用。当ARSession初始化时,ARSCNView中会显示一个3d字符。我想要获得这个角色 前往追踪ARCamera的观点,以便他们在用户移动拍照时始终关注相机。

我使用过Apple的变色龙演示,它使用SCNLookAtConstraint添加了一个跟踪摄像机视角的焦点节点但我得到了 奇怪的行为。头部落到侧面并随着ARCamera平移而旋转。如果我添加一个SCNTransformConstraint来限制 头部向上/向下/左右移动,它保持垂直,但然后看起来远离并且不会跟踪。

我尝试分开变色龙演示,看看为什么我的工作不起作用,但几天之后我就被卡住了。

我使用的代码是:

class Daisy: SCNScene, ARCharacter, CAAnimationDelegate {

    // Rig for animation
    private var contentRootNode: SCNNode! = SCNNode()
    private var geometryRoot: SCNNode!
    private var head: SCNNode!
    private var leftEye: SCNNode!
    private var rightEye: SCNNode!

    // Head tracking properties
    private var focusOfTheHead = SCNNode()
    private let focusNodeBasePosition = simd_float3(0, 0.1, 0.25)

    // State properties
    private var modelLoaded: Bool = false
    private var headIsMoving: Bool = false
    private var shouldTrackCamera: Bool = false



    /*
    * MARK: - Init methods
    */

    override init() {

        super.init()

        loadModel()
        setupSpecialNodes()
        setupConstraints()

    }


    /*
    * MARK: - Setup methods
    */

    func loadModel() {

        guard let virtualObjectScene = SCNScene(named: "daisy_3.dae", inDirectory: "art.scnassets") else {
            print("virtualObjectScene not intialised")
            return
        }

        let wrapper = SCNNode()

        for child in virtualObjectScene.rootNode.childNodes {
            wrapper.addChildNode(child)
        }

        self.rootNode.addChildNode(contentRootNode)

        contentRootNode.addChildNode(wrapper)

        hide()

        modelLoaded = true

    }


    private func setupSpecialNodes() {

        // Assign characters rig elements to nodes
        geometryRoot = self.rootNode.childNode(withName: "D_Rig", recursively: true)
        head = self.rootNode.childNode(withName: "D_RigFBXASC032Head", recursively: true)
        leftEye = self.rootNode.childNode(withName: "D_Eye_L", recursively: true)
        rightEye = self.rootNode.childNode(withName: "D_Eye_R", recursively: true)

        // Set up looking position nodes
        focusOfTheHead.simdPosition = focusNodeBasePosition

        geometryRoot.addChildNode(focusOfTheHead)

    }


    /* 
    * MARK: - Head animations
    */ 

    func updateForScene(_ scene: ARSCNView) {

        guard shouldTrackCamera, let pointOfView = scene.pointOfView else {
            print("Not going to updateForScene")
            return
        }

        followUserWithHead(to: pointOfView)

    }

    private func followUserWithHead(to pov: SCNNode) {

        guard !headIsMoving else { return }

        // Update the focus node to the point of views position
        let target = focusOfTheHead.simdConvertPosition(pov.simdWorldPosition, to: nil)

        // Slightly delay the head movement and the animate it to the new focus position
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: {
            let moveToTarget = SCNAction.move(to: SCNVector3(target.x, target.y, target.z), duration: 1.5)
            self.headIsMoving = true
            self.focusOfTheHead.runAction(moveToTarget, completionHandler: {
                self.headIsMoving = false
            })
        })

    }



    private func setupConstraints() {

        let headConstraint = SCNLookAtConstraint(target: focusOfTheHead)
        headConstraint.isGimbalLockEnabled = true

        let headRotationConstraint = SCNTransformConstraint(inWorldSpace: false) { (node, transform) -> SCNMatrix4 in

            // Only track the up/down and side to side movement
            var eulerX = node.presentation.eulerAngles.x
            var eulerZ = node.presentation.eulerAngles.z

            // Restrict the head movement so it doesn't rotate too far
            if eulerX < self.rad(-90) { eulerX = self.rad(-90) }
            if eulerX > self.rad(90) { eulerX = self.rad(90) }

            if eulerZ < self.rad(-30) { eulerZ = self.rad(-30) }
            if eulerZ > self.rad(30) { eulerZ = self.rad(30) }

            let tempNode = SCNNode()
            tempNode.transform = node.presentation.transform
            tempNode.eulerAngles = SCNVector3(eulerX, 0, eulerZ)
            return tempNode.transform

        }

        head?.constraints = [headConstraint, headRotationConstraint]

    }

    // Helper to convert degrees to radians
    private func rad(_ deg: Float) -> Float {
        return deg * Float.pi / 180
    }

}

场景编辑器中的模型是:

enter image description here

1 个答案:

答案 0 :(得分:1)

我已经解决了我遇到的问题。有两个问题:

  1. followUserWithHead中的目标应该已经转换了它的父亲的simdWorldPosition并且已经从(而不是)转换为

    focusOfTheHead.parent!.simdConvertPosition(pov.simdWorldPosition,from:nil)

  2. 头节点的本地坐标不正确。 z轴应该是x轴,因此当我获得焦点时,头部运动跟踪,耳朵始终跟随相机。

  3. enter image description here

    我没有意识到Xcode中的 Debug View Hierarchy 将显示SCNScene的详细信息。这有助于我调试场景并找到节点跟踪的位置。您可以将场景导出为dae,然后加载到SceneKit编辑器

    修改 我使用localFront作为下面评论中建议的mnuages,它使跟踪工作在正确的方向。虽然头部偶尔会移动。我把它放在模型上运行的动画上,试图应用一个转换,然后在下一个更新周期中进行更改。我决定从头部移除跟踪并使用相同的方法仅跟踪眼睛。