SceneKit和CoreMotion在iPhone 5上"落后",但在iPhone 6上正常

GBF*_*iel 5 ios core-motion scenekit swift

我正在使用SceneKit测试,所以我决定制作一个虚拟现实测试应用程序.我将从设备运动中获取数据,然后根据数据更改摄像机角度.我在iPhone 6上测试,所以没问题.当我在iPhone 5和朋友的iPhone 5S上运行它时,事情变得奇怪,相机将需要一些延迟旋转,与iPhone 6上的瞬间完全不同.Xcode上的FPS在两个设备上保持大约60 FPS,我添加了一些时间测试,并且两个设备上的运动数据也在60Hz左右.我迷路了.

代码的基础是这个项目:https://github.com/stevenjs/VR-iOS-Experiment

如果可能的话,我想了解一些如何修复它的提示,而不仅仅是准备复制和粘贴的代码.我想学.

我无法用文字解释,所以我录制了一些视频来告诉你发生了什么:

iPhone 6:https://www.youtube.com/watch?v = pdyTEwvsR1I

iPhone 5:https://www.youtube.com/watch?v = Dr.DdKtHkrYo

这是代码:

    // Create Scene
    var scene = SCNScene()
    scnView.scene = scene
    //scnView.autoenablesDefaultLighting = true

    //Add camera to scene.
    let camera = SCNCamera()
    camera.xFov = 45
    camera.yFov = 45

    let camerasNode = SCNNode()
    camerasNode.camera = camera
    camerasNode.position = SCNVector3(x: 0, y: 0, z: 0)
    // The user will be holding their device up (i.e. 90 degrees roll from a flat orientation)
    // so roll the camera by -90 degrees to orient the view correctly
    // otherwise the object will be created "below" the user
    camerasNode.eulerAngles = SCNVector3Make(degreesToRadians(-90.0), 0, 0)

    let cameraRollNode = SCNNode()
    cameraRollNode.addChildNode(camerasNode)

    let cameraPitchNode = SCNNode()
    cameraPitchNode.addChildNode(cameraRollNode)

    let cameraYawNode = SCNNode()
    cameraYawNode.addChildNode(cameraPitchNode)

    scene.rootNode.addChildNode(cameraYawNode)

    scnView.pointOfView = camerasNode

    // Ambient Light
    let ambientLight = SCNLight()
    ambientLight.type = SCNLightTypeAmbient
    ambientLight.color = UIColor(white: 0.1, alpha: 1.0)
    let ambientLightNode = SCNNode()
    ambientLightNode.light = ambientLight
    scene.rootNode.addChildNode(ambientLightNode)

    // Omni Light
    let diffuseLight = SCNLight()
    diffuseLight.type = SCNLightTypeOmni
    diffuseLight.color = UIColor(white: 1.0, alpha: 1.0)
    let diffuseLightNode = SCNNode()
    diffuseLightNode.light = diffuseLight
    diffuseLightNode.position = SCNVector3(x: -30, y: 30, z: 50)
    scene.rootNode.addChildNode(diffuseLightNode)

            let material = SCNMaterial()
    material.diffuse.contents = UIColor.redColor()
    material.locksAmbientWithDiffuse = true
    let material2 = SCNMaterial()
    material2.diffuse.contents = UIColor.whiteColor()
    material2.locksAmbientWithDiffuse = true
    let material3 = SCNMaterial()
    material3.diffuse.contents = UIColor.blueColor()
    material3.locksAmbientWithDiffuse = true
    let material4 = SCNMaterial()
    material4.diffuse.contents = UIColor.purpleColor()
    material4.locksAmbientWithDiffuse = true
    let material5 = SCNMaterial()
    material5.diffuse.contents = UIColor.yellowColor()
    material5.locksAmbientWithDiffuse = true
    let material6 = SCNMaterial()
    material6.diffuse.contents = UIColor.orangeColor()
    material6.locksAmbientWithDiffuse = true


    //Create the box
    let baseBox = SCNBox(width: 5, height: 5, length: 5, chamferRadius: 0)

    baseBox.materials = [material, material2, material3, material4, material5, material6]
    let boxNode = SCNNode(geometry: baseBox)
    boxNode.position = SCNVector3(x: 0, y: 3, z: -10)
    scene.rootNode.addChildNode(boxNode)


    // Respond to user head movement
    motionManager = CMMotionManager()
    motionManager?.deviceMotionUpdateInterval = 1.0 / 60.0

    motionManager?.startDeviceMotionUpdatesUsingReferenceFrame(CMAttitudeReferenceFrameXArbitraryZVertical,
        toQueue: NSOperationQueue.mainQueue(),
        withHandler: { (motion: CMDeviceMotion!, error: NSError!) -> Void in
            self.counter++
            let currentAttitude = motion.attitude
            let roll = Float(currentAttitude.roll)
            let pitch = Float(currentAttitude.pitch)
            let yaw = Float(currentAttitude.yaw)

            //only working at 60FPS on iPhone 6... WHY

            //according to documentation, SCNVector3 from a node is, (pitch, yaw, node)
            cameraRollNode.eulerAngles = SCNVector3Make(0.0, 0.0, -roll)
            cameraPitchNode.eulerAngles = SCNVector3Make(pitch, 0.0, 0.0)
            cameraYawNode.eulerAngles = SCNVector3Make(0.0, yaw, 0.0)
    })
Run Code Online (Sandbox Code Playgroud)

ric*_*ter 11

嗯,这只是一个预感,但我猜结果是正确的,所以让它正式!

您的问题似乎是在主线程上有两个60Hz定时器/循环争用时间 - CoreMotion更新队列和SceneKit渲染循环.你有效地从一个到另一个进行通信,因为你在CoreMotion更新处理程序中设置了SceneKit状态,并希望SceneKit渲染循环在同一帧上拾取它.

(我怀疑硬件的不同之处在于iPhone 6有足够的性能开销,时间恰好可以正常运行,旧硬件没有 - 所以SceneKit正在拾取模型位置,这些位置是运动数据背后的一两帧. )

相反,在相同的60Hz循环中做所有事情.(实际上,这是一个很好的一般建议,无论你的其他60Hz事件源是CoreMotion还是别的东西 - 在游戏引擎或类似的实时渲染器中,你应该让目标帧率用于渲染来决定你做的其他事情.)

这意味着您应该轮询运动数据,而不是将其推送给您 - 您只关心您正在渲染的帧的时间戳的运动状态,而不是之前1/60秒的样本,或者最后几个丢帧时的样本.使用场景渲染器委托挂钩到SceneKit渲染循环,deviceMotion在您的renderer:updateAtTime:方法中读取CoreMotion 并在那里设置模型旋转.请注意,startDeviceMotionUpdatesUsingReferenceFrame:在开始轮询运动数据之前,仍需要调用.

此外,SceneKit通常仅在知道场景内容需要动画时才运行其渲染循环 - 例如,如果您已将动画附加到场景中的某些内容,或者在事务中设置动画属性,或使用动作或物理.(这样,如果渲染的下一帧与最后一帧相同,它将不会在你的设备的电池中一次又一次地绘制相同的东西.)但是当你期望在动画循环期间进行更改时,你需要确保它一直在运行.

playing在视图上设置属性true实际上是正确的方法 - 它告诉SceneKit你希望它保持渲染,而不是在动画停止时停止.(是的,文档在这一点上可能更清楚......如果你让Apple知道他们可能会修复它.)


最后,一个Swift技巧:您可以直接分配给具有结构类型的属性中的成员,而无需构造一个全新的值,因此不要这样做:

cameraRollNode.eulerAngles = SCNVector3Make(0.0, 0.0, -roll)
cameraPitchNode.eulerAngles = SCNVector3Make(pitch, 0.0, 0.0)
cameraYawNode.eulerAngles = SCNVector3Make(0.0, yaw, 0.0)
Run Code Online (Sandbox Code Playgroud)

你可以这样做:

cameraRollNode.eulerAngles.z = -roll
cameraPitchNode.eulerAngles.x = pitch
cameraYawNode.eulerAngles.y = yaw
Run Code Online (Sandbox Code Playgroud)