Highlight
RealityKit 在 visionOS 上新增了 Model3D、RealityView、volumetric window 和 ImmersiveSpace 等 API,让开发者可以用 SwiftUI 的熟悉语法在 2D 窗口、体积窗口和沉浸空间中放置 3D 内容,并通过 Entity-Component-System 架构自定义行为。
核心内容
三种方式集成 3D 内容
在 2D 窗口中嵌入 3D:最简单的方式。用 Model3D 视图加载 USD 文件,配合 resizable 和 scaledToFit 修饰符(03:21)。模型异步加载,可以自定义 placeholder。
Volumetric window(体积窗口):为 3D 模型提供独立的窗口,支持从任意角度查看,且保持真实尺寸。1 米宽的模型始终显示为 1 米(05:01)。
ImmersiveSpace(沉浸空间):App 可以在空间中任意位置放置 3D 元素,突破窗口边界。适合完全沉浸的体验(07:05)。
Entity-Component-System 架构
Entity 是容器对象,本身不渲染也不做物理模拟。给它添加 Component 才能赋予行为(09:36)。
常用 Component:
ModelComponent:渲染 3D 网格和材质TransformComponent:控制位置、旋转、缩放HoverEffectComponent:注视时高亮InputTargetComponent+CollisionComponent:接收手势输入SpatialAudioComponent:空间音频
System 包含定期执行的逻辑代码,作用于匹配特定条件的 Entity。ECS 架构让行为和数据分离,便于扩展。
RealityView
RealityView 是 SwiftUI 中的 3D 视图容器,提供三种 closure(12:27):
make:异步加载和初始化 Entityupdate:响应 SwiftUI 状态变化更新 Entitysubscribe:订阅 RealityKit 事件(动画完成、物理碰撞等)
坐标系约定:原点在 RealityView 中心,Y 轴向上,Z 轴朝向用户,X 轴向右,1 单位 = 1 米。RealityView 提供 convert 函数在 SwiftUI 和 RealityKit 坐标空间之间转换。
详细内容
Model3D 在 2D 窗口中显示 3D 模型
(03:40)
import SwiftUI
import RealityKit
struct GlobeModule: View {
var body: some View {
Model3D(named: "Globe") { model in
model
.resizable()
.scaledToFit()
} placeholder: {
ProgressView()
}
}
}
关键点:
Model3D加载 USD/USDZ 文件,支持异步加载resizable和scaledToFit让模型适应可用空间placeholder在加载期间显示ProgressView- 需要把 USD 文件作为资源添加到项目中
Volumetric window
(05:52)
import SwiftUI
import RealityKit
struct WorldApp: App {
var body: some SwiftUI.Scene {
WindowGroup(id: "planet-earth") {
Model3D(named: "Globe")
}
.windowStyle(.volumetric)
.defaultSize(width: 0.8, height: 0.8, depth: 0.8, in: .meters)
}
}
关键点:
.windowStyle(.volumetric)把窗口变成体积窗口.defaultSize用真实世界单位(米)定义尺寸- 体积窗口中的模型保持真实比例,可以从任意角度查看
- 通过
openWindow环境值触发打开
ImmersiveSpace
(07:31)
import SwiftUI
import RealityKit
struct WorldApp: App {
var body: some SwiftUI.Scene {
ImmersiveSpace(id: "objects-in-orbit") {
RealityView { content in
// 添加 3D 内容
}
}
}
}
关键点:
ImmersiveSpace是新的 Scene 类型- 通过
openImmersiveSpace环境值异步打开 - 内容可以放置在空间任意位置
- 有多种 immersion style(mixed、progressive、full)
RealityView 异步加载和实体定位
(12:54)
import SwiftUI
import RealityKit
struct Orbit: View {
var body: some View {
RealityView { content in
async let earth = ModelEntity(named: "Earth")
async let moon = ModelEntity(named: "Moon")
if let earth = try? await earth, let moon = try? await moon {
content.add(earth)
content.add(moon)
moon.position = [0.5, 0, 0]
}
}
}
}
关键点:
async let并发加载多个模型content.add()把 Entity 添加到场景中position用[x, y, z]数组设置位置,单位是米- RealityKit 坐标系:Y 向上,Z 朝向用户,X 向右
拖拽交互
(18:31)
import SwiftUI
import RealityKit
struct DraggableModel: View {
var earth: Entity
var body: some View {
RealityView { content in
content.add(earth)
}
.gesture(DragGesture()
.targetedToEntity(earth)
.onChanged { value in
earth.position = value.convert(value.location3D,
from: .local, to: earth.parent!)
})
}
}
关键点:
- Entity 需要同时有
InputTargetComponent和CollisionComponent才能接收手势 .targetedToEntity让手势只作用于特定 Entityvalue.location3D是手势位置,需要从 SwiftUI 坐标空间转换到 Entity 的父坐标空间- 在 Reality Composer Pro 中可以方便地添加 CollisionComponent
播放动画
(14:56)
import SwiftUI
import RealityKit
struct AnimatedModel: View {
@State var subscription: EventSubscription?
var body: some View {
RealityView { content in
if let moon = try? await Entity(named: "Moon"),
let animation = moon.availableAnimations.first {
moon.playAnimation(animation)
content.add(moon)
}
subscription = content.subscribe(to: AnimationEvents.PlaybackCompleted.self) {
// 动画完成后的处理
}
}
}
}
关键点:
availableAnimations获取模型中预置的动画playAnimation()播放动画content.subscribe()订阅事件,这里是动画播放完成事件@State保存订阅对象,防止被提前释放
轨道动画
(20:20)
let orbit = OrbitAnimation(name: "Orbit",
duration: 30,
axis: [0, 1, 0],
startTransform: moon.transform,
bindTarget: .transform,
repeatMode: .repeat)
if let animation = try? AnimationResource.generate(with: orbit) {
moon.playAnimation(animation)
}
关键点:
OrbitAnimation让 Entity 围绕父级旋转duration: 30表示 30 秒完成一圈axis: [0, 1, 0]表示围绕 Y 轴旋转startTransform从当前位置开始,避免跳变repeatMode: .repeat让动画循环播放
空间音频
(22:12)
// 创建空 Entity 作为音频源
let audioSource = Entity()
// 配置音频为定向发射,聚焦度 0.75
audioSource.spatialAudio = SpatialAudioComponent(directivity: .beam(focus: 0.75))
// 旋转音频源,让声音朝特定方向发射
audioSource.orientation = .init(angle: .pi, axis: [0, 1, 0])
// 加载循环音频并播放
if let audio = try? await AudioFileResource(named: "SatelliteLoop",
configuration: .init(shouldLoop: true)) {
satellite.addChild(audioSource)
audioSource.playAudio(audio)
}
关键点:
- 音频默认是空间化的,声音像真实存在于空间中
directivity: .beam(focus: 0.75)创建聚焦声束- 通过
orientation控制声音发射方向 shouldLoop: true让音频循环播放- 三种音频类型:Spatial(定向)、Ambient(环境)、Channel(直达扬声器)
自定义 Component 和 System
(23:47)
import RealityKit
// Component 是附加在 Entity 上的数据
struct TraceComponent: Component {
var mesh = TraceMesh()
}
// 从 Entity 上读取或写入 Component
func updateTrace(for entity: Entity) {
var component = entity.components[TraceComponent.self] ?? TraceComponent()
component.update()
entity.components[TraceComponent.self] = component
}
// Codable Component 可以在 Reality Composer Pro 中使用
struct PointOfInterestComponent: Component, Codable {
var name = ""
}
关键点:
- Component 遵循
Component协议 - 通过
entity.components[ComponentType.self]读写 - 遵循
Codable后可在 Reality Composer Pro 中编辑
(24:51)
import SwiftUI
import RealityKit
// System 包含逻辑代码
struct TraceSystem: System {
static let query = EntityQuery(where: .has(TraceComponent.self))
init(scene: Scene) {
// 初始化
}
func update(context: SceneUpdateContext) {
for entity in context.entities(Self.query, updatingSystemWhen: .rendering) {
addCurrentPositionToTrace(entity)
}
}
}
// 注册 System
struct MyApp: App {
init() {
TraceSystem.registerSystem()
}
}
关键点:
- System 遵循
System协议 EntityQuery定义 System 关心的 EntityupdatingSystemWhen: .rendering按渲染帧率更新- 在 App 的
init()中调用registerSystem()注册 - System 自动作用于 App 中所有 RealityKit 内容
核心启发
-
做什么:做一个 3D 太阳系教育应用,让学生可以拖拽行星、观察轨道运动。
-
为什么值得做:RealityKit 的
OrbitAnimation和拖拽手势让抽象的天文概念变得可触摸。 -
怎么开始:用
RealityView加载行星模型,添加DragGesture实现拖拽,OrbitAnimation实现公转动画。 -
做什么:开发一个产品展示应用,让客户可以从各个角度查看商品 3D 模型。
-
为什么值得做:Volumetric window 保持真实尺寸,客户可以绕着商品走一圈查看细节。
-
怎么开始:用
WindowGroup+.windowStyle(.volumetric)创建体积窗口,内部用Model3D加载 USDZ 模型。 -
做什么:做一个沉浸式音乐可视化应用,音符在空间中漂浮并发出空间音频。
-
为什么值得做:
SpatialAudioComponent让声音有方向感,配合粒子效果营造沉浸氛围。 -
怎么开始:创建
ImmersiveSpace,用Entity+SpatialAudioComponent放置音频源,配合自定义 System 更新粒子位置。 -
做什么:开发一个室内设计预览工具,让用户在真实房间里摆放虚拟家具。
-
为什么值得做:RealityKit 的锚点功能可以把 3D 模型固定在墙面或地板上,空间音频可以模拟房间声学效果。
-
怎么开始:用
AnchorEntity把家具模型锚定到平面,用CollisionComponent实现拖拽调整位置。
关联 Session
- Enhance your spatial computing app with RealityKit — RealityKit 进阶:portal、粒子与锚点
- Meet Reality Composer Pro — Reality Composer Pro 工具入门
- Explore materials in Reality Composer Pro — Reality Composer Pro 材质编辑
- Work with Reality Composer Pro content in Xcode — 在 Xcode 中使用 Reality Composer Pro 内容
评论
GitHub Issues · utterances