Ios 场景中的ARKit 3D头部跟踪

Ios 场景中的ARKit 3D头部跟踪,ios,scenekit,arkit,Ios,Scenekit,Arkit,我正在使用ARKit创建一个增强相机应用程序。当ARSession初始化时,ARSCNView中会显示一个3d角色。我正试图得到角色的 头部跟踪ARCamera的视角,以便在用户移动拍照时,他们始终注视着相机 我使用了苹果的变色龙演示,它添加了一个焦点节点,使用SCNLookAtConstraint跟踪相机的视角,但我得到了一些帮助 奇怪的行为。随着ARCamera平移,头部下降到一侧并旋转。如果我添加SCNTransformConstraint来限制 头部向上/向下/左右移动,它保持垂直,但随

我正在使用ARKit创建一个增强相机应用程序。当ARSession初始化时,ARSCNView中会显示一个3d角色。我正试图得到角色的 头部跟踪ARCamera的视角,以便在用户移动拍照时,他们始终注视着相机

我使用了苹果的变色龙演示,它添加了一个焦点节点,使用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
    }

}
class-Daisy:SCNScene、ARCharacter、CAAnimationDelegate{
//动画装备
私有变量contentRootNode:SCNNode!=SCNNode()
私人var geometryRoot:SCNNode!
私有变量头:SCNNode!
列兵var leftEye:SCNNode!
私人var右眼:SCNNode!
//头部跟踪特性
私有变量focusofhead=SCNNode()
私有let focusNodeBasePosition=simd_float3(0,0.1,0.25)
//状态属性
private var modelLoaded:Bool=false
private var headIsMoving:Bool=false
私有变量shouldTrackCamera:Bool=false
/*
*MARK:-Init方法
*/
重写init(){
super.init()
loadModel()
setupSpecialNodes()
setupConstraints()
}
/*
*标记:-设置方法
*/
func loadModel(){
guard let virtualObjectScene=SCNScene(名为:“daisy_3.dae”,目录:“art.scnassets”)else{
打印(“virtualObjectScene未初始化”)
返回
}
let wrapper=SCNNode()
对于virtualObjectScene.rootNode.childNodes中的子节点{
wrapper.addChildNode(子节点)
}
self.rootNode.addChildNode(contentRootNode)
contentRootNode.addChildNode(包装器)
隐藏()
modelLoaded=true
}
专用函数设置专用节点(){
//将角色装配元素指定给节点
geometryRoot=self.rootNode.childNode(名称为“D_Rig”,递归:true)
head=self.rootNode.childNode(名称为“D_RigFBXASC032Head”,递归:true)
leftEye=self.rootNode.childNode(名称为“D_Eye_L”,递归:true)
rightEye=self.rootNode.childNode(名称为“D_Eye_R”,递归:true)
//设置查找位置节点
focusofhead.simdPosition=focusNodeBasePosition
geometryRoot.addChildNode(头部焦点)
}
/* 
*马克:-头部动画
*/ 
func updateForScene(u场景:ARSCNView){
警卫应该跟踪摄像机,让pointOfView=scene.pointOfView-else{
打印(“不去updateForScene”)
返回
}
followUserWithHead(到:视点)
}
private func followUserWithHead(到pov:SCNNode){
守卫!头正在移动其他{返回}
//将焦点节点更新到“视点”位置
让target=focusofhead.simdConvertPosition(pov.simdWorldPosition,to:nil)
//稍微延迟头部移动,并将其设置为新焦点位置的动画
DispatchQueue.main.asyncAfter(截止日期:.now()+0.2,执行:{
让moveToTarget=SCNAction.move(到:scinvector3(target.x,target.y,target.z),持续时间:1.5)
self.headIsMoving=真
self.focusof head.runAction(moveToTarget,completionHandler:{
self.headIsMoving=false
})
})
}
专用函数setupConstraints(){
让头部约束=SCNLookAtConstraint(目标:头部焦点)
headConstraint.IsGimBallocked=true
让headRotationConstraint=SCNTransformConstraint(inWorldSpace:false){(节点,变换)->中的SCNMatrix4
//仅跟踪向上/向下和侧向移动
var eulerX=node.presentation.eulerAngles.x
var eulerZ=node.presentation.eulerAngles.z
//限制头部移动,使其不会旋转太远
如果eulerXself.rad(90){eulerX=self.rad(90)}
如果eulerZself.rad(30){eulerZ=self.rad(30)}
设tempNode=SCNNode()
tempNode.transform=node.presentation.transform
tempNode.eulerAngles=SCNVector3(eulerX,0,eulerZ)
返回tempNode.transform
}
头?.constraints=[头约束,头旋转约束]
}
//将度转换为弧度的辅助对象
专用函数rad(deg:Float)->Float{
返回度*浮点数/180
}
}
场景编辑器中的模型是:


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

  • followUserWithHead中的目标应已转换其父级的simdWorldPosition,并已从转换为(而不是转换为)

    头的焦点。父!。simdConvertPosition(pov.simdWorldPosition,from:nil)

  • 头部节点的局部坐标不正确。z轴应该是x轴,所以当我得到焦点,头部运动跟踪时,耳朵总是跟着相机

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

    编辑: 我在下面的评论中使用了localFront,这使跟踪工作朝着正确的方向进行。不过,头部确实偶尔会移动。我把这归因于在模型上运行的动画试图应用tra