SceneKit 视图向后渲染
SceneKit view is rendered backwards
我正在尝试我的第一个 SceneKit 应用程序。我的目标是模拟地球表面的视图,并能够将设备的相机指向任何方向并在相机视图上叠加信息。
首先,我只是试图让 SceneKit 相机视图与设备的方向相匹配。为了验证它是否按预期工作,我在特定的纬度和经度坐标处添加了一堆球体。
除一个重要问题外,一切正常。该视图从它应该显示的内容镜像 left/right (east/west)。我花了几个小时尝试调整相机的不同排列方式。
下面是我完整的测试应用视图控制器。我想不出正确的更改组合来正确渲染场景。北极的球体是正确的。从北极延伸到我当前经度的赤道的球线如预期的那样出现在头顶上。这是不正确的球体的其他线。它们是从它们应该的样子镜像 east/west 就好像镜子在我自己的经度上一样。
如果要测试此代码,请使用 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
方法 and/or 和 viewDidLoad
末尾的 motionManager.startDeviceMotionUpdates
闭包的某种组合中进行更改。
我不明白究竟出了什么问题,但我相信 SceneKit 在 eulerAngles
中使用俯仰和偏航的方式与您想象的方式不符。我找不到 chapter/verse 指定正俯仰、滚动和偏航的方向。
通过将 pitch/yaw 设置更改为
,我能够让它工作(至少我认为我已经完成了你想要的)
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 节点球体周围的淡橙色圆圈。
感谢 Hal 从我的场景中创建 "scn" 文件的非常有用的建议。在场景编辑器中查看该场景后,我意识到一切都颠倒了 left/right 因为偏航欧拉角(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)))
我正在尝试我的第一个 SceneKit 应用程序。我的目标是模拟地球表面的视图,并能够将设备的相机指向任何方向并在相机视图上叠加信息。
首先,我只是试图让 SceneKit 相机视图与设备的方向相匹配。为了验证它是否按预期工作,我在特定的纬度和经度坐标处添加了一堆球体。
除一个重要问题外,一切正常。该视图从它应该显示的内容镜像 left/right (east/west)。我花了几个小时尝试调整相机的不同排列方式。
下面是我完整的测试应用视图控制器。我想不出正确的更改组合来正确渲染场景。北极的球体是正确的。从北极延伸到我当前经度的赤道的球线如预期的那样出现在头顶上。这是不正确的球体的其他线。它们是从它们应该的样子镜像 east/west 就好像镜子在我自己的经度上一样。
如果要测试此代码,请使用 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
方法 and/or 和 viewDidLoad
末尾的 motionManager.startDeviceMotionUpdates
闭包的某种组合中进行更改。
我不明白究竟出了什么问题,但我相信 SceneKit 在 eulerAngles
中使用俯仰和偏航的方式与您想象的方式不符。我找不到 chapter/verse 指定正俯仰、滚动和偏航的方向。
通过将 pitch/yaw 设置更改为
,我能够让它工作(至少我认为我已经完成了你想要的) 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 节点球体周围的淡橙色圆圈。
感谢 Hal 从我的场景中创建 "scn" 文件的非常有用的建议。在场景编辑器中查看该场景后,我意识到一切都颠倒了 left/right 因为偏航欧拉角(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)))