SceneKit视图呈现向后

我正在尝试我的第一个SceneKit应用程序。 我的目标是模拟地球表面的视图,并能够将设备的摄像机指向任意方向,并在摄像机视图上叠加信息。

首先,我只是试图让SceneKit相机视图匹配设备的方向。 为了validation它是否正常工作,我在特定的经度和纬度坐标处添加了一堆球体。

除了一个重要的问题,一切都在起作用。 该视图从应该显示的内容镜像左/右(东/西)。 我花了几个小时尝试调整相机的不同排列。

以下是我的完整testing应用程序视图控制器 我无法弄清楚正确的变化组合,让场景正确渲染。 北极的球体是正确的。 按我自己目前的经度从北极延伸到赤道的球体线如预期的那样出现在头顶上。 这是领域的其他线是不正确的。 它们从东西向镜像,就像镜子在我自己的经度上一样。

如果你想testing这个代码,使用SceneKit创build一个新的游戏项目。 将模板GameViewController.swiftreplace为下面的文件。 您还需要在Info.plist中添加“隐私 – 使用说明中的位置”键。 我也build议调整for lon in ...行中的for lon in ...以便数字以您自己的经度开始或结束。 然后您可以看到球体是否正在显示的正确部分上绘制。 这可能还需要对UIColor(hue: CGFloat(lon + 104) * 2 / 255.0颜色UIColor(hue: CGFloat(lon + 104) * 2 / 255.0

 import UIKit import QuartzCore import SceneKit import CoreLocation import CoreMotion let EARTH_RADIUS = 6378137.0 class GameViewController: UIViewController, CLLocationManagerDelegate { var motionManager: CMMotionManager! var scnCameraArm: SCNNode! var scnCamera: SCNNode! var locationManager: CLLocationManager! var pitchAdjust = 1.0 var rollAdjust = -1.0 var yawAdjust = 0.0 func radians(_ degrees: Double) -> Double { return degrees * Double.pi / 180 } func degrees(_ radians: Double) -> Double { return radians * 180 / Double.pi } func setCameraPosition(lat: Double, lon: Double, alt: Double) { let yaw = lon let pitch = lat scnCameraArm.eulerAngles.y = Float(radians(yaw)) scnCameraArm.eulerAngles.x = Float(radians(pitch)) scnCameraArm.eulerAngles.z = 0 scnCamera.position = SCNVector3(x: 0.0, y: 0.0, z: Float(alt + EARTH_RADIUS)) } func setCameraPosition(loc: CLLocation) { setCameraPosition(lat: loc.coordinate.latitude, lon: loc.coordinate.longitude, alt: loc.altitude) } // MARK: - UIViewController methods override func viewDidLoad() { super.viewDidLoad() // create a new scene let scene = SCNScene() let scnCamera = SCNNode() let camera = SCNCamera() camera.zFar = 2.5 * EARTH_RADIUS scnCamera.camera = camera scnCamera.position = SCNVector3(x: 0.0, y: 0.0, z: Float(EARTH_RADIUS)) self.scnCamera = scnCamera let scnCameraArm = SCNNode() scnCameraArm.position = SCNVector3(x: 0, y: 0, z: 0) scnCameraArm.addChildNode(scnCamera) self.scnCameraArm = scnCameraArm scene.rootNode.addChildNode(scnCameraArm) // create and add an ambient light to the scene let ambientLightNode = SCNNode() ambientLightNode.light = SCNLight() ambientLightNode.light!.type = .ambient ambientLightNode.light!.color = UIColor.darkGray scene.rootNode.addChildNode(ambientLightNode) // retrieve the SCNView let scnView = self.view as! SCNView // set the scene to the view scnView.scene = scene //scnView.pointOfView = scnCamera // Draw spheres over part of the western hemisphere for lon in stride(from: 0, through: -105, by: -15) { for lat in stride(from: 0, through: 90, by: 15) { let mat4 = SCNMaterial() if lat == 90 { mat4.diffuse.contents = UIColor.yellow } else if lat == -90 { mat4.diffuse.contents = UIColor.orange } else { //mat4.diffuse.contents = UIColor(red: CGFloat(lat + 90) / 255.0, green: CGFloat(lon + 104) * 4 / 255.0, blue: 1, alpha: 1) mat4.diffuse.contents = UIColor(hue: CGFloat(lon + 104) * 2 / 255.0, saturation: 1, brightness: CGFloat(255 - lat * 2) / 255.0, alpha: 1) } let ball = SCNSphere(radius: 100000) ball.firstMaterial = mat4 let ballNode = SCNNode(geometry: ball) ballNode.position = SCNVector3(x: 0.0, y: 0.0, z: Float(100000 + EARTH_RADIUS)) let ballArm = SCNNode() ballArm.position = SCNVector3(x: 0, y: 0, z: 0) ballArm.addChildNode(ballNode) scene.rootNode.addChildNode(ballArm) ballArm.eulerAngles.y = Float(radians(Double(lon))) ballArm.eulerAngles.x = Float(radians(Double(lat))) } } // configure the view scnView.backgroundColor = UIColor(red: 0, green: 191/255, blue: 255/255, alpha: 1) // sky blue locationManager = CLLocationManager() locationManager.delegate = self locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation let auth = CLLocationManager.authorizationStatus() switch auth { case .authorizedWhenInUse: locationManager.startUpdatingLocation() case .notDetermined: locationManager.requestWhenInUseAuthorization() default: break } motionManager = CMMotionManager() motionManager.deviceMotionUpdateInterval = 1 / 30 motionManager.startDeviceMotionUpdates(using: .xTrueNorthZVertical, to: OperationQueue.main) { (motion, error) in if error == nil { if let motion = motion { //print("pitch: \(self.degrees(motion.attitude.roll * self.pitchAdjust)), roll: \(self.degrees(motion.attitude.pitch * self.rollAdjust)), yaw: \(self.degrees(-motion.attitude.yaw))") self.scnCamera.eulerAngles.z = Float(motion.attitude.yaw + self.yawAdjust) self.scnCamera.eulerAngles.x = Float(motion.attitude.roll * self.pitchAdjust) self.scnCamera.eulerAngles.y = Float(motion.attitude.pitch * self.rollAdjust) } } } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) if UIApplication.shared.statusBarOrientation == .landscapeRight { pitchAdjust = -1.0 rollAdjust = 1.0 yawAdjust = Double.pi } else { pitchAdjust = 1.0 rollAdjust = -1.0 yawAdjust = 0.0 } } override var shouldAutorotate: Bool { return false } override var prefersStatusBarHidden: Bool { return true } override var supportedInterfaceOrientations: UIInterfaceOrientationMask { return .landscape } // MARK: - CLLocationManagerDelegate methods func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { if status == .authorizedWhenInUse { manager.startUpdatingLocation() } } func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { for loc in locations { print(loc) if loc.horizontalAccuracy > 0 && loc.horizontalAccuracy <= 100 { setCameraPosition(loc: loc) } } } func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { } } 

可能需要在setCameraPosition方法和/或viewDidLoad结尾处的motionManager.startDeviceMotionUpdates闭包的某些组合中进行更改。

我不明白到底发生了什么问题,但是我相信SceneKit在eulerAngles中使用俯仰和偏航的方式并不符合你eulerAngles的方式。 我无法find指定哪个方向正向俯仰,俯仰和偏航的章节。

我能够通过改变音高/偏航设置来使其工作(至less我认为我已经完成了你所要做的)

  ballArm.eulerAngles.x = -1 * Float(radians(Double(lat))) 

我还添加了一些debugging颜色和标签,并将场景归档为.SCN格式,以将其加载到Xcode场景编辑器中(我认为Xcode 9将在debugging器中为您执行此操作)。 在每个节点上放一个名字可以让你看到编辑器中的内容。 修改后的代码:

  // Draw spheres over part of the western hemisphere for lon in stride(from: 0, through: -105, by: -15) { for lat in stride(from: 0, through: 90, by: 15) { let mat4 = SCNMaterial() if lon == 0 { mat4.diffuse.contents = UIColor.black } else if lon == -105 { mat4.diffuse.contents = UIColor.green } else if lat == 90 { mat4.diffuse.contents = UIColor.yellow } else if lat == 0 { mat4.diffuse.contents = UIColor.orange } else { mat4.diffuse.contents = UIColor(red: CGFloat(lat + 90) / 255.0, green: CGFloat(lon + 104) * 4 / 255.0, blue: 1, alpha: 1) //mat4.diffuse.contents = UIColor(hue: CGFloat(lon + 104) * 2 / 255.0, saturation: 1, brightness: CGFloat(255 - lat * 2) / 255.0, alpha: 1) //mat4.diffuse.contents = UIColor.green } let ball = SCNSphere(radius: 100000) ball.firstMaterial = mat4 let ballNode = SCNNode(geometry: ball) ballNode.position = SCNVector3(x: 0.0, y: 0.0, z: Float(100000 + EARTH_RADIUS)) let ballArm = SCNNode() ballArm.position = SCNVector3(x: 0, y: 0, z: 0) // debugging label ballArm.name = "\(lat) \(lon)" ballArm.addChildNode(ballNode) scene.rootNode.addChildNode(ballArm) ballArm.eulerAngles.y = Float(radians(Double(lon))) ballArm.eulerAngles.x = -1 * Float(radians(Double(lat))) } } // configure the view scnView.backgroundColor = UIColor.cyan let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) let docsFolderURL = urls[urls.count - 1] let archiveURL = docsFolderURL.appendingPathComponent("rmaddy.scn") let archivePath = archiveURL.path // for copy/paste from Simulator's file system print (archivePath) let archiveResult = NSKeyedArchiver.archiveRootObject(scene, toFile: archivePath) print(archiveResult) 

这是场景编辑器中场景的截图。 请注意在节点列表中select的lat 45 / lon -90节点球体周围微弱的橙色圆圈。

在这里输入图像说明

感谢Hal对于从我的场景中创build“scn”文件的非常有帮助的build议。 在场景编辑器中观看那个场景之后,我意识到一切都是左右颠倒的,因为偏航欧拉angular(Y轴)旋转与我所想的相反。 所以当我将节点的eulerAngles.y设置为所需的经度时,我正在以我想要的相反方向旋转。 但是由于相机有相同的错误,我所在的球体处于正确的位置,但是其他的都是从左到右的。

解决方法是否定球体和摄像机位置的经度值。

 scnCameraArm.eulerAngles.y = Float(radians(yaw)) 

需要是:

 scnCameraArm.eulerAngles.y = -Float(radians(yaw)) 

和:

 ballArm.eulerAngles.y = Float(radians(Double(lon))) 

需要是:

 ballArm.eulerAngles.y = -Float(radians(Double(lon)))