iPhone 5 上的 SceneKit 和 CoreMotion "lagging" 但 iPhone 6 上正常

SceneKit and CoreMotion "lagging" on iPhone 5 but normal on iPhone 6

我正在使用 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=MlDdKtHkrYo

代码如下:

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

好吧,这只是一种预感,但我想结果是正确的,所以让我们把它正式化吧!

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

(我怀疑硬件差异在于 iPhone 6 具有足够的性能开销,以至于时序恰好可以正常工作,而较旧的硬件则不然——所以 SceneKit 正在拾取模型位置你的运动数据落后一两帧。)

相反,在同一个 60Hz 循环中执行所有操作。 (真的,这是一个很好的一般性建议,无论您的其他 60Hz 事件源是 CoreMotion 还是其他东西 — 在游戏引擎或类似的实时渲染器中,您应该让渲染的目标帧率决定您所做的一切。)

这意味着您应该 轮询 运动数据,而不是将其推送给您 — 您只关心截至您正在渲染的帧的时间戳的运动状态,不是之前 1/60 秒的样本,或者如果丢帧,则不是最后几个样本。在开始轮询运动数据之前,使用 scene renderer delegate, read CoreMotion's deviceMotion in your renderer:updateAtTime: method and set your model rotations there. Note that you still need to call startDeviceMotionUpdatesUsingReferenceFrame: 连接到 SceneKit 渲染循环。

此外,SceneKit 通常 运行 只有在它知道场景内容需要动画时才会进行渲染循环——例如如果您已将动画附加到场景中的某物,或在事务中设置动画 属性,或使用动作或物理。 (这样一来,如果渲染的下一帧与上一帧相同,它就不会 运行 耗尽设备的电池一次又一次地绘制相同的东西。)但是当您希望在期间进行更改时动画循环,您需要确保它一直处于 运行ning 状态。

设置 playing property on your view to true is actually the right way to do that — it tells SceneKit you want it to keep rendering, not stop when animations stop. (Yeah, the documentation could probably be clearer on that point... if you let Apple know 他们可能会修复它。)


最后,一个 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)

你可以这样做:

cameraRollNode.eulerAngles.z = -roll
cameraPitchNode.eulerAngles.x = pitch
cameraYawNode.eulerAngles.y = yaw