我正在iOS上使用SceneKit开发一些代码,在我的代码中,我想确定全局z平面上的x和y坐标,其中z为0.0,而x和y由轻击手势确定。我的设置如下:
override func viewDidLoad() { super.viewDidLoad() // create a new scene let scene = SCNScene() // create and add a camera to the scene let cameraNode = SCNNode() let camera = SCNCamera() cameraNode.camera = camera scene.rootNode.addChildNode(cameraNode) // place the camera cameraNode.position = SCNVector3(x: 0, y: 0, z: 15) // create and add an ambient light to the scene let ambientLightNode = SCNNode() ambientLightNode.light = SCNLight() ambientLightNode.light.type = SCNLightTypeAmbient ambientLightNode.light.color = UIColor.darkGrayColor() scene.rootNode.addChildNode(ambientLightNode) let triangleNode = SCNNode() triangleNode.geometry = defineTriangle(); scene.rootNode.addChildNode(triangleNode) // retrieve the SCNView let scnView = self.view as SCNView // set the scene to the view scnView.scene = scene // configure the view scnView.backgroundColor = UIColor.blackColor() // add a tap gesture recognizer let tapGesture = UITapGestureRecognizer(target: self, action: "handleTap:") let gestureRecognizers = NSMutableArray() gestureRecognizers.addObject(tapGesture) scnView.gestureRecognizers = gestureRecognizers } func handleTap(gestureRecognize: UIGestureRecognizer) { // retrieve the SCNView let scnView = self.view as SCNView // check what nodes are tapped let p = gestureRecognize.locationInView(scnView) // get the camera var camera = scnView.pointOfView.camera // screenZ is percentage between z near and far var screenZ = Float((15.0 - camera.zNear) / (camera.zFar - camera.zNear)) var scenePoint = scnView.unprojectPoint(SCNVector3Make(Float(p.x), Float(p.y), screenZ)) println("tapPoint: (\(p.x), \(p.y)) scenePoint: (\(scenePoint.x), \(scenePoint.y), \(scenePoint.z))") } func defineTriangle() -> SCNGeometry { // Vertices var vertices:[SCNVector3] = [ SCNVector3Make(-2.0, -2.0, 0.0), SCNVector3Make(2.0, -2.0, 0.0), SCNVector3Make(0.0, 2.0, 0.0) ] let vertexData = NSData(bytes: vertices, length: vertices.count * sizeof(SCNVector3)) var vertexSource = SCNGeometrySource(data: vertexData, semantic: SCNGeometrySourceSemanticVertex, vectorCount: vertices.count, floatComponents: true, componentsPerVector: 3, bytesPerComponent: sizeof(Float), dataOffset: 0, dataStride: sizeof(SCNVector3)) // Normals var normals:[SCNVector3] = [ SCNVector3Make(0.0, 0.0, 1.0), SCNVector3Make(0.0, 0.0, 1.0), SCNVector3Make(0.0, 0.0, 1.0) ] let normalData = NSData(bytes: normals, length: normals.count * sizeof(SCNVector3)) var normalSource = SCNGeometrySource(data: normalData, semantic: SCNGeometrySourceSemanticNormal, vectorCount: normals.count, floatComponents: true, componentsPerVector: 3, bytesPerComponent: sizeof(Float), dataOffset: 0, dataStride: sizeof(SCNVector3)) // Indexes var indices:[CInt] = [0, 1, 2] var indexData = NSData(bytes: indices, length: sizeof(CInt) * indices.count) var indexElement = SCNGeometryElement( data: indexData, primitiveType: .Triangles, primitiveCount: 1, bytesPerIndex: sizeof(CInt) ) var geo = SCNGeometry(sources: [vertexSource, normalSource], elements: [indexElement]) // material var material = SCNMaterial() material.diffuse.contents = UIColor.redColor() material.doubleSided = true material.shininess = 1.0; geo.materials = [material]; return geo }
如你看到的。我有一个三角形,该三角形的高度为4个单位,宽为4个单位,并设置在以x,y(0.0,0.0)为中心的z平面(z = 0)上。相机是默认的SCNCamera,它沿负z方向看,我将其放置在(0,0,15)处。zNear和zFar的默认值分别为1.0和100.0。在我的handleTap方法中,我获取水龙头的x和y屏幕坐标,并尝试在z = 0.0的情况下查找x和y全局场景坐标。我正在使用unprojectPoint的电话。
unprojectPoint的文档指示
取消投影其z坐标为0.0的点将返回近裁剪平面上的一个点;取消投影z坐标为1.0的点将返回远裁剪平面上的点。
尽管并没有特别说明,对于两者之间的点,近平面和远平面之间存在线性关系,但我做出了这一假设,并将screenZ的值计算为z =的近平面和远平面之间的距离百分比。 0平面位于。要查看答案,我可以单击三角形的角附近,因为我知道它们在全局坐标中的位置。
我的问题是,当我开始更改相机上的zNear和zFar裁剪平面时,我没有获得正确的值,也没有获得一致的值。所以我的问题是,我该怎么做?最后,我将创建一个新的几何并将其放置在与用户单击位置相对应的z平面上。
在此先感谢您的帮助。
3D图形管线中的典型深度缓冲区不是线性的。透视划分导致归一化设备坐标中的深度处于不同的比例。(另请参见此处。)
因此,您要输入的z坐标unprojectPoint实际上并不是您想要的。
unprojectPoint
那么,如何找到与世界空间中的平面匹配的归一化深度坐标呢?好吧,如果该平面垂直于相机(您的相机)是有帮助的。然后,您要做的就是在该平面上投影一个点:
let projectedOrigin = gameView.projectPoint(SCNVector3Zero)
现在,您可以在3D视图+归一化深度空间中找到世界原点的位置。要将2D视图空间中的其他点映射到该平面上,请使用此向量的z坐标:
let vp = gestureRecognizer.locationInView(scnView) let vpWithZ = SCNVector3(x: vp.x, y: vp.y, z: projectedOrigin.z) let worldPoint = gameView.unprojectPoint(vpWithZ)
这样可以在世界空间中获得一个点,该点将单击/点击位置映射到z = 0平面,position如果要向用户显示该位置,则适合用作节点的位置。
position
(请注意,这种方法仅在映射到垂直于相机视线方向的平面上才有效。如果要将视线坐标映射到不同方向的表面上,则in中的归一化深度值vpWithZ将不会恒定)
vpWithZ