SceneKit and CoreMotion lag behind on iPhone 5 but fine on iPhone 6

I tested SceneKit, so I decided to make an application for testing virtual reality. I get data from the movement of the device and then change the camera angles based on the data. I tested on the iPhone 6, so no problem. The thing gets strange when I launch it on the iPhone 5 and another iPhone 5S, the camera will linger to rotate, very different from the iPhone 6, which is instant. FPS on Xcode supports about 60 FPS on both devices, I have added several time tests, and the movement data is approaching 60 Hz, as well as on both devices. I am lost.

The basis for the code is this project: https://github.com/stevenjs/VR-iOS-Experiment

If possible, I would like to get some tips on how to fix this, and not just the code, ready to be copied and pasted. I want to learn.

I cannot explain this well with words, so I recorded some videos to show you what is going on:

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

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

Here is the code:

// 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 (ie 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) }) 
+5
source share
1 answer

Well, that was just a hunch, but I think it turned out to be right, so let it become formal!

It seems that your problem is that there are two 60 Hz timers / loops in the main thread - the CoreMotion update queue and the SceneKit rendering cycle. You effectively exchange with each other that you set the SceneKit state in the CoreMotion update handler and hope that the SceneKit rendering cycle picks it up in the same frame.

(I suspect the hardware difference is that the iPhone 6 has sufficient performance overhead, that the time is working fine, and outdated equipment is not, so SceneKit picks up model positions that are a frame or two behind your motion data.)

Instead, do everything in the same 60 Hz cycle. (Actually, this is a good general tip, whether your other 60Hz event source is CoreMotion or something else - in a game engine or similar real-time renderer, you should let the target frame rate for rendering render everything else, what are you doing.)

This means that you should be a survey for motion data, and not for it to be pressed on you - you only care about the state of motion with the time stamp of the frame that you are performing, and not with a countdown from 1/60 second to or last few samples if you drop the frame. Connect to the SceneKit rendering loop with a scene rendering delegate , read CoreMotion deviceMotion in your renderer:updateAtTime: method and set the model rotation there. Note that you still need to call startDeviceMotionUpdatesUsingReferenceFrame: before you can start polling motion data.

In addition, SceneKit usually starts the rendering cycle only when it knows that the content of the scene should be animated. if you attached the animation to something in the scene or set an animation property in a transaction or used actions or physics. (Thus, if the next render of the frame is the same as the last one, it will not work on your device’s battery, repeating the same thing over and over.) But when you expect to make changes during the animation cycle, you need to make sure that it works all the time.

Setting the playing property in your view to true is actually the right way to do this - it tells SceneKit that you want to keep the rendering, and not stop when the animation stops. (Yes, the documentation would probably be clearer on this ... if you let Apple know about this , they will probably fix it.)


Finally, Swift's hint: you can directly assign elements in properties using struct types without having to create a whole new value, so instead:

 cameraRollNode.eulerAngles = SCNVector3Make(0.0, 0.0, -roll) cameraPitchNode.eulerAngles = SCNVector3Make(pitch, 0.0, 0.0) cameraYawNode.eulerAngles = SCNVector3Make(0.0, yaw, 0.0) 

You can do it:

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

Source: https://habr.com/ru/post/1216203/


All Articles