You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何将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

火山引擎 最新活动