VisionPro开发 - 如何让物体总是面向镜头?

635 阅读3分钟

首页:漫游Apple Vision Pro

Code Repo: github.com/xuchi16/vis…

Project Path: github.com/xuchi16/vis…


face_me.png

基本概念

在VisionOS中,可以通过ARKit获取多种感知能力

  1. 平面检测(Plane detection) :检测周围环境中的水平表面(如桌子和地板)以及垂直平面(如墙壁和门),并使用它们来锚定内容。

  2. 世界跟踪(World tracking) :确定设备相对于其周围环境的位置和方向,并添加世界锚点以放置内容。

  3. 手部跟踪(Hand tracking) :使用人的手和手指位置作为自定义手势和交互的输入。

  4. 场景重建(Scene reconstruction) :构建人周围物理环境的网格,并将其融入沉浸式空间以支持交互。

  5. 图像跟踪(Image tracking) :在人的周围环境中寻找已知图像,并使用它们作为自定义内容的锚点。

目前,在visionOS模拟器中,仅支持了World tracking,其他均暂未支持

通过这一感知能力,可以实现面向镜头、跟随镜头等功能,本文展示了让对象总是面向镜头的实现方式。

基本实现

  1. 定义ARKitSessionManager,主要负责初始化ARKitSessionWorldTrackingProvider以及获取设备实时姿态。
import SwiftUI
import ARKit

@MainActor
class ARKitSessionManager: ObservableObject {

    let session = ARKitSession()
    let worldTracking = WorldTrackingProvider()

    func startSession() async {
        if WorldTrackingProvider.isSupported {
            do {
                try await session.run([worldTracking])
            } catch {
                assertionFailure("Failed to run session: (error)")
            }
        }
    }

    func getOriginFromDeviceTransform() -> simd_float4x4 {
        guard let pose = worldTracking.queryDeviceAnchor(atTimestamp: CACurrentMediaTime()) else {
            return simd_float4x4()
        }
        return pose.originFromAnchorTransform
    }
}
  1. 为了让目标物体能够有始终面向摄像机的特性,根据6. 物体移动 一节介绍的ECS模式,需要定义对应的Component和System。
  • Component记录实体状态数据
  • System控制实体行为

这里FaceComponent没有过多状态,只是为了让System获取摄像机信息,记录了对应的ARKitSessionManager供System使用。

import RealityKit
public struct FaceComponent: Component {
    let manager: ARKitSessionManager?
}

FollowSystem中,

  • 通过followEntity.manager!.getOriginFromDeviceTransform()获取设备Transform,并且提取其中位置坐标
  • 通过entity.transform.translation获取实体位置坐标

Entity上提供了一个非常易用的函数look(at:from:upVector:relativeTo:),用于定位和调整实体的朝向,使其从给定位置看向一个目标。我们可以在任何实体上使用这个方法,但它特别适用于定位摄像头和灯光,使其瞄准空间中的特定点。

import RealityKit
public struct FaceSystem: System {
    static let faceQuery = EntityQuery(where: .has(FaceComponent.self))
    
    public init(scene: Scene) {
    }
    
    public func update(context: SceneUpdateContext) {
        let entities = context.scene.performQuery(Self.faceQuery)
        
        for entity in entities {
            guard let followEntity = entity.components[FaceComponent.self] else {
                continue
            }
            
            if followEntity.manager == nil {
                return
            }
            let cameraTransform = followEntity.manager!.getOriginFromDeviceTransform()
            
            let cameraPosition = SIMD3<Float>(cameraTransform.columns.3.x, cameraTransform.columns.3.y, cameraTransform.columns.3.z)
            let entityPosition = entity.transform.translation
            
            entity.look(at: cameraPosition, from: entityPosition, relativeTo: nil)
        }
    }
}
  1. 打开全沉浸空间并加载3D对象,为了后续能够使用ARKit中的信息,我们需要初始化ARKitSessionManager并启动ARKitSession。有了ARKitSessionManager后,将其传递给新建的FaceComponent,最终附属到实体上,这样FaceSystem就能够获得设备和实体的位置信息,并实现追踪效果了。
struct ImmersiveView: View {
    @State var entity = Entity()
    @ObservedObject var arkitSessionManager = ARKitSessionManager()

    var body: some View {
        RealityView { content in
            let tv = try! await Entity(named: "tv_retro")
            tv.transform.rotation = simd_quatf(angle: .pi, axis: [0, 1, 0])
            entity.addChild(tv)

            let followComponent = FaceComponent(manager: arkitSessionManager)
            entity.components[FaceComponent.self] = followComponent
            entity.position = SIMD3<Float>(x: 0, y: 1.2, z: 0)
            content.add(entity)
        }
        .task {
            await arkitSessionManager.startSession()
        }
    }
}

最终效果