realitykit

realitykit

热门

使用 RealityKit 和 ARKit 构建 iOS 增强现实 (AR) 与 3D 体验。适用于添加 RealityView 内容、加载实体 (Entity) 或 USDZ 模型、将对象锚定到平面或世界坐标、区分实体碰撞检测 (Hit Test) 与 ARKit 真实世界射线检测 (Raycast)、处理 AR 相机可用性、世界追踪、场景更新,以及 RealityKit 实体的手势与交互等场景。

961Star
48Fork
更新于 2026/7/31
SKILL.md
只读
名称
realitykit
描述

使用 RealityKit 和 ARKit 构建 iOS 增强现实 (AR) 与 3D 体验。适用于添加 RealityView 内容、加载实体 (Entity) 或 USDZ 模型、将对象锚定到平面或世界坐标、区分实体碰撞检测 (Hit Test) 与 ARKit 真实世界射线检测 (Raycast)、处理 AR 相机可用性、世界追踪、场景更新,以及 RealityKit 实体的手势与交互等场景。

RealityKit

在 iOS 上使用 RealityKit 进行渲染并结合 ARKit 实现世界追踪,构建地道的 AR 体验。涵盖 RealityView、实体管理、射线检测、场景理解以及基于手势的交互。适配 Swift 6.3 / iOS 26+。

目录

环境配置

项目配置

  1. Info.plist 中添加 NSCameraUsageDescription 权限说明
  2. 在 iOS 上,RealityViewCameraContent 默认会显示 AR 相机视图(iOS 18+、macOS 15+);若需明确降级至非 AR 模式,请将相机模式设为 .virtual
  3. 基础 AR 体验无需额外申请权限配置 (Entitlement)。如果 AR 是应用的核心功能,请在 Info.plist 的 required-device capability 中添加 arkit;否则请通过 isSupported 动态控制 AR 界面展示。

设备要求

在展示 AR UI 之前,务必针对具体的 AR 配置检查其 isSupported 属性。

import ARKit

guard ARWorldTrackingConfiguration.isSupported else {
    showUnsupportedDeviceMessage()
    return
}

核心类型

类型 支持平台 作用
RealityView iOS 18+, visionOS 1+ 承载 RealityKit 内容的 SwiftUI 视图
RealityViewCameraContent iOS 18+, macOS 15+ 在 iOS 上通过 AR 相机视图展示内容,在 macOS 上展示非 AR 内容
Entity 全平台 所有场景对象的基类
ModelEntity 全平台 带有可视 3D 模型的 Entity
AnchorEntity 全平台 将实体锚定到真实世界的指定锚点

RealityView 基础

RealityView 是 RealityKit 在 SwiftUI 中的入口。
RealityViewCameraContent 是 iOS/macOS 对应的内容类型。在 iOS 上,它默认使用 AR 相机视图;当手动设置或 AR/相机权限不可用时,可使用 content.camera = .virtual 切换至非 AR 模式。

import ARKit
import SwiftUI
import RealityKit

struct ARExperienceView: View {
    var body: some View {
        RealityView { (content: RealityViewCameraContent) in
            if !ARWorldTrackingConfiguration.isSupported {
                content.camera = .virtual
            }

            let sphere = ModelEntity(
                mesh: .generateSphere(radius: 0.05),
                materials: [SimpleMaterial(
                    color: .blue,
                    isMetallic: true
                )]
            )
            sphere.position = [0, 0, -0.5]  // 位于相机前方 50 厘米
            content.add(sphere)
        }
    }
}

Make 与 Update 范式

使用 update 闭包来响应 SwiftUI 的状态更新:

struct PlacementView: View {
    @State private var modelColor: UIColor = .red

    var body: some View {
        RealityView { content in
            let box = ModelEntity(
                mesh: .generateBox(size: 0.1),
                materials: [SimpleMaterial(
                    color: .red,
                    isMetallic: false
                )]
            )
            box.name = "colorBox"
            box.position = [0, 0, -0.5]
            content.add(box)
        } update: { content in
            if let box = content.entities.first(
                where: { $0.name == "colorBox" }
            ) as? ModelEntity {
                box.model?.materials = [SimpleMaterial(
                    color: modelColor,
                    isMetallic: false
                )]
            }
        }

        Button("切换颜色") {
            modelColor = modelColor == .red ? .green : .red
        }
    }
}

加载与创建实体

从 USDZ 文件加载

建议异步加载 3D 模型,避免阻塞主线程:

RealityView { content in
    if let robot = try? await ModelEntity(named: "robot") {
        robot.position = [0, -0.2, -0.8]
        robot.scale = [0.01, 0.01, 0.01]
        content.add(robot)
    }
}

添加组件 (Component)

实体基于 ECS(实体组件系统)架构设计。可通过挂载不同的组件来赋予实体特定的行为特征:

let box = ModelEntity(
    mesh: .generateBox(size: 0.1),
    materials: [SimpleMaterial(color: .red, isMetallic: false)]
)

// 赋予物理特性,使其响应物理引擎
box.components.set(PhysicsBodyComponent(
    massProperties: .default,
    material: .default,
    mode: .dynamic
))

// 添加碰撞形状以支持交互检测
box.components.set(CollisionComponent(
    shapes: [.generateBox(size: [0.1, 0.1, 0.1])]
))

// 开启输入响应,接收手势事件
box.components.set(InputTargetComponent())

锚定与放置

AnchorEntity

使用 AnchorEntity 将内容绑定到检测到的物理表面或特定的世界坐标:

RealityView { content in
    // 锚定到水平面
    let floorAnchor = AnchorEntity(.plane(
        .horizontal,
        classification: .floor,
        minimumBounds: [0.2, 0.2]
    ))

    let model = ModelEntity(
        mesh: .generateBox(size: 0.1),
        materials: [SimpleMaterial(color: .orange, isMetallic: false)]
    )
    floorAnchor.addChild(model)
    content.add(floorAnchor)
}

锚定目标类型

目标类型 说明
.plane(.horizontal, ...) 水平面(如地面、桌面)
.plane(.vertical, ...) 垂直面(如墙面)
.plane(.any, ...) 任意检测到的平面
.world(transform:) 固定的世界空间坐标点

射线检测

请务必区分 RealityKit 的场景内查询与 ARKit 真实世界的射线检测:

  • RealityViewCameraContent.ray(through:in:to:) 在 RealityKit 坐标系中返回一条相机射线。它用于将屏幕点投射到虚拟场景中,并不代表检测到了真实物理表面。
  • RealityViewCameraContent.hitTest(point:in:query:mask:) 用于检测带有 CollisionComponent 碰撞形状的虚拟实体。请将碰撞形状用于实体选中和手势拖拽,而不是用于 ARKit 的平面检测。
  • 若只需在检测到的平面上进行简单放置,请直接使用 AnchorEntity(.plane(...))
  • 当需要与真实物理表面进行一次性相交检测时,请结合使用 ARKit 的 ARRaycastQueryARSession.raycast(_:),随后通过 AnchorEntity(raycastResult:) 挂载实体。
let results = session.raycast(query)
if let result = results.first {
    let anchor = AnchorEntity(raycastResult: result)
    anchor.addChild(model)
    content.add(anchor)
}

切勿将实体的 Hit Test 混淆或替代为 ARKit 的物理表面 Raycast。

手势与交互

若要实现手势交互,实体必须同时添加用于检测碰撞的 CollisionComponent 和用于响应输入的 InputTargetComponent

实体拖拽手势

struct DraggableARView: View {
    var body: some View {
        RealityView { content in
            let box = ModelEntity(
                mesh: .generateBox(size: 0.1),
                materials: [SimpleMaterial(color: .blue, isMetallic: true)]
            )
            box.position = [0, 0, -0.5]
            box.components.set(CollisionComponent(
                shapes: [.generateBox(size: [0.1, 0.1, 0.1])]
            ))
            box.components.set(InputTargetComponent())
            box.name = "draggable"
            content.add(box)
        }
        .gesture(
            DragGesture()
                .targetedToAnyEntity()
                .onChanged { value in
                    let entity = value.entity
                    guard let parent = entity.parent else { return }
                    entity.position = value.convert(
                        value.location3D,
                        from: .local,
                        to: parent
                    )
                }
        )
    }
}

场景理解

逐帧更新

连续的场景更新工作应当订阅 SceneEvents.Update,而非使用 SwiftUI 的定时器来驱动 RealityKit。保持订阅有效并使用 event.deltaTime;详情参阅 实体动画

平台架构边界

在 visionOS 上,ARKit 提供了包含 ARKitSessionWorldTrackingProviderPlaneDetectionProvider 在内的全新 API 体系。这些 visionOS 专属类型在 iOS 上不可用。在 iOS 端,RealityKit 会自动通过 RealityViewCameraContent 处理世界追踪。

针对 iOS 的架构设计或迁移,请先通过 ARWorldTrackingConfiguration.isSupported 检查 AR 可用性,使用 RealityViewCameraContent 承载内容,并利用 AnchorEntity 放置基于 Entity/ModelEntity 构建的场景。

职责划分: CollisionComponent + InputTargetComponent 负责 RealityKit 内部的交互;AccessibilityComponent 负责实体的无障碍元数据;而更具体的 SwiftUI 手势逻辑以及 VoiceOver / 切换控制 (Switch Control) 策略则应交给上层组件处理。

若项目中存在现有的 SCNView/SCNNode 代码,请将其作为独立的 SceneKit 分支运行或彻底迁移至 RealityKit,切勿在同一个场景树中混合使用。

常见误区

忌:跳过 AR 设备能力检查

在展示 AR 界面前,请务必先执行 环境配置 章节中的配置支持度检查。若设备不支持,应降级显示非 AR 内容或展示明确的不可用提示。

忌:同步加载大型模型

在主线程上同步加载大型 USDZ 文件会导致界面丢帧甚至卡死。RealityViewmake 闭包本身支持 async —— 请务必使用异步加载。

// ❌ 错误做法 —— 同步加载阻塞主线程
RealityView { content in
    let model = try! Entity.load(named: "large-scene")
    content.add(model)
}

// ✅ 正确做法 —— 异步加载
RealityView { content in
    if let model = try? await ModelEntity(named: "large-scene") {
        content.add(model)
    }
}

忌:可交互实体漏加碰撞与输入组件

支持交互的实体必须同时具备 手势与交互 中提到的两个组件;如果缺失,点击和拖拽手势将会直接穿透该实体。

忌:在 update 闭包中创建新实体

每次 SwiftUI 状态发生变化时,update 闭包都会被重新调用。在 update 中创建实体会导致每次渲染重新叠加重复的对象。

// ❌ 错误做法 —— 每次状态更新都会重复创建并添加实体
RealityView { content in
    // 保持为空
} update: { content in
    let sphere = ModelEntity(mesh: .generateSphere(radius: 0.05))
    content.add(sphere)  // 每次 update 都会反复 add
}

// ✅ 正确做法 —— 在 make 中创建,在 update 中修改
RealityView { content in
    let sphere = ModelEntity(mesh: .generateSphere(radius: 0.05))
    sphere.name = "mySphere"
    content.add(sphere)
} update: { content in
    if let sphere = content.entities.first(
        where: { $0.name == "mySphere" }
    ) as? ModelEntity {
        // 仅修改已有实体的属性
        sphere.position.y = newYPosition
    }
}

忌:忽视相机权限处理

iOS 上的 RealityKit 依赖相机访问权限。若用户拒绝授权,视图将直接黑屏且没有任何原因提示。

// ❌ 错误做法 —— 未处理权限逻辑
RealityView { content in
    // 用户拒绝相机权限时会直接黑屏
}

// ✅ 正确做法 —— 先校验并请求权限
struct ARContainerView: View {
    @State private var cameraAuthorized = false

    var body: some View {
        Group {
            if cameraAuthorized {
                RealityView { content in
                    // AR 内容
                }
            } else {
                ContentUnavailableView(
                    "需要相机权限",
                    systemImage: "camera.fill",
                    description: Text("请在“设置”中开启相机权限以使用 AR 功能。")
                )
            }
        }