如何在无ARView的RealityViewContent沉浸式空间中实现射线投射?
在visionOS的RealityViewContent中实现射线投射
核心解决方案
在visionOS中,RealityViewContent本身关联着当前的沉浸式场景,你可以直接通过它的scene属性调用射线投射方法,无需依赖已被废弃的ARView。关键是确保实体已被添加到RealityViewContent的场景中(此时实体的scene属性才会被赋值)。
步骤1:基础射线投射实现
以下是从指定原点沿方向投射射线,并获取碰撞实体与本地坐标的示例代码:
import SwiftUI import RealityKit struct ImmersiveView: View { // 定义虚拟相机实体 let virtualCamera = Entity() init() { // 配置虚拟相机 virtualCamera.components[CameraComponent.self] = CameraComponent() virtualCamera.position = [0, 1.6, -3] virtualCamera.look(at: SIMD3<Float>(0, 0, 0)) } var body: some View { RealityView { content in // 添加场景实体和虚拟相机到RealityViewContent let sceneEntity = try! Entity.load(named: "MyScene") content.add(sceneEntity) content.add(virtualCamera) // 执行一次射线投射测试 performRaycast(from: virtualCamera.position, direction: virtualCamera.transform.forward, in: content) } update: { content in // 可在此处执行实时射线投射(例如每一帧更新) } } func performRaycast(from origin: SIMD3<Float>, direction: SIMD3<Float>, in content: RealityViewContent) { // 创建射线查询:查找最近的碰撞实体,匹配所有碰撞掩码 let raycastQuery = RaycastQuery( origin: origin, direction: direction, queryType: .nearest, mask: .all, relativeTo: nil ) // 执行射线投射并处理结果 guard let hitResult = content.scene.performRaycast(raycastQuery).first else { print("未检测到碰撞") return } // 获取碰撞实体 let hitEntity = hitResult.entity print("碰撞实体:\(hitEntity.name)") // 将世界空间碰撞点转换为实体本地坐标 let worldHitPos = hitResult.position let localHitPos = hitEntity.convert(position: worldHitPos, from: nil) print("实体本地碰撞坐标:\(localHitPos)") // 可选:获取碰撞点的UV纹理坐标(用于采样材质颜色) if let uvCoord = hitResult.textureCoordinates { print("UV坐标:\(uvCoord)") } } }
步骤2:虚拟相机像素射线转换
针对你提到的10×10像素虚拟相机,需要将每个像素坐标转换为世界空间的射线方向,代码如下:
func pixelToWorldRay(pixelX: Int, pixelY: Int, camera: Entity, viewSize: CGSize) -> (origin: SIMD3<Float>, direction: SIMD3<Float>) { guard let cameraComponent = camera.components[CameraComponent.self] else { fatalError("目标实体未配置相机组件") } // 将像素坐标转换为NDC(归一化设备坐标,范围-1~1) let ndcX = (Float(pixelX) / Float(viewSize.width)) * 2 - 1 let ndcY = 1 - (Float(pixelY) / Float(viewSize.height)) * 2 // 修正Y轴翻转问题 // 获取相机的视图矩阵与投影矩阵 let viewMatrix = camera.transform.matrix.inverse let projectionMatrix = cameraComponent.projectionMatrix(for: viewSize, near: 0.1, far: 1000) // 计算裁剪空间的射线起点与终点 let startClip = SIMD4<Float>(ndcX, ndcY, -1, 1) let endClip = SIMD4<Float>(ndcX, ndcY, 1, 1) // 转换到世界空间 let invProjectionView = (projectionMatrix * viewMatrix).inverse let startWorld = invProjectionView * startClip let endWorld = invProjectionView * endClip // 归一化方向向量,并返回原点与方向 let origin = SIMD3<Float>(startWorld.x / startWorld.w, startWorld.y / startWorld.w, startWorld.z / startWorld.w) let direction = normalize(SIMD3<Float>(endWorld.x / endWorld.w, endWorld.y / endWorld.w, endWorld.z / endWorld.w) - origin) return (origin, direction) }
使用时,只需遍历10×10的像素坐标,调用该函数生成射线后执行投射:
// 示例:遍历10x10像素 let viewSize = CGSize(width: 10, height: 10) for x in 0..<10 { for y in 0..<10 { let ray = pixelToWorldRay(pixelX: x, pixelY: y, camera: virtualCamera, viewSize: viewSize) performRaycast(from: ray.origin, direction: ray.direction, in: content) } }
关键注意事项
- 确保所有需要参与碰撞检测的实体已添加
CollisionComponent,否则射线无法检测到它们。 - 实体的
scene属性仅在实体被添加到RealityViewContent后才会被赋值,若提前调用射线投射会返回nil。 - 若需要过滤特定实体,可修改
RaycastQuery的mask参数,匹配实体的CollisionComponent掩码。
内容的提问来源于stack exchange,提问作者Reinhard Männer
相关产品推荐
相关产品推荐

