运用 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 模型库,您可以运用它。经过点击东西栏右侧的增加(+)按钮来拜访该库。从库中挑选方针将其导入到您的项目中。

visionOS示例代码:Diorama

Diorama 运用自界说资源而不是可用的库资源。要在您自己的 Reality Composer Pro 场景中运用自界说资源,可以经过以下三种办法之一将其导入到您的项目中:经过将它们拖动到 Reality Composer Pro 的项目浏览器中,运用“文件”菜单中的“导入”选项,或许将资源复制到项目的 Swift 包内的 .rkassets 包中。

visionOS示例代码:Diorama

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

创立包括应用程序实体的场景

单个 Reality Composer Pro 项目可以有多个场景。场景是一个以 .usda 文件形式存储的实体层次结构,您可以在RealityView 中加载和显现它。您可以运用 Reality Composer 的场景来构建整个 RealityKit 场景,或许存储可重复运用的实体层次结构,您可以在运转时组合这些实体层次结构以构建场景 —— 这就是 Diorama 运用的办法。您可以经过挑选“文件”>“新建”>“场景”,或按下⌘N键,将尽或许多的不同场景增加到您的项目中。

visionOS示例代码:Diorama

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

向场景增加资源

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

visionOS示例代码:Diorama

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 视图中挑选该实体。在查看器窗口的右下角,单击“增加组件”按钮。可用组件列表会显现出来,列表中的第一项是“新组件”。此项会创立一个新的组件类,以及一个可选的新系统类,并将组件增加到所选实体中。

visionOS示例代码:Diorama

假如您查看组件列表,您会看到 Diorama 用来表明哪些改换是爱好点的 PointOfInterestComponent。假如所选实体尚未包括 PointOfInterestComponent,则挑选该组件会将其增加到所选实体中。每个实体只能具有一种特定类型的组件。您可以在查看器中修改现有组件的值,从而在应用程序中点击爱好点时更改所显现的内容。

运用改换来符号方位

在 Reality Composer Pro 中,改换是一种空实体,用于符号空间中的一个点。改换包括方位、旋转和份额,并且其子实体会继承这些属性。但改换自身没有视觉表明,也不会主动执行任何操作。您可以运用改换来符号场景中的方位,或许组织实体的层次结构。例如,您可以将需求一起移动的多个实体制作成相同改换的子实体,这样您就可以经过移动父改换来一起移动它们。

visionOS示例代码:Diorama

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 初始化程序中加载地图表的办法:

let entity = try await Entity.load(named: "DioramaAssembled",
                                   in: RealityKitContent.RealityKitContentBundle)

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

创立起浮视图

DioramaPointOfInterestComponent 增加到改换中,以显现有关风趣方位的具体信息。每个爱好点的名称都出现在起浮视图中,起浮视图坐落地图上方的方位。当您点击起浮视图时,它会打开以显现具体信息,应用程序从 PointOfInterestComponent 中获取这些具体信息。应用程序经过为每个爱好点创立一个 SwiftUI 视图,并运用在 ImmersiveView.swift 中声明的查询来查询具有 PointOfInterestComponent 的所有实体,从而显现这些具体信息。

static let markersQuery = EntityQuery(where: .has(PointOfInterestComponent.self))

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

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 存储了一个称为“附件标签”的标识符,该标识符仅有标识一个附件,以便应用程序可以在恰当的时间检索和显现它。

struct PointOfInterestRuntimeComponent: Component {
    let attachmentTag: ObjectIdentifier
}

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

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,用于指定附件。

ForEach(attachmentsProvider.sortedTagViewPairs, id: .tag) { pair in
    pair.view
}

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

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 代码)。

visionOS示例代码:Diorama

节点图可以包括子图,类似于函数。它们包括具有输入和输出的可重用节点集合。子图包括制作等高线线条的逻辑和偏移平整圆盘的逻辑。双击子图即可修改。有关运用 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:)

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)")
    }
}