【问题标题】:SceneKit view is rendered backwardsSceneKit 视图向后渲染
【发布时间】:2017-11-18 09:28:05
【问题描述】:

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

首先,我只是想让 SceneKit 相机视图与设备的方向匹配。为了验证它是否按预期工作,我在特定的纬度和经度坐标处添加了一堆球体。

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

下面是我完整的测试应用视图控制器。我无法找出正确的更改组合以使场景正确渲染。北极的球体是正确的。在我目前的经度下,从北极延伸到赤道的球体线正如预期的那样出现在头顶。这是不正确的球体的其他线。它们从它们应有的东西向东/向西镜像,就好像镜子在我自己的经度上一样。

如果您想测试此代码,请使用 SceneKit 创建一个新的游戏项目。将模板 GameViewController.swift 文件替换为以下文件。您还需要将“Privacy - Location When In Use Description”键添加到 Info.plist。我还建议调整for lon in ... 行,使数字以您自己的经度开始或结束。然后您可以看到球体是否被绘制在显示屏的正确部分。这可能还需要对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 闭包的某种组合中进行更改。

【问题讨论】:

    标签: ios swift core-location scenekit core-motion


    【解决方案1】:

    我不明白到底出了什么问题,但我相信 SceneKit 在 eulerAngles 中使用俯仰和偏航的方式与您想象的方式不符。我找不到指定正俯仰、滚动和偏航方向的章节/诗句。

    通过将俯仰/偏航设置更改为

    ,我能够让它工作(至少我认为我已经完成了你想要做的事情)
        ballArm.eulerAngles.x = -1 * Float(radians(Double(lat)))
    

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

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

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

    【讨论】:

    • 感谢您为我指明了正确的方向。您创建“scn”文件并在场景编辑器中查看的建议表明我的世界是镜像的。我在错误的方向绕 y 轴旋转。所以实际上我需要否定的是eulerAngles.y,而不是x 值。我需要对相机和球体都这样做。因此,虽然您建议的答案实际上并不是我问题的正确解决方案,但您的回答对我找到正确的解决方案非常有帮助。
    【解决方案2】:

    感谢 Hal 提出的从我的场景创建“scn”文件的非常有用的建议。在场景编辑器中查看该场景后,我意识到一切都是左右颠倒的,因为偏航欧拉角(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)))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-05-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-10
      • 2016-02-09
      • 2019-06-25
      • 1970-01-01
      相关资源
      最近更新 更多