如何将3D节点放置在触摸屏幕X/Y坐标、距相机Z轴10cm处
嘿,这个需求其实就是要把屏幕触摸的2D坐标映射到相机前方固定Z距离的3D位置上,我给你整理了一套可行的代码方案,还附带了逻辑解释:
核心思路
我们需要把UIKit的屏幕触摸坐标,转换成**相机局部坐标系中Z=-0.1(也就是前方10cm)**的对应3D点,再转成世界空间坐标赋值给节点。这样不管触摸屏幕哪个位置,节点都会精准对应到相机前方10cm的对应位置。
完整代码实现
假设你已经有了SCNView实例(比如叫scnView)和相机节点cameraNode,可以在触摸事件方法里这么写:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touch = touches.first else { return } // 获取触摸点的屏幕坐标 let touchLocation = touch.location(in: scnView) // 固定节点到相机前方10cm(SceneKit中相机默认看向-Z方向,所以Z=-0.1) let cameraForwardDistance: Float = -0.1 // 1. 把屏幕坐标转成标准化设备坐标(NDC,范围[-1,1]),注意翻转Y轴(UIKit Y轴从上到下,SceneKit从下到上) let viewWidth = Float(scnView.bounds.width) let viewHeight = Float(scnView.bounds.height) let ndcX = (Float(touchLocation.x) / viewWidth) * 2 - 1 let ndcY = 1 - (Float(touchLocation.y) / viewHeight) * 2 // 2. 根据相机视野角,计算当前Z距离下的视锥体半宽/半高 guard let camera = cameraNode.camera else { return } let fovRadians = camera.fieldOfView * .pi / 180 let halfViewHeight = tan(fovRadians / 2) * abs(cameraForwardDistance) let halfViewWidth = halfViewHeight * Float(viewWidth / viewHeight) // 3. 计算相机局部空间中的目标位置 let cameraLocalPos = SCNVector3(ndcX * halfViewWidth, ndcY * halfViewHeight, cameraForwardDistance) // 4. 把相机局部坐标转成世界坐标,赋值给节点 let worldPos = cameraNode.convertPosition(cameraLocalPos, to: nil) sphere.position = worldPos }
关键逻辑解释
- 坐标翻转:UIKit的屏幕Y轴是从上到下的,而SceneKit的视口Y轴是从下到上,所以需要用
1 - ...翻转Y坐标,避免触摸位置和3D点上下颠倒。 - 视锥体计算:利用相机的视野角(
fieldOfView)和固定的Z距离,算出这个距离下相机能看到的画面宽度和高度,这样就能把标准化的触摸坐标转换成实际的3D空间坐标。 - 坐标转换:用
convertPosition(_:to:)把相机局部空间的点转成世界空间,确保节点位置和相机的当前姿态同步(比如相机移动或旋转时,节点依然保持在相机前方对应触摸点的位置)。
如果需要支持触摸移动时实时更新节点位置,把这段逻辑放到touchesMoved方法里就可以啦。
内容的提问来源于stack exchange,提问作者Zeus Eternal




