大多数浏览器和
Developer App 均支持流媒体播放。
-
使用 RealityKit 增强你的空间计算 App
摆脱窗口限制,了解如何使用 RealityKit 为你的 App 带来引人入胜的沉浸式 3D 内容。了解 SwiftUI 场景如何与 RealityView 协同工作,以及如何将内容嵌入到实体层次结构中。我们还将探索如何使用锚点融合虚拟内容和现实世界,在 App 中引入粒子效果,添加视频内容以及使用传送门创造更多沉浸式体验。
章节
- 0:00 - Introduction
- 2:05 - RealityView attachments
- 6:11 - Video playback
- 10:59 - Portals
- 15:04 - Particle emitters
- 17:06 - Anchors
- 19:28 - Wrap-up
资源
相关视频
WWDC23
-
下载
♪ 悦耳的器乐嘻哈 ♪ ♪ 大家好 我是 Yujin RealityKit 团队的一名工程师 今天 我将向你介绍 RealityKit 中的新功能 并且你可以使用这些功能 来增强空间计算 App 自 2019 年 RealityKit 发布以来 很多 App 便借助其中丰富的功能集 创造了令人惊叹的体验 如今 空间计算为 RealityKit 添加了更多功能 例如传送门、粒子发射器 和 RealityView 附件 在讲座 “使用 RealityKit 打造空间体验”中 我们了解了 RealityKit 的基本构建块: 作为包装器对象的实体、 用于定义实体特定行为的组件、 以及对实体和组件进行操作 来添加功能的系统 我们还介绍了 连接 SwiftUI 和 RealityKit 的桥梁: RealityView API 并且 我们还向你展示了 如何在 RealityKit 场景中 添加交互、动画以及空间音频 如果你还未观看过此讲座 我强烈建议你去看一看 在本次讲座中 我们会向你介绍 RealityKit 中的新功能 这些功能可以让你的 App 更具吸引力 并为用户提供更加沉浸的体验 首先 我们会了解 如何使用 RealityView 中的附件 将 SwiftUI 视图 嵌入到 RealityKit 的内容中 接着 我们会了解 如何在 RealityKit 场景中 添加视频回放 然后 我们会了解如何使用传送门 打开通往另一个世界的窗户 在这里 我们还会介绍 如何使用粒子发射器 API 来增强场景的视觉效果 最后 我们会了解 如何使用 RealityKit 中的锚点 将 3D 内容附加到 现实世界中的位置 例如墙 我们首先 从 RealityView 附件开始 附件是将 SwiftUI 内容 嵌入到 RealityKit 场景中的 有效方式 在本示例 App 中 我使用附件 将文本标题放在了 地球和月球模型的下方 我还附加了一个视图 解释了月球如何影响 海洋潮汐 让我们看看 如何在代码中实现以上内容 在该 App 中 我使用了 RealityView 来渲染地球模型 RealityView 是一种视图 可用于添加 RealityKit 实体 实体需要添加到 RealityView 中 才能实现渲染、动画和模拟 在这里 我们仅加载了地球实体 并将其添加到 RealityView 内容中 现在 我们将 RealityView 改为使用附件 附件是一种视图 可以放置在与 RealityKit 内容 相关的特定位置 设置附件包括两个部分 第一 在 RealityView 中的 make 闭包中添加参数 第二 在 RealityView 中 添加 attachments 视图构建器 首先 我们来介绍一下 attachments 视图构建器 在这里 你可以提供 想要添加在 RealityKit 内容中的 SwiftUI 视图 在本示例中 我添加了一个文本视图 来标记地球 我们还会在该视图中 添加一个标记修饰符 以便稍后在将该视图作为实体 传递给 make 闭包时 可以对其进行识别 该标签可以是任何可哈希的值 这里 我使用了 字符串 earth_label 在 RealityView 的 make 闭包中 attachments 参数所包含的视图 现在表示实体 为了以实体的形式获取视图 我们会在 attachments 上 调用 entity(for:) 并传入此前在视图构建器中 提供的同一个 tag 即 earth_label 这样 我们就获取了视图附件实体 并可以将其像其他实体一样 添加到 RealityKit 内容中 为了让标签位于地球下方 我们将附件作为 地球实体的子级进行添加 并将其放置于较低的位置 现在 我们就可以使用不同的标签 对其他我们想要添加的附件 重复该过程 我们在 Xcode 中 具体看一下该操作过程 在示例 App 中 我将在 RealityView 中添加 3 个附件 首先 我会在地球下方 添加一个标签 然后 在月球下方也添加一个 最后 我会添加一小段 解释月球轨道如何影响潮汐的文字 我使用了 SwiftUI 中的 glassBackgroundEffect 来进行样式设置 在 RealityView 中的 make 闭包中 我会在内容中添加相应的实体 首先 我会在地球下方添加 earthAttachment 然后 在月球下方也添加一个 最后 我会将潮汐解释放置在 包装器实体的左侧 我会构建并运行 App 这样 我们便看到创建的附件 显示在模型的旁边 我们来概括一下附件的数据流程 附件从 RealityView 中的 附件视图构建器开始 我们可以在这里提供 想要添加到 RealityKit 视图中的 SwiftUI 视图 在 RealityView 中的 make 闭包中 我们可以获取成为实体的附件 并将其添加到场景中 我们还可以在 update 闭包中 更新实体 该闭包会在 SwiftUI 视图状态 发生更改时被调用 你可以使用该闭包 响应 RealityView 中的 动态变化内容 想要进一步了解附件的使用方法 请查看讲座 “在 Xcode 中 处理 Reality Composer Pro 内容” RealityView 附件是 将其他 UI 元素中的 文本内容添加到场景中的有效方式 此外 我们还可以 在 App 中添加视频 来使其更具吸引力 为此 我们可以使用 VideoPlayerComponent VideoPlayerComponent 是 RealityKit 中新增的组件类型 用于将视频内容 嵌入到 3D 场景中 需要提醒你的是 组件定义了可以附加到 实体上的特定行为 为了使用 VideoPlayerComponent 来播放视频 我们首先会从资源包中 加载视频文件 接着 使用该文件 创建一个 AVPlayer 实例 通过该实例 我们就可以创建一个 VideoPlayerComponent 在你将 VideoPlayerComponent 附加到实体后 其就会自动生成 一个与视频长宽比匹配的 矩形网格 该行为类似于现有的 视频播放器 API 例如 SwiftUI 中的 VideoPlayer 以及 Core Animation 中的 AVPlayerLayer 然而 由于 RealityKit 是一个 3D 框架 所以 你的视频会以 具有网格的实体形式进行表示 以便你可以在 3D 空间中 对其进行移动和定位 AV Foundation 支持的 所有视频格式 都适用于 VideoPlayerComponent 包括 2D 视频格式 和使用 MV-HEVC 的 3D 视频 最后 VideoPlayerComponent 会自动显示 通过 AVPlayer 提供的字幕 想要进一步了解有关如何创建 3D 视频等视频内容的信息 请查看讲座 “为空间体验提供视频内容” 为了在 RealityKit 场景中 添加视频 首先 我们使用视频资源的 URL 创建一个 AVPlayerItem 接着 创建一个 AVPlayer 我们用刚刚创建的 AVPlayer 初始化 VideoPlayerComponent 并将其添加到实体上 VideoPlayerComponent 就会根据视频的长宽比 自动生成一个大小与之匹配的网格 由于 RealityKit 使用真实世界的单位 所以默认情况下 视频高度为一米 如果你需要不同尺寸的视频 缩放实体即可 在本示例中 我希望视频高度为 40 厘米 所以 我们将 entity.scale 乘以 0.4 最后 我们就可以播放视频了 我们将 CurrentItem 设置为 AVPlayerItem 并在 AVPlayer 上调用 play 让我们用这段代码 重新构建并运行 App 我在 App 中添加了 一个了解更多按钮 用来在场景中添加视频实体 点击按钮 我将借助不透明组件 和 fromToByAnimation 将视频淡入其中 在视频内容部分 我准备了一个 解释月球引力如何引起 地球涨潮的短片 我们来看一看 月球围绕着行星运行 其引力对海洋产生了强大的作用力 导致海洋向月球 微微隆起 VideoPlayerComponent 遵循系统范围内的 字幕偏好设置 我们在设置 App 的 辅助功能部分下开启该选项 在地球和月球 不断地相互作用下 潮涨潮落 就这样 一天两次 永不停息 VideoPlayerComponent 还支持穿透着色 启用此功能后 穿透内容 就会根据视频中的 匹配颜色进行调整 这就和在该平台的 TV App 上 观看电影和电视节目时 所使用的处理方式相同 如果需要使用穿透着色 将 isPassthroughTintingEnabled 属性设置为 true 即可 你还可以订阅 VideoPlayerEvents 以便当 VideoPlayerComponent 上的 内容类型、观看模式 以及视频大小等属性 发生更改时收到通知 如果需要订阅事件 你可以调用 RealityView 内容中的 subscribe 函数 并指定事件类型和实体 你可以在 event handler 闭包中 对事件作出响应 VideoPlayerComponent 是 3D 场景一个很好的补充 至此 我们的 App 已经有了 地球和月球的模型 但我还想将其呈现在 外太空背景下 要是我们可以在房间中 创建一个魔法窗口 来显示月球在外太空的轨道 那就真的太棒了 为了实现这个效果 我们可以使用传送门来渲染场景 传送门创造了一个 通往不同世界的入口 透过网格表面 我们可以看到另一个世界 该世界的实体使用单独的光照 并受到传送门几何形状的遮罩 该示例展示了 RealityKit 中的 三个独特功能 第一 使用传送门 可以渲染外太空的场景 第二 使用粒子效果 可以装饰传送门的边缘 第三 使用锚点可以将传送门 放置在房间的墙上 我们首先从传送门开始 为了创建传送门 我们首先要创建一个世界 为此 我们在包含世界组件的场景中 添加一个实体 该组件将实体树 标记为属于不同的世界 并且 世界中的实体 只能通过传送门表面看到 为了向世界中添加内容 我们可以将实体作为 世界实体的子集进行附加 在这里 我们会添加天空、 地球和月球的模型 以及用于定义世界内部照明的 ImageBasedLight 世界实体的所有子级 只能出现在世界内部 接下来 我们将创建传送门 为此 我们需要添加 包含世界组件的实体 模型组件包含两个属性: 网格和材料 对于网格 我们会生成一个圆形平面 来作为传送门的表面 对于材质 我们会分配一个新的传送门材质 来使网格看起来像传送门 为了将传送门与世界相连 我们会在实体中添加一个传送门组件 并将其 target 属性 设置为世界实体 这样 传送门在显示世界内部内容时 就会起到遮罩的作用 让我们看看代码 是如何实现这种效果的 在 RealityView 中 我添加了两个函数调用 用于实现 makeWorld 和 makePortal 在 makeWorld 函数中 我们将创建世界实体 并使用传送门内容对其进行填充 在 makePortal 函数中 我们将创建传送门实体 并将其与刚刚创建的世界进行链接 最后 我们将这两个实体 添加到 RealityView 内容中 接下来 我们来深入了解每个函数 在 makeWorld 函数中 我们创建了一个实体 并附加了 WorldComponent 接着 我们加载 EnvironmentResource 并将其作为 ImageBasedLight 然后 我们会使用 ImageBasedLightComponent 和 ImageBasedLightReceiverComponent 将 EnvironmentResource 应用到世界中 想要进一步了解 RealityKit 中基于图像的照明 请查看讲座 “探索如何渲染空间计算” 接下来 我们将使用内容填充世界 我会加载地球、月球和天空的模型 并将其作为子级添加到世界中 由于这些实体都是世界的子级 因此它们只能透过传送门看到 让我们继续回到 makePortal 函数 为了创建传送门 我们首先需要一个网格 通过为实体创建模型组件 我们便可以完成创建 为了让传送门显示为圆形 我们会生成一个尺寸相同 且圆角半径为尺寸一半大小的平面 我还会创建一个 PortalMaterial 来作为 ModelComponent 的材质 最后 我们还会附加一个 使用此前创建的世界实体 进行初始化的传送门组件 这样 传送门就会与世界链接起来 从而我们便可以透过网格 看到世界的内容 接下来 我们使用粒子效果 来装饰传送门的边缘 为此 我们可以使用 RealityKit 中的 ParticleEmitterComponent 粒子发射器可用于 在 RealityKit 中 表示多种视觉效果 例如火花、雪花和撞击效果 粒子发射器可以通过 Reality Composer Pro 或在运行时通过 ParticleEmitterComponent 使用 RealityKit 进行创建 在这里 我使用 Reality Composer Pro 事先准备好了粒子资源 接着 我们就可以使用该资源 来装饰此前创建的传送门 我们将其加载到场景中 并在运行时使用 RealityKit 修改粒子属性 为了让粒子可随时间得到更新 我创建了一个名为 ParticleTransitionSystem 的自定义系统 在这里 我们会使用 EntityQuery 来查找 具有 ParticleEmitterComponent 的实体 在系统更新内部 我们会执行查询 并对输出实体进行迭代 对于每个实体 我们都会调用 updateParticles 函数 然后在下一部分实现该函数 想要进一步了解有关 RealityKit 中自定义系统的信息 请查看讲座 “使用 RealityKit 构建空间体验” 在 updateParticles 函数中 我们首先从实体中获取 ParticleEmitterComponent ParticleEmitterComponent 包含许多 控制粒子外观和行为等 不同方面的属性 在这里 我们会根据实体的缩放 设置 lifeSpan 和 vortexStrength 属性 从而 粒子可以随着实体的增大 加快围绕传送门旋转的速度 最后 我们将通过 把组件重新分配给实体来应用更改 这样 我们就设置好了 想要进一步了解粒子发射器中的 所有不同属性 请查看讲座 “认识 Reality Composer Pro” 我们就快要完成 App 了 最后 我们将传送门 附加到房间的墙上 为此 我们可以使用 RealityKit 中的锚点 锚点可用于将内容放置于墙、地板 或其他与头部和手部相关的位置上 RealityKit 中的锚点 支持两种跟踪模式: .continuous 和 .once 在使用 .continuous 跟踪模式时 锚实体会随着时间与锚点一起移动 例如在你移动头部的时候 在使用 .once 跟踪模式时 锚实体一经定位 便不会再移动 如果需要监听实体何时被锚定 你可以订阅 RealityKit 中的 AnchoredStateChanged 事件 请注意 尽管你可以使用父实体的锚点 来放置 3D 内容 但是为了保护用户隐私 锚点本身的显式转换 对 App 是不可见的 如果需要访问锚点转换 你需要使用 ARKit 想要进一步了解有关信息 请查看讲座 “认识用于空间计算的 ARKit” 为了在 App 中使用锚点 我们首先需要修改 App 以使用沉浸式空间 沉浸式空间是一种 特殊类型的包装器 它可以让 App 渲染窗口之外的内容 为此 我们可以在 SwiftUI 场景中 添加 ImmersiveSpace 我们还会添加 .immersionStyle 修饰符 并将其设置为 mixed 在 ImmersiveSpace 中 我们可以使用 RealityView 来放置即将锚定的内容 想要进一步了解有关 沉浸式空间的信息 请查看讲座 “使用 SwiftUI 摆脱窗口的限制” 在 RealityView 中 我们可以将锚实体 作为传送门的包装器 为了初始化锚实体 我们需要使用想要锚定内容的 表面指定类型 在本例中 我们想要 最小尺寸为 1 米 x 1 米的垂直墙面 如果锚点与指定类型匹配 RealityKit 就会自动 将内容附加在墙上 至此 我们就完成了全部内容 在运行 App 时 我们会得到 一个附加在墙上的传送门 从传送门和粒子到锚点和附件 RealityKit 为你提供了 大量用于打造沉浸式体验的功能 我们来总结一下本次讲座中 所介绍的全部内容 RealityView 中的附件可让你 在实体层次结构中 嵌入 SwiftUI 内容 以便你可以同时放置 UI 元素和 3D 元素 VideoPlayerComponent、 传送门以及粒子效果 可让你添加动态元素 来增强 RealityKit 中的场景 最后 锚点可让你将 3D 元素 附加到现实世界表面 例如地板和墙 “使用 RealityKit 构建空间体验”讲座 介绍了实体、组件 和 RealityView 等 关键概念 “在 Xcode 中使用 Reality Composer Pro 内容”讲座 为你演示了结合使用 Reality Composer Pro 和 RealityKit 构建沉浸式 App 的过程 我非常期待看到你使用 RealityKit 中的新功能 创造出的所有精彩作品 感谢你的观看 ♪
-
-
2:30 - Attachments
import SwiftUI import RealityKit struct MoonOrbit: View { var body: some View { RealityView { content, attachments in guard let earth = try? await Entity(named: "Earth") else { return } content.add(earth) if let earthAttachment = attachments.entity(for: "earth_label") { earthAttachment.position = [0, -0.15, 0] earth.addChild(earthAttachment) } } attachments: { Attachment(id: "earth_label") { Text("Earth") } } } }
-
8:03 - VideoPlayerComponent
public func makeVideoEntity() -> Entity { let entity = Entity() let asset = AVURLAsset(url: Bundle.main.url(forResource: "tides_video", withExtension: "mp4")!) let playerItem = AVPlayerItem(asset: asset) let player = AVPlayer() entity.components[VideoPlayerComponent.self] = .init(avPlayer: player) entity.scale *= 0.4 player.replaceCurrentItem(with: playerItem) player.play() return entity }
-
10:05 - Passthrough tinting
var videoPlayerComponent = VideoPlayerComponent(avPlayer: player) videoPlayerComponent.isPassthroughTintingEnabled = true entity.components[VideoPlayerComponent.self] = videoPlayerComponent
-
10:40 - VideoPlayerEvents
content.subscribe(to: VideoPlayerEvents.VideoSizeDidChange.self, on: entity) { event in // ... }
-
13:12 - Portal
struct PortalView : View { var body: some View { RealityView { content in let world = makeWorld() let portal = makePortal(world: world) content.add(world) content.add(portal) } } } public func makeWorld() -> Entity { let world = Entity() world.components[WorldComponent.self] = .init() let environment = try! EnvironmentResource.load(named: "SolarSystem") world.components[ImageBasedLightComponent.self] = .init(source: .single(environment), intensityExponent: 6) world.components[ImageBasedLightReceiverComponent.self] = .init(imageBasedLight: world) let earth = try! Entity.load(named: "Earth") let moon = try! Entity.load(named: "Moon") let sky = try! Entity.load(named: "OuterSpace") world.addChild(earth) world.addChild(moon) world.addChild(sky) return world } public func makePortal(world: Entity) -> Entity { let portal = Entity() portal.components[ModelComponent.self] = .init(mesh: .generatePlane(width: 1, height: 1, cornerRadius: 0.5), materials: [PortalMaterial()]) portal.components[PortalComponent.self] = .init(target: world) return portal }
-
15:50 - Adding particles around the portal
public class ParticleTransitionSystem: System { private static let query = EntityQuery(where: .has(ParticleEmitterComponent.self)) public func update(context: SceneUpdateContext) { let entities = context.scene.performQuery(Self.query) for entity in entities { updateParticles(entity: entity) } } } public func updateParticles(entity: Entity) { guard var particle = entity.components[ParticleEmitterComponent.self] else { return } let scale = max(entity.scale(relativeTo: nil).x, 0.3) let vortexStrength: Float = 2.0 let lifeSpan: Float = 1.0 particle.mainEmitter.vortexStrength = scale * vortexStrength particle.mainEmitter.lifeSpan = Double(scale * lifeSpan) entity.components[ParticleEmitterComponent.self] = particle }
-
18:19 - Anchoring the portal
import SwiftUI import RealityKit struct PortalApp: App { @State private var immersionStyle: ImmersionStyle = .mixed var body: some SwiftUI.Scene { ImmersiveSpace { RealityView { content in let anchor = AnchorEntity(.plane(.vertical, classification: .wall, minimumBounds: [1, 1])) content.add(anchor) anchor.addChild(makePortal()) } } .immersionStyle(selection: $immersionStyle, in: .mixed) } }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。