You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在无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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 03:27:35