2015-03-26 8 views
5

Ich habe mit SceneKit getestet, also habe ich mich entschieden, eine Virtual Reality Test App zu machen. Ich würde die Daten von der Gerätebewegung erhalten und würde dann die Kamerawinkel basierend auf den Daten ändern. Ich habe auf einem iPhone 6 getestet, also kein Problem. Das Ding wird komisch, wenn ich es auf dem iPhone 5 und einem Freund iPhone 5S, die Kamera wird einige Verzögerung zu drehen, sehr anders als auf iPhone 6, die Instant ist. Die FPS auf Xcode hält auf beiden Geräten rund 60 FPS, ich habe einige Zeittests hinzugefügt und die Bewegungsdaten kommen auf beiden Geräten auf etwa 60 Hz. Ich bin verloren.SceneKit und CoreMotion "lagging" auf dem iPhone 5, aber normal auf dem iPhone 6

Die Basis für den Code ist dieses Projekt: https://github.com/stevenjs/VR-iOS-Experiment

Wenn möglich, würde ich einige Tipps, wie wie man es beheben, bereit, den Code nicht nur & eingefügt sein kopieren. Ich will lernen.

Ich kann es nicht gut erklären, mit Worten, also habe ich einige Videos aufgenommen, Ihnen zu zeigen, was los ist:

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

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

die Ist hier 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 (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) 
    }) 
+0

Gute Frage ... weiß nicht ich. Um eine Vermutung zu riskieren, würde ich sagen, dass es etwas mit der Synchronisierung zwischen der Bewegungsaktualisierungswarteschlange und der SCN-Rendering-Schleife zu tun hat. Anstatt diese beiden 60Hz-Prozesse unabhängig voneinander auszuführen und zu versuchen, zwischen ihnen zu kommunizieren, möchten Sie vielleicht die Dinge in einer einzigen 60-Hz-Schleife kombinieren - also nach Bewegungsdaten in der Render-Delegate-Methode Ihrer Szene suchen. (Siehe [die Übersicht im 'CMMotionManager' Dokument] (https://developer.apple.com/library/ios/documentation/CoreMotion/Reference/CMMotionManager_Class/index.html).) – rickster

+0

@rickster Danke! Ich versuche, dies zu implementieren, wie Sie gesagt haben, jetzt ist mein ViewController der Delegat, aber die Delegate-Methode 'render (aRenderer: SCNSceneRenderer, updateAtTime: NSTimeInterval)' wird nur dreimal aufgerufen. Vielleicht weil die Render-Methode das einzige ist, das die Frames aktualisiert? Es ist Art Kurzschluss selbst? –

+0

Yup, als ich 'sceneView.playing = true' am Ende der Methode hinzufügte, zwang dies das Szenen-Kit, das Rendering für immer fortzusetzen, was funktionierte. Ich frage mich, was ist der richtige Weg, dies zu tun? –

Antwort

11

Nun, das war nur eine Ahnung, aber ich denke, es hat sich als richtig herausgestellt, also machen wir es formal!

Ihr Problem scheint zu sein, dass es zwei 60Hz-Timer/Schleifen gibt, die für die Zeit im Hauptthread konkurrieren - die CoreMotion-Aktualisierungswarteschlange und die SceneKit-Rendering-Schleife. Sie kommunizieren effektiv miteinander, indem Sie den SceneKit-Status im CoreMotion-Update-Handler festlegen und hoffen, dass die SceneKit-Rendering-Schleife sie im selben Frame aufruft.

(Ich vermute, der Hardware-Unterschied ist, dass das iPhone 6 genug Leistungs Overhead hat, dass das Timing passiert, okay, und die ältere Hardware nicht - also SceneKit nimmt Modellpositionen, die ein oder zwei Frames sind Ihre Bewegungsdaten.)

Stattdessen alles in der gleichen 60Hz-Schleife tun. (Dies ist ein guter allgemeiner Hinweis, ob Ihre andere 60Hz-Ereignisquelle CoreMotion oder etwas anderes ist - in einer Game Engine oder einem ähnlichen Echtzeit-Renderer sollten Sie die Zielframerate für das Rendering diktieren lassen, was Sie sonst noch tun.)

Das bedeutet, Sie sollten Polling für Bewegungsdaten sein, nicht bekommen es zu Ihnen geschoben - Sie kümmern sich nur um den Bewegungszustand als der Zeitstempel des Frames, den Sie rendern, nicht die Probe von 1/60 Sekunde vor, oder die letzten paar Beispiele, wenn Sie einen Rahmen fallen gelassen haben. Haken Sie sich mit einer scene renderer delegate in den SceneKit-Rendering-Loop ein, lesen Sie CoreMotion's deviceMotion in Ihrer renderer:updateAtTime: Methode und stellen Sie dort Ihre Modellrotationen ein. Beachten Sie, dass Sie immer noch startDeviceMotionUpdatesUsingReferenceFrame: aufrufen müssen, bevor Sie mit der Suche nach Bewegungsdaten beginnen können.

Auch SceneKit startet normalerweise nur dann seine Rendering-Schleife, wenn es weiß, dass der Szeneninhalt animiert werden muss - z. wenn Sie eine Animation an etwas in der Szene angehängt haben oder eine animierbare Eigenschaft in einer Transaktion festlegen oder Aktionen oder Physik verwenden. (Wenn also das nächste gerenderte Bild das selbe ist wie das letzte, wird es nicht heruntergefahren, wenn der Akku des Geräts immer wieder neu gezeichnet wird.) Aber wenn Sie während der Animationsschleife Änderungen erwarten, Sie müssen sicherstellen, dass es die ganze Zeit läuft.

Die playing -Eigenschaft in Ihrer Ansicht zu true ist eigentlich der richtige Weg, um dies zu tun - es sagt SceneKit Sie wollen es weiter zu machen, nicht aufhören, wenn Animationen aufhören. (Ja, könnte die Dokumentation wahrscheinlich an diesem Punkt klarer sein ... wenn Sie let Apple know sie wahrscheinlich es beheben werde.)


Schließlich ist ein Swift-Tipp: Sie direkt an die Mitglieder in ihren Eigenschaften mit Strukturtypen zuordnen ohne dass ein ganz neuen Wert, so zu tun, anstatt diese zu konstruieren: können

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

Sie dies tun:

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

Dies ist eine großartige Antwort - definitiv verdient mehr Stimmen! Vielen Dank. – StephenT

+0

Ich stimme zu. Gute Antwort. Vielen Dank. – rmvz3