visionOS示例代码:Diorama

使用 Reality Composer Pro 设计您的 visionOS 应用程序的场景。

下载

  • visionOS 1.0+
  • Xcode 15.0+

概述

使用 Reality Composer Pro 来为您的 visionOS 应用程序组合、编辑和预览 RealityKit 内容。在 Reality Composer Pro 项目中,您可以创建一个或多个场景,每个场景都包含了一系列称为实体的虚拟对象层次结构,您的应用程序可以高效加载和显示这些实体。

除了帮助您组合实体层次结构外,Reality Composer Pro 还允许您添加和配置组件 ------ 甚至是您自己编写的自定义组件 ------ 到场景中的实体。视频地址

玩法

您还可以使用 Shader Graph 设计实体的视觉外观,Shader Graph 是一种基于节点的视觉工具,用于创建 RealityKit 材质。Shader Graph 让您对实体的表面细节和形状具有极大的控制权。您甚至可以创建基于应用程序状态或用户输入状态而变化的动画材质和动态材质。

Diorama 演示了许多 RealityKit 和 Reality Composer Pro 的功能。它显示了一个互动的虚拟地形图,就像您在国家公园的出入口和巡游站点找到的现实世界景观模型一样。这个虚拟地图有一些您可以点击的兴趣点,以显示更详细的信息。您还可以在两张不同的地图之间平稳切换:约塞米蒂国家公园和卡塔利娜岛。

导入用于构建场景的资源

您的 Reality Composer Pro 项目必须包含资源,您可以使用这些资源来为应用程序组合场景。Diorama 项目中包含了一些资源,包括 3D 模型,比如景观模型、地图、一些在地图上飞行的鸟类和云朵,还有一些声音和图像。Reality Composer Pro 提供了一个 3D 模型库,您可以使用它。通过点击工具栏右侧的添加(+)按钮来访问该库。从库中选择对象将其导入到您的项目中。

Diorama 使用自定义资源而不是可用的库资源。要在您自己的 Reality Composer Pro 场景中使用自定义资源,可以通过以下三种方式之一将其导入到您的项目中:通过将它们拖动到 Reality Composer Pro 的项目浏览器中,使用"文件"菜单中的"导入"选项,或者将资源复制到项目的 Swift 包内的 .rkassets 包中。

注意: 虽然您仍然可以直接在 visionOS 中加载 USDZ 文件和其他资源,但 RealityKit 将您的 Reality Composer Pro 项目中的资源编译成了一种二进制格式,其加载速度比从单独的文件中加载要快得多。

创建包含应用程序实体的场景

单个 Reality Composer Pro 项目可以有多个场景。场景是一个以 .usda 文件形式存储的实体层次结构,您可以在RealityView 中加载和显示它。您可以使用 Reality Composer 的场景来构建整个 RealityKit 场景,或者存储可重复使用的实体层次结构,您可以在运行时组合这些实体层次结构以构建场景 ------ 这就是 Diorama 使用的方法。您可以通过选择"文件">"新建">"场景",或按下⌘N键,将尽可能多的不同场景添加到您的项目中。

在 Reality Composer Pro 窗口的顶部,每个当前打开的场景都有一个单独的选项卡。要打开一个场景,请在项目浏览器中双击该场景的 .usda 文件。要编辑场景,请选择其选项卡,并使用层次结构查看器、3D 视图和检查器进行更改。

向场景添加资源

RealityKit 只能在场景中包含实体,但它不能使用 Reality Composer Pro 支持的每种类型的资源作为实体。例如,当您将一些资源(如 3D 模型)放置在场景中时,Reality Composer Pro 会自动将其转换为实体。它还间接使用其他资源。例如,它主要使用图像文件来定义模型实体的表面细节。

Diorama 使用多个场景来将资源分组,然后在运行时将这些场景组合成一个单一的沉浸式体验。例如,景观模型有自己的场景,其中包括桌子、地图表面和路径线。还有专门用于飞越桌子的鸟类以及飘过桌子上方的云的独立场景。

要向场景添加实体,请将资源从项目浏览器拖动到场景的层次结构视图或 3D 视图中。如果您拖动的资源是一种可以表示为实体的类型,则 Reality Composer Pro 会将其添加到您的场景中。您可以在场景层次结构或 3D 视图中选择任何资源,并使用窗口右侧的检查器或 3D 视图中的操纵器更改其位置、旋转和比例。

向实体添加组件

RealityKit 遵循一种称为"实体组件系统"(ECS)的设计模式。在 ECS 应用程序中,您可以使用组件在实体上存储附加数据,并且可以通过编写使用这些组件数据的系统来实现实体行为。您可以在 Reality Composer Pro 中添加和配置组件到实体中,包括已经提供的组件,如 PhysicsBodyComponent,以及您编写并放置在 Reality Composer Pro Swift 包的 Sources 文件夹中的自定义组件。您甚至可以在 Reality Composer Pro 中创建新的组件,然后在 Xcode 中对其进行编辑。有关 ECS 的更多信息,请参阅《Understanding RealityKit's modular architecture》。

Diorama 使用自定义组件来标识哪些变换是兴趣点,以标记鸟类,以便应用程序确保它们聚集在一起,并控制仅适用于两个地图之一的实体的不透明度。

要向实体添加组件,请在层次结构视图或 3D 视图中选择该实体。在检查器窗口的右下角,单击"添加组件"按钮。可用组件列表会显示出来,列表中的第一项是"新组件"。此项会创建一个新的组件类,以及一个可选的新系统类,并将组件添加到所选实体中。

如果您查看组件列表,您会看到 Diorama 用来表示哪些变换是兴趣点的 PointOfInterestComponent。如果所选实体尚未包含 PointOfInterestComponent,则选择该组件会将其添加到所选实体中。每个实体只能具有一种特定类型的组件。您可以在检查器中编辑现有组件的值,从而在应用程序中点击兴趣点时更改所显示的内容。

使用变换来标记位置

在 Reality Composer Pro 中,变换是一种空实体,用于标记空间中的一个点。变换包含位置、旋转和比例,并且其子实体会继承这些属性。但变换本身没有视觉表示,也不会自动执行任何操作。您可以使用变换来标记场景中的位置,或者组织实体的层次结构。例如,您可以将需要一起移动的多个实体制作成相同变换的子实体,这样您就可以通过移动父变换来同时移动它们。

Diorama 使用带有 PointOfInterestComponent 的变换来指示地图上的兴趣点。当应用程序运行时,这些变换标记了浮动牌坊的位置,上面有位置名称。点击牌坊时,它会展开显示更详细的信息。为了将变换变成一个交互式视图,应用程序会寻找变换上特定的组件,称为 PointOfInterestComponent。由于变换除了位置、方向和比例之外不包含任何数据,它使用此组件来保存应用程序在牌坊上显示所需的数据。如果在 Reality Composer Pro 中打开 DioramaAssembled 场景并单击名为 Cathedral_Rocks 的变换,您会在检查器中看到 PointOfInterestComponent

加载运行时场景

要加载 Reality Composer Pro 场景,请使用 load(named:in:),传递要加载的场景的名称以及项目的包。Reality Composer Pro Swift 包定义了一个常量,它提供了对其包的即用即得的访问。该常量是 Reality Composer Pro 项目的名称,后面附加了"Bundle"。在这种情况下,项目的名称为 RealityKitContent,所以该常量被称为 RealityKitContentBundle。以下是 Diorama 在 RealityView 初始化程序中加载地图表的方式:

csharp 复制代码
let entity = try await Entity.load(named: "DioramaAssembled", 
                                   in: RealityKitContent.RealityKitContentBundle)

当从异步上下文中调用时,load(named:in:)函数是异步的。由于 RealityView 初始化程序的内容闭包是异步的,它会自动使用异步版本来加载场景。请注意,在使用异步版本时,您必须使用 await 关键字来调用它。

创建浮动视图

DioramaPointOfInterestComponent 添加到变换中,以显示有关有趣位置的详细信息。每个兴趣点的名称都出现在浮动视图中,浮动视图位于地图上方的位置。当您点击浮动视图时,它会展开以显示详细信息,应用程序从 PointOfInterestComponent 中获取这些详细信息。应用程序通过为每个兴趣点创建一个 SwiftUI 视图,并使用在 ImmersiveView.swift 中声明的查询来查询具有 PointOfInterestComponent 的所有实体,从而显示这些详细信息。

rust 复制代码
static let markersQuery = EntityQuery(where: .has(PointOfInterestComponent.self))

在 RealityView 初始化程序中,Diorama 查询以检索兴趣点实体,并将它们传递给一个名为 createLearnMoreView(for:) 的函数,该函数创建视图并在点击时保存它以供显示。

less 复制代码
subscriptions.append(content.subscribe(to: ComponentEvents.DidAdd.self, componentType: PointOfInterestComponent.self, { event in
    createLearnMoreView(for: event.entity)
}))

为兴趣点创建附件

DioramaPointOfInterestComponent 中显示的信息会显示在一个名为 LearnMoreView 的视图中,该视图存储为附件。附件是 SwiftUI 视图,同时也是 RealityKit 实体,您可以将其放置在特定位置的 RealityKit 场景中。Diorama 使用附件来定位浮动在每个兴趣点上方的视图。

应用程序首先检查实体是否具有名为 PointOfInterestRuntimeComponent 的组件。如果没有,则创建一个新组件并将其添加到实体中。这个新组件包含一个只在运行时使用的值,您不需要在 Reality Composer Pro 中进行编辑。

通过将这个值放入一个单独的组件中,并在运行时将其添加到实体中,Reality Composer Pro 永远不会在检查器中显示它。PointOfInterestRuntimeComponent 存储了一个称为"附件标签"的标识符,该标识符唯一标识一个附件,以便应用程序可以在适当的时间检索和显示它。

csharp 复制代码
struct PointOfInterestRuntimeComponent: Component {
    let attachmentTag: ObjectIdentifier
}

接下来,Diorama 创建一个名为 LearnMoreView 的 SwiftUI 视图,使用来自 PointOfInterestComponent 的信息来填充视图,为视图添加标签,并将标签存储在 PointOfInterestRuntimeComponent 中。最后,它将视图存储在一个 AttachmentProvider 中,这是一个自定义类,用于维护对附件视图的引用,以便当它们不在场景中时不会被销毁。

less 复制代码
let tag: ObjectIdentifier = entity.id

let view = LearnMoreView(name: pointOfInterest.name,
                         description: pointOfInterest.description ?? "",
                         imageNames: pointOfInterest.imageNames,
                         trail: trailEntity,
                         viewModel: viewModel)
    .tag(tag)
entity.components[PointOfInterestRuntimeComponent.self] = PointOfInterestRuntimeComponent(attachmentTag: tag)

attachmentsProvider.attachments[tag] = AnyView(view)

显示兴趣点附件

将视图分配给附件提供程序实际上并不会在场景中显示该视图。RealityView 的初始化程序有一个可选的视图构建器,称为 attachments,用于指定附件。

arduino 复制代码
ForEach(attachmentsProvider.sortedTagViewPairs, id: .tag) { pair in
    pair.view
}

在初始化程序的更新闭包中,RealityKit 在视图内容更改时调用,应用程序查询具有 PointOfInterestRuntimeComponent 的实体,使用该组件中的标签检索正确的附件,然后将该附件添加到其位置上方。

css 复制代码
viewModel.rootEntity?.scene?.performQuery(Self.runtimeQuery).forEach { entity in
    guard let attachmentEntity = attachments.entity(for: component.attachmentTag) else { return }
    
    if let pointOfInterestComponent = entity.components[PointOfInterestComponent.self] {
        attachmentEntity.components.set(RegionSpecificComponent(region: pointOfInterestComponent.region))
        attachmentEntity.components.set(OpacityComponent(opacity: 0))
    }
    
    viewModel.rootEntity?.addChild(attachmentEntity)
    attachmentEntity.setPosition([0, 0.2, 0], relativeTo: entity)
}

使用 Shader Graph 创建自定义材质

为了在两个不同的地形地图之间切换,Diorama 显示一个滑块,用于在两个位置之间变形地图。为了实现这一目标,并在地图上绘制高程线,DioramaAssembled 场景中的 FlatTerrain 实体使用了一个 Shader Graph 材质。Shader Graph 是一个内置于 Reality Composer Pro 中的基于节点的材质编辑器。Shader Graph 允许您创建动态材质,您可以在运行时更改。在 Reality Composer Pro 之前,实现这种动态材质的唯一方法是创建CustomMaterial并编写 Metal 着色器以实现必要的逻辑。

DioramaDynamicTerrainMaterialEnhanced 做了两件事情。它基于位移贴图图像中存储的高度数据绘制地图上的等高线,并且还根据相同的数据偏移平坦圆盘的顶点。通过在两个不同的高度图之间插值,应用程序在两组不同的高度数据之间实现了平滑的过渡。

在构建 Shader Graph 材质时,您可以为其提供称为"提升的输入"的输入参数,这些参数可以从 Swift 代码中设置。这使您能够实现以前需要编写 Metal 着色器的逻辑。您在编辑器中构建的材质可以同时影响使用自定义表面输出节点的实体的外观(相当于在片段着色器中编写 Metal 代码),或者使用几何修改器输出来影响顶点的位置(相当于在顶点着色器中运行 Metal 代码)。

节点图可以包含子图,类似于函数。它们包含具有输入和输出的可重用节点集合。子图包含绘制等高线线条的逻辑和偏移平坦圆盘的逻辑。双击子图即可编辑。有关使用 Shader Graph 构建材质的更多信息,请参阅《Explore Materials in Reality Composer Pro》。

在运行时更新 Shader Graph 材质

要更改地图,DynamicTerrainMaterialEnhanced 具有一个被提升的输入参数,称为 Progress。如果将该参数设置为 1.0,它会显示 Catalina Island。如果设置为 0,它会显示 Yosemite。其他任何数字都表示两者之间的过渡状态。当有人操作滑块时,应用程序会根据滑块的值更新该输入参数。

重要提示:

Shader Graph 材质参数区分大小写。如果大小写错误,您的代码实际上不会更新材质。

应用程序在一个名为 handleMaterial() 的函数中设置输入参数的值,该函数由滑块的 .onChanged 闭包调用。该函数从地形实体检索 ShaderGraphMaterial,并在其上调用 setParameter(name:value:)

php 复制代码
private func handleMaterial() {
    guard let terrain = viewModel.rootEntity?.terrain,
            let terrainMaterial = terrainMaterial else { return }
    do {
        var material = terrainMaterial
        try material.setParameter(name: materialParameterName, value: .float(viewModel.sliderValue))
        
        if var component = terrain.modelComponent {
            component.materials = [material]
            terrain.components.set(component)
        }
        
        try terrain.update(shaderGraphMaterial: terrainMaterial, { m in
            try m.setParameter(name: materialParameterName, value: .float(viewModel.sliderValue))
        })
    } catch {
        print("problem: (error)")
    }
}
相关推荐
Swift社区5 个月前
SwiftUI 在 WWDC 24 之后的新变化
ios·swiftui·wwdc
东吴贾诩6 个月前
What's New In Xcode16
xcode·wwdc
微凉的衣柜6 个月前
WWDC 2024及其AI功能的引入对中国用户和开发者的影响
人工智能·ios·wwdc
CAKDJF6 个月前
第二证券今日投资参考:苹果WWDC大会开幕 地产板块再迎催化
macos·ios·wwdc
喜好儿aigc6 个月前
WWDC 2024:苹果将在 iOS 18 中对 Siri 进行人工智能升级,集合多项人工智能功能
人工智能·ios·wwdc·苹果·ios 18
wuhanwhite6 个月前
苹果WWDC 2024 带来的 AI 风暴:从生产力工具到个人助理,AI 将如何融入我们的生活?
ios·生活·wwdc
加百力6 个月前
苹果WWDC揭晓AI系统、电脑等设备系统全线更新,iPhone将接入ChatGPT
人工智能·电脑·wwdc
声网6 个月前
WWDC 苹果发布 AI 全家桶;三星宣布「实时翻译」将兼容第三方 App丨 RTE 开发者日报 Vol.222
人工智能·实时互动·wwdc
一一一一一一__16 个月前
苹果不会在WWDC 2024中推出任何搭载M4芯片的Mac电脑
macos·电脑·wwdc
喜好儿aigc7 个月前
WWDC 2024前瞻:苹果如何用AI技术重塑iOS 18和Siri
人工智能·ios·apple·wwdc·苹果