大多数浏览器和
Developer App 均支持流媒体播放。
-
使用 RealityKit 构建空间绘画 App
充分利用 RealityKit 的强大功能来构建空间绘画 App。你将打造 RealityKit 与 ARKit 和 SwiftUI 充分整合的炫目空间体验,同时探索资源在 RealityKit 中的运作方式,以及如何使用低级别网格和纹理 API 等功能根据用户的画笔笔画做出快速更新。
章节
- 0:00 - Introduction
- 2:43 - Set up spatial tracking
- 5:42 - Build a spatial user interface
- 13:57 - Generate brush geometry
- 26:11 - Create a splash screen
资源
相关视频
WWDC24
- 利用 RealityKit 音频让空间计算 App 更加引人入胜
- 在 Reality Composer Pro 中编写交互式 3D 内容
- 在 visionOS 中打造自定悬停效果
- 探索适用于 iOS、macOS 和 visionOS 的 RealityKit API
WWDC23
-
下载
大家好 我叫 Adrian 是 RealityKit 团队的一名工程师 在本次讲座中 我将带领大家为 visionOS 构建空间绘画 App 探索如何在这一过程中 充分利用 RealityKit 的全新功能
RealityKit 这个框架 可将高性能 3D 模拟和渲染功能 带到 iOS、macOS 和 visionOS 在 visionOS 上 RealityKit 为 App 中的 各种空间功能奠定了基础
自 Apple Vision Pro 发布以来 我们从像你这样出色的开发者 那里收到了大量的反馈 我们一直在努力 根据大家的反馈 提升这个平台的功能
今天我很自豪地宣布一些新的 API 它们突破了 使用 RealityKit 进行创作的极限 让我们来一探究竟
我们的空间绘画 App 将 RealityKit 强大的 3D 功能 与 SwiftUI 和 ARKit 充分整合 从而为用户提供出色的体验 我们将体验 构建自定网格、纹理 和着色器的乐趣 打造精美的视觉设计
当你启动 App 时 映入眼帘的是一个抢眼的启动屏幕
完成快速设置后 你就可以开始创作了
只需在空中捏合双指 就能立即开始绘画
你可以使用 调色盘视图 更改画笔笔画的外观
这款 App 支持类似于管状的 实心画笔类型
以及绚丽的闪光画笔
画笔笔画还可自定 例如 你可以更改笔画的颜色 或粗细
要介绍的内容很多 我很高兴 能一起开发这款 App 让我们开始吧! 首先我们要设置空间追踪 以便 App 能理解 你的手部和环境数据
接下来 我们会构建一个 UI 这样你就可以 控制画笔和画布 我们还将使用强大的新功能 来自定悬停时的 UI
我们将深入了解 网格在 RealityKit 中的工作原理 还会讨论我们的 App 如何使用 新的 RealityKit API 利用 Metal 高效生成 画笔几何模型
然后 我们会创建一个具有 动态纹理和空间 UI 元素 同时又引人入胜的启动屏幕 为这款 App 画上 完美的句号
我们的绘画 App 需要 理解你的手部位姿 因为你会捏合手指并移动手 进行书写 为此 我们需要设置 手部锚点的空间追踪
在 visionOS 中 App 可以将 SwiftUI 或 RealityKit 内容 放置在窗口、空间容器和空间中
如果你的 App 采用沉浸式空间 它可以通过锚点 接收空间追踪信息
这包括来自现实场景锚点和 平面锚点的 场景理解信息 以及来自手部锚点的 位姿信息
在 visionOS 1.0 中 可以使用 ARKit 访问这类数据
在 visionOS 2.0 中 我们推出了更简单的方式 以便在你的 RealityKit App 中 使用空间追踪 下面我们使用这个 API 在绘画 App 中设置空间手部追踪
在 RealityKit 中 你可以使用 AnchorEntity 将 RealityKit 实体 附加到 AR 锚点
我们的绘画 App 为每只手创建了 两个 AnchorEntity 一个锚定在拇指指尖 另一个锚定在 食指指尖
为了访问空间追踪数据 我们的 App 需要用户的许可 在绘画 App 中 当用户轻点“Start”时 App 会请求相关授权
一定要慎重考虑 何时向用户请求许可
只有当你的 App 需要时 你才应该请求授权 在本例中 就是当用户即将开始绘图时
要在 RealityKit 中 请求追踪数据的授权 请使用 SpatialTrackingSession 这是 visionOS 2.0 中新增的 API
声明 App 所需的 追踪功能 在本例中 App 需要手部数据
接下来 为 SpatialTrackingSession 调用 run 这时 App 会显示一条提醒 以便征得用户对这类追踪的授权
run 函数会返回一个 未批准追踪功能的列表 你可以查看这个列表 了解是否已获得许可
如果空间追踪功能已获批准 则可以通过 AnchorEntity 转换 访问追踪数据
如果用户拒绝提供许可 AnchorEntity 转换 将不会更新 但是 AnchorEntity 仍会直观地更新位姿
现在我们总结一下 如果你的 App 采用 ImmersiveSpace 它可以将 RealityKit 内容 锚定到现实场景
你可以使用 AnchorEntity 来为你的 RealityKit 内容 设置锚定
从今年开始 如果你的 App 需要访问 AnchorEntity 变换 你可以使用 SpatialTrackingSession
此外 如果使用 SpatialTrackingSession 你的 AnchorEntity 还能 与 RealityKit 的物理系统进行交互
接下来我们来谈谈 App 的用户界面
在启动屏幕上轻点“Start”后 你将进入一个包含绘图画布的 沉浸式空间
你可以通过拖动球形控制柄 来改变画布的 大小或位置
准备开始绘图时 调色盘视图就会出现
你可以在这里配置画笔的 形状和颜色
准备好绘图时 只需进入画布 即可开始
让我们深入探讨一下 如何构建这个界面
我们将从画布 放置界面开始 通过这个界面 你可以 定义绘图区域
在画布放置过程中 有两个元素 构成了沉浸式空间 在地面上 有一个 3D 形状 勾勒出画布的边缘 可以使用控制柄 来更改画布位置
首先我们来看看边界网格 这个网格是实时生成的 因为边界的大小 可以通过拖动滑块来修改
网格由两个圆圈定义 如左图所示 外圆为绿色 内圆为红色
我们可以将这种形状 定义为 SwiftUI 路径
圆圈是 360 度的弧形 因此我们创建了两条这样的弧线 每条弧线的半径各不相同
然后通过指定 归一化的奇偶填充模式 我们就定义了 想要创建的形状
要在 RealityKit 中生成网格 我们可以利用今年 新推出的 API MeshResource extruding MeshResource extruding 是一个强大的 API 它可以帮助你将 2D 矢量内容 转换为 3D 模型
我们只需指定形状 所需的深度 和分辨率 就大功告成了
有一个重要的 考虑因素 在 visionOS 中 RealityKit 使用的是注视点渲染器 在你的周边视野中 图像区域会 以较低的分辨率渲染 这有助于优化 App 的性能 如果你的场景包含 对比度较高的细几何模型 你可能会注意到 闪烁的构件 在本示例中 圆环太薄 因此请务必避免使用 如图所示的细几何元素
尤其是在对比度较高的区域
要解决这个问题 我们可以增加 几何模型的厚度 并移除高对比度的细边缘 请注意 左侧的闪烁构件 已得到缓解
要进一步了解 空间内容混叠 我向你推荐 WWDC23 的讲座 “探索空间计算的渲染”
接下来我们来谈谈画布控制柄 我想强调一个细节 当你注视这个控制柄时 你会发现 有一个蓝色高亮效果
在 visionOS 中 当你注视 RealityKit 内容时 HoverEffectComponent 会添加视觉效果
在 visionOS 1.0 中 HoverEffectComponent 使用默认的聚焦效果
今年我们又为 HoverEffectComponent 引入了 两种悬停效果 高亮效果会为实体 应用统一的高亮颜色
现在可以将 HoverEffectComponent 与 ShaderGraph 着色器结合使用 着色器支持的悬停效果 非常灵活 因此你可以精确控制 实体在悬停时的外观
控制柄上的蓝色高亮显示 就是 得益于高亮显示悬停效果 要使用高亮效果 请使用点高亮 初始化你的 HoverEffectComponent 并提供高亮颜色
你也可以通过更改强度值 来提升高亮的鲜艳度
你可能已经注意到 用于放置画布的 UI 元素 似乎在 背后的环境之上发光 这是因为它们使用了 叠加混合模式 今年 RealityKit 为内置材质 如 UnlitMaterial 和 PhysicallyBasedMaterial 新增了对叠加混合模式的支持 要使用这一功能 首先要创建一个 Program 并将 blendMode 设置为 add
一旦用户选择了 自己的绘图画布 接下来就是重头戏了 调色盘视图会出现 用户就可以开始 设置自己的画笔
调色盘视图内置在 SwiftUI 中 并允许你自定 画笔类型 和样式 在调色盘底部 有一组预设画笔 可供选择
我想特别介绍一下 画笔预设视图 请注意 每个画笔预设缩略图 实际上都是一个完整的 三维形状
这种网格的生成方式 与实际画笔笔画相同 SwiftUI 和 RealityKit 无缝整合 在这里 我们为每个缩略图 使用了一个 RealityView 这样就能充分利用 RealityKit 的全部功能
当你注视画笔预设时 一个醒目的悬停效果就会激活 沿着画笔扫过紫色的光晕
这就是我前面提到的 基于着色器的悬停效果
让我们深入了解一下 这种效果是如何实现的
基于着色器的悬停效果通过 Shader Graph 中的 HoverState 节点启用 这个节点提供了 将悬停效果整合到 着色器中的有用工具
例如 Intensity 是系统提供的值 它根据注视状态 在 0 和 1 之间变化 你可以使用 Intensity 值 来重新打造高亮效果 就像之前看到的画布控制柄一样
不过对于预设视图 我们希望实现 更高级的效果
光晕效果 应该沿着画笔网格 从画笔笔画的起点 一直扫到终点
为了实现这种复杂的效果 App 使用了 Shader Graph 材质 让我们一起来了解一下 Shader Graph
我们将使用 HoverState 节点的 Time Since Hover Start 属性 这是一个自悬停事件开始 以来的秒数值
我们将用这个值来定义 光晕高亮曲线的位置 当悬停事件开始时 光晕位置将沿着曲线 开始扫过
在为画笔笔画 生成网格时 我们的 App 会提供一个名为 CurveDistance 的属性 这个 App 通过 UV1 通道为每个顶点 提供 CurveDistance 值
这是画笔笔画曲线距离 的可视化效果 这个值会随着笔画长度 的增加而增加
着色器会将光晕高亮的位置 与曲线距离进行比较
这样着色器就能了解光晕 相对于 当前几何模型的位置
下一步是定义 光晕效果的大小
当前几何模型处在光晕位置 的范围内时 它就会发光
现在我们可以添加缓动曲线 它可以在光晕 扫过几何模型时 定义悬停效果的强度
最后一步是 根据我们刚刚计算出的强度值 将悬停效果的颜色 与原始画笔笔画的 颜色混合
这样看起来就很棒了!
要使用基于着色器的悬停效果 首先要创建一个带有着色器设置的 HoverEffectComponent
然后使用 ShaderGraphMaterial 它将接收 HoverState 节点的更新
现在我们已经为用户创建了 一种配置画笔的方法 是时候谈谈 App 的核心了 为每个画笔笔画生成几何模型
一般来说 网格是顶点 和连接顶点的三角形 等基元的集合
每个顶点都 与一些属性相关联 例如这个顶点的位置 或纹理坐标
这些属性 通过数据来描述 例如每个顶点位置 都是一个三维矢量
顶点数据需要整理 成缓冲区 以便提交给 GPU
对于大多数 RealityKit 网格 数据在内存中是连续整理的 因此在内存中 顶点位置 0 之后 是顶点位置 1 顶点位置 1 之后是顶点位置 2 以此类推 所有其他顶点属性 也是如此 索引缓冲区是单独布局的 它包含网格中每个三角形 的顶点索引
RealityKit 的标准网格布局 用途广泛 适合多种不同的使用情况 但在某些情况下 特定领域的方法 可能更有效
绘画 App 使用定制的 几何模型处理管道 来创建用户画笔笔画 的网格
例如 每个画笔笔画都经过了 平滑处理 可改善网格曲率
这种算法经过优化 因此可以尽快实现 在画笔笔画末端添加点 这一操作 这一点对于最大限度地 减少延迟至关重要
对于画笔笔画网格 使用单个缓冲区 来布置顶点
但与标准网格布局不同的是 每个顶点 逐一获得完整描述
因此属性是交错的 第一个顶点的位置之后 是这个顶点的法线 法线之后是双切线 以此类推 直到所有属性 都描述完毕 然后缓冲区才开始描述 第二个顶点 以此类推
相比之下 标准顶点缓冲区 会连续排列 每个属性的所有数据 画笔顶点缓冲区布局 对于绘画 App 来说 特别方便
在生成画笔笔画时 App 会不断将顶点 添加到顶点缓冲区的末端 请注意 画笔顶点缓冲区 可以添加新顶点 无需修改 旧数据的位置 而使用 标准顶点缓冲区时 大部分数据需要 随着缓冲区的增长而移动 画笔顶点的属性 也与标准布局不同
其中一些属性 如位置、 法线和位切线 是标准属性
而有些属性 如颜色、材质属性 和曲线距离 则是自定义属性
在我们 App 的代码中 画笔顶点 用 Metal 着色语言中的 这个结构来表示
这个结构的每个条目 都对应顶点的一个属性
因此我们面临着一个问题 一方面 我们希望保留 高性能几何引擎 的顶点布局 避免不必要的转换 或拷贝 但另一方面 我们几何引擎的布局 与 RealityKit 的标准布局 不兼容 我们需要的是一种方法 可以将 GPU 缓冲区 原封不动地移植到 RealityKit 并指示 RealityKit 如何读取缓冲区
现在 你可以使用名为 LowLevelMesh 的全新 API
有了 LowLevelMesh 你就能以多种方式 排列顶点数据
你可以使用四个不同的 Metal 缓冲区来存放顶点数据 因此 我们可以使用与 RealityKit 标准布局类似的布局
但有时 拥有不止一个缓冲区是非常有用的 例如 假设你需要 比其他属性 更频繁地更新纹理坐标 将这些动态数据 移到自己的缓冲区 会更有效率
你可以重新安排顶点缓冲区 以便交错排列 或是将交错 与非交错排列方式相结合
你也可以使用任何 Metal 基元类型 如三角形条带
我鼓励大家思考 LowLevelMesh 及其自定缓冲区布局 如何能为你的 App 带来益处
也许你的网格数据 来自二进制文件 并有自己的自定布局 现在 你可以直接将数据 传输到 RealityKit 中 而无需任何转换开销
或者 你可以将 现有的网格处理管道 与自己预定义的缓冲区布局 比如你在数字内容创建工具 或 CAD 应用程序中看到的布局 桥接到 RealityKit
你甚至可以使用 LowLevelMesh 将网格数据 从游戏引擎高效地 桥接到 RealityKit 中
LowLevelMesh 拓展了 你向 RealityKit 提供 网格数据的可能性 我们很期待看到 你的 App 能取得怎样的成果! 让我们深入了解一下 如何在代码中 创建 LowLevelMesh
现在 我们的 App 可将顶点缓冲区 原封不动地提供给 LowLevelMesh 而无需进行任何额外的转换 或不必要的拷贝
你可以使用 LowLevelMesh 属性 来描述顶点的布局方式 我将在 SolidBrushVertex 结构的 Swift 扩展中 设置属性列表
首先 我将声明 位置属性
让我们来详细了解一下 第一步是定义语义 这将指示 LowLevelMesh 如何解释属性
在本例中 属性是一个位置 因此我将使用这个语义
接下来 我将为这个属性 定义 Metal 顶点格式 在本例中 我需要选择 float3 以便与 SolidBrushVertex 中 的定义相匹配
接下来 我将以字节为单位 提供这个属性的偏移量
最后 我将提供一个布局索引 这是对顶点布局列表 的索引 我们将在后面讨论 绘画 App 只使用一个布局 因此我将使用索引 0
现在 我将 声明其他网格属性
法线和双切线属性 与位置类似 只是使用了不同的内存偏移 和语义
颜色属性使用 半精度浮点值 今年的新功能是可以 在 LowLevelMesh 中 使用任何 Metal 顶点格式 包括压缩顶点格式
对于其他两个参数 我将使用语义 UV1 和 UV3 今年还新增了 多达 8 个 UV 通道 供你在 LowLevelMesh 中使用 Shader Graph 材质 可以访问这些值 现在我们可以创建 LowLevelMesh 对象本身 为此 我将创建一个 LowLevelMesh 描述符 LowLevelMesh 描述符 在概念上类似于 Metal 的 MTLVertexDescriptor 但它也包含 RealityKit 在 摄取网格时需要的信息
首先 我将声明顶点和索引缓冲区 所需的容量
接下来我将传递 顶点属性的列表 这是我在上一张幻灯片 中列出的列表
然后 我将列出顶点布局列表 每个顶点属性都使用 其中一个布局
LowLevelMesh 为顶点数据提供 多达 4 个 Metal 缓冲区 缓冲区索引声明了 应使用哪个缓冲区
然后提供缓冲区偏移 和每个顶点的步长 大多数情况下 你只会使用 一个缓冲区 就像这里一样
现在我们可以 初始化 LowLevelMesh 了
最后一步 是填充部件列表 每个部件跨越 索引缓冲区的一个区域
你可以为每个网格部分 分配不同的 RealityKit 材质索引
我们的 App 采用三角形带状 拓扑结构 以提高内存效率
最后 你可以从 LowLevelMesh 创建 MeshResource 并将它分配给 实体的 ModelComponent
当需要更新 LowLevelMesh 的 顶点数据时 你可以使用 withUnsafeMutableBytes API
这个 API 可让你访问 实际的缓冲区 这个缓冲区将提交 给 GPU 进行渲染 因此 更新网格数据时 开销极小
例如 由于我们事先知道 网格的内存布局 我们可以使用 bindMemory 把提供的原始指针 转换为缓冲区指针
索引缓冲区数据 也是如此 你可以通过 withUnsafeMutableIndices 更新 LowLevelMesh 索引缓冲区
正如我们所见 LowLevelMesh 是 加速 App 网格 处理管道的强大工具 此外 LowLevelMesh 还允许你 使用 GPU 计算 来支持顶点或索引缓冲区的更新 我们来看一个示例
这是我们绘画 App 中的 闪光画笔 它会生成一个跟随画笔笔画 的粒子场 这个粒子场每帧都会 动态更新 因此它使用的更新方案 与我们看到的实体画笔不同
鉴于网格更新的频率 和复杂性 这种情况非常适合使用 GPU
让我们来详细了解一下 闪光画笔包含 每个粒子的属性列表 例如位置和颜色 和之前一样 我们也包含了 curveDistance 参数 以及粒子的大小
我们的 GPU 粒子模拟使用 SparkleBrushParticle 类型 来追踪每个粒子的 属性和速度 我们的 App 会使用 SparkleBrushParticles 的 辅助缓冲区进行模拟
SparkleBrushVertex 结构用于 网格的顶点数据 它包含了每个顶点的 UV 坐标 以便我们的着色器了解如何 在 3D 空间中确定粒子的方向 我们会为每个粒子 创建一个有四个顶点的平面
因此我们的 App 需要维护两个 缓冲区 来更新闪光画笔网格 一个粒子模拟缓冲区 填充了 SparkleBrushParticles 另一个是 LowLevelMesh 顶点缓冲区 包含了 SparkleBrushVertices
就像实体画笔一样 我将用一个 LowLevelMesh 属性列表 来说明 顶点缓冲区
属性列表与 SparkleBrushVertex 的成员相对应
当需要在 GPU 上填充 LowLevelMesh 时 你需要使用 Metal 命令缓冲区 和计算命令编码器
当缓冲区完成工作后 RealityKit 会 自动应用更改 下面是它在代码中的样子 正如我之前提到的 App 使用 Metal 缓冲区 进行粒子模拟 并使用 LowLevelMesh 作为顶点缓冲区
我将设置一个 Metal 命令缓冲区 和计算指令编码器 这样我们的 App 就可以运行 GPU 计算内核 来构建网格了
我将在 LowLevelMesh 上调用 replace 并提供命令缓冲区
这将返回一个 Metal 缓冲区 RealityKit 将直接使用 这个顶点缓冲区进行渲染
将模拟调度 到 GPU 后 我将提交命令缓冲区 命令缓冲区完成后 RealityKit 将自动开始使用 更新后的顶点数据
由于画笔笔画生成速度快、反应灵敏 我们的 App 看起来非常棒 现在 让我们用 引人入胜的启动屏幕 为 App 画上完美的句号
启动屏幕是欢迎用户进入 我们 App 空间的绝佳方式 同时也是一个打造有趣体验 和展示 App 视觉风格的机会
我们 App 的 启动屏幕包含四个视觉元素
标志包含 3D 文字 “RealityKit Drawing App” 使用了两种不同的字体
标志也是 3D 形状
底部有一个开始按钮 邀请用户开始绘图
在背景中 有一个引人注目的图形 会在你所处的环境中发光
我们先来制作标志
首先 我将创建一个 “RealityKit”属性字符串 并使用 默认的系统字体
今年的新功能是可以在 RealityKit 中使用 MeshResource extruding 从 AttributedString 创建 MeshResource
由于我们使用的是 AttributedString 因此很容易添加 具有不同属性的附加文本行 让我们用不同的字体和更大的字号来 书写文本 “Drawing App”
现在让我们使用段落样式 将文本居中
要进一步了解如何使用 AttributedString 为文本添加样式 请观看 WWDC21 讲座: “Foundation 中的新功能”
让我们放大 目前制作的文本 现在 3D 模型看起来有点过于扁平 所以 我们来自定一下模型 将 ShapeExtrusionOptions 结构 传递给 MeshResource extruding 就可以进行自定
首先我要指定一个较大的深度 以生成更粗的 3D 形状 接下来我会向网格添加第二种 材质 你可以指定将哪个材质索引 分配给正面、背面和侧面
最后 我将添加一个 微妙的倒角 这样从正面查看文字时 轮廓材质就会更加明显 在本例中 指定倒角 半径为 0.1
这款 App 还会生成标志 为此 需要使用 MeshResource extruding 我将使用 SwiftUI 路径 因此可以灵活定义形状 这个标志设置为 一系列贝塞尔曲线
要进一步了解 SwiftUI 路径 请查看 SwiftUI 教程 “绘制路径和形状”
现在我们来谈谈 启动屏幕的背景 这是 App 最引人注目的 美学元素之一 为了构建这一背景 我使用了名为 LowLevelTexture 的全新 API LowLevelTexture 提供的快速资源 更新语义与 LowLevelMesh 相同 但针对的是纹理素材资源
在启动屏幕上 App 使用 LowLevelTexture 为胶囊形状群 生成一种形状描述 这个形状描述 存储在纹理的红色通道中
颜色较深的区域 位于胶囊内部 而颜色较浅的区域 位于胶囊外部
在纹理的绿色通道中 App 存储了 对启动屏幕渐晕的描述
这个纹理将 通过 Reality Composer Pro 中 的 Shader Graph 着色器 解释为最终图像
要创建 LowLevelTexture 可以使用 LowLevelTexture 描述符 LowLevelTexture 描述符类似于 Metal 的 MTLTextureDescriptor 和 LowLevelMesh 一样 LowLevelTexture 可提供 对像素格式和纹理使用的详细控制 现在 你可以使用 RealityKit 压缩像素格式 对于启动屏幕 我们只需要红色和绿色通道 因此我们使用 RG16Float 像素格式
你可以从描述符中 初始化一个 LowLevelTexture 然后从 LowLevelTexture 创建 RealityKit 纹理资源
现在你就可以在材质中 使用这个纹理了
在 GPU 上更新 LowLevelTexture 就像更新 LowLevelMesh 一样 首先设置我们的 Metal 命令 缓冲区和计算命令编码器
然后使用命令缓冲区 调用 LowLevelTexture.replace 这会返回 Metal 纹理 你可以在计算着色器中写入这个纹理
最后调度 GPU 计算 并提交命令缓冲区 命令缓冲区完成后 Metal 纹理 将自动显示在 RealityKit 中 再次将所有内容整合在一起 我对这个启动屏幕的外观非常满意 醒目的背景 与个性化的 3D 几何图形相结合 给人一种与众不同的感觉 为我们的体验 画上了完美的句号!
以上就是所有内容 今天 我们使用 RealityKit 构建了 一款交互式空间绘画 App 我们使用了 RealityKit 的 空间追踪 API 这样 App 就能检测到 用户在所处空间中进行绘图的位置 我们使用 SwiftUI 和高级悬停效果 来构建交互式空间 UI 以自定义画笔和样式 我们了解了 RealityKit 中 资源更新的工作原理 并使用高级 LowLevel API 交互式生成 网格和纹理 最后我们使用了新的 API 来导入 2D 矢量图形并将这些图形空间化
我建议你查看讲座 “探索适用于 iOS、macOS 和 visionOS 的 RealityKit API” 和“利用 RealityKit 音频让 空间计算 App 更加引人入胜” 进一步了解 RealityKit 今年的新功能 我们已经迫不及待 想要看看你的作品了 尽情享受 WWDC24 的其他内容吧!
-
-
4:18 - Using SpatialTrackingSession
// Retain the SpatialTrackingSession while your app needs access let session = SpatialTrackingSession() // Declare needed tracking capabilities let configuration = SpatialTrackingSession.Configuration(tracking: [.hand]) // Request authorization for spatial tracking let unapprovedCapabilities = await session.run(configuration) if let unapprovedCapabilities, unapprovedCapabilities.anchor.contains(.hand) { // User has rejected hand data for your app. // AnchorEntities will continue to remain anchored and update visually // However, AnchorEntity.transform will not receive updates } else { // User has approved hand data for your app. // AnchorEntity.transform will report hand anchor pose }
-
7:07 - Use MeshResource extrusion
// Use MeshResource(extruding:) to generate the canvas edge let path = SwiftUI.Path { path in // Generate two concentric circles as a SwiftUI.Path path.addArc(center: .zero, radius: outerRadius, startAngle: .degrees(0), endAngle: .degrees(360), clockwise: true) path.addArc(center: .zero, radius: innerRadius, startAngle: .degrees(0), endAngle: .degrees(360), clockwise: true) }.normalized(eoFill: true) var options = MeshResource.ShapeExtrusionOptions() options.boundaryResolution = .uniformSegmentsPerSpan(segmentCount: 64) options.extrusionMethod = .linear(depth: extrusionDepth) return try MeshResource(extruding: path, extrusionOptions: extrusionOptions)
-
9:33 - Highlight HoverEffectComponent
// Use HoverEffectComponent with .highlight let placementEntity: Entity = // ... let hover = HoverEffectComponent( .highlight(.init( color: UIColor(/* ... */), strength: 5.0) ) ) placementEntity.components.set(hover)
-
9:54 - Using Blend Modes
// Create an UnlitMaterial with Additive Blend Mode var descriptor = UnlitMaterial.Program.Descriptor() descriptor.blendMode = .add let prog = await UnlitMaterial.Program(descriptor: descriptor) var material = UnlitMaterial(program: prog) material.color = UnlitMaterial.BaseColor(tint: UIColor(/* ... */))
-
13:45 - Shader based hover effects
// Use shader-based hover effects let hoverEffectComponent = HoverEffectComponent(.shader(.default)) entity.components.set(hoverEffectComponent) let material = try await ShaderGraphMaterial(named: "/Root/SolidPresetBrushMaterial", from: "PresetBrushMaterial", in: realityKitContentBundle) entity.components.set(ModelComponent(mesh: /* ... */, materials: [material]))
-
16:56 - Defining a vertex buffer struct for the solid brush
struct SolidBrushVertex { packed_float3 position; packed_float3 normal; packed_float3 bitangent; packed_float2 materialProperties; float curveDistance; packed_half3 color; };
-
19:27 - Defining LowLevelMesh Attributes for solid brush
extension SolidBrushVertex { static var vertexAttributes: [LowLevelMesh.Attribute] { typealias Attribute = LowLevelMesh.Attribute return [ Attribute(semantic: .position, format: MTLVertexFormat.float3, layoutIndex: 0, offset: MemoryLayout.offset(of: \Self.position)!), Attribute(semantic: .normal, format: MTLVertexFormat.float3, layoutIndex: 0, offset: MemoryLayout.offset(of: \Self.normal)!), Attribute(semantic: .bitangent, format: MTLVertexFormat.float3, layoutIndex: 0, offset: MemoryLayout.offset(of: \Self.bitangent)!), Attribute(semantic: .color, format: MTLVertexFormat.half3, layoutIndex: 0, offset: MemoryLayout.offset(of: \Self.color)!), Attribute(semantic: .uv1, format: MTLVertexFormat.float, layoutIndex: 0, offset: MemoryLayout.offset(of: \Self.curveDistance)!), Attribute(semantic: .uv3, format: MTLVertexFormat.float2, layoutIndex: 0, offset: MemoryLayout.offset(of: \Self.materialProperties)!) ] } }
-
21:14 - Make LowLevelMesh
private static func makeLowLevelMesh(vertexBufferSize: Int, indexBufferSize: Int, meshBounds: BoundingBox) throws -> LowLevelMesh { var descriptor = LowLevelMesh.Descriptor() // Similar to MTLVertexDescriptor descriptor.vertexCapacity = vertexBufferSize descriptor.indexCapacity = indexBufferSize descriptor.vertexAttributes = SolidBrushVertex.vertexAttributes let stride = MemoryLayout<SolidBrushVertex>.stride descriptor.vertexLayouts = [LowLevelMesh.Layout(bufferIndex: 0, bufferOffset: 0, bufferStride: stride)] let mesh = try LowLevelMesh(descriptor: descriptor) mesh.parts.append(LowLevelMesh.Part(indexOffset: 0, indexCount: indexBufferSize, topology: .triangleStrip, materialIndex: 0, bounds: meshBounds)) return mesh }
-
22:28 - Creating a MeshResource
let mesh: LowLevelMesh let resource = try MeshResource(from: mesh) entity.components[ModelComponent.self] = ModelComponent(mesh: resource, materials: [...])
-
22:37 - Updating vertex data of LowLevelMesh using withUnsafeMutableBytes API
let mesh: LowLevelMesh mesh.withUnsafeMutableBytes(bufferIndex: 0) { buffer in let vertices: UnsafeMutableBufferPointer<SolidBrushVertex> = buffer.bindMemory(to: SolidBrushVertex.self) // Write to vertex buffer `vertices` }
-
23:07 - Updating LowLevelMesh index buffers using withUnsafeMutableBytes API
let mesh: LowLevelMesh mesh.withUnsafeMutableIndices { buffer in let indices: UnsafeMutableBufferPointer<UInt32> = buffer.bindMemory(to: UInt32.self) // Write to index buffer `indices` }
-
23:58 - Creating a particle brush using LowLevelMesh
struct SparkleBrushAttributes { packed_float3 position; packed_half3 color; float curveDistance; float size; }; // Describes a particle in the simulation struct SparkleBrushParticle { struct SparkleBrushAttributes attributes; packed_float3 velocity; }; // One quad (4 vertices) is created per particle struct SparkleBrushVertex { struct SparkleBrushAttributes attributes; simd_half2 uv; };
-
24:58 - Defining LowLevelMesh Attributes for sparkle brush
extension SparkleBrushVertex { static var vertexAttributes: [LowLevelMesh.Attribute] { typealias Attribute = LowLevelMesh.Attribute return [ Attribute(semantic: .position, format: .float3, layoutIndex: 0, offset: MemoryLayout.offset(of: \Self.attributes.position)!), Attribute(semantic: .color, format: .half3, layoutIndex: 0, offset: MemoryLayout.offset(of: \Self.attributes.color)!), Attribute(semantic: .uv0, format: .half2, layoutIndex: 0, offset: MemoryLayout.offset(of: \Self.uv)!), Attribute(semantic: .uv1, format: .float, layoutIndex: 0, offset: MemoryLayout.offset(of: \Self.attributes.curveDistance)!), Attribute(semantic: .uv2, format: .float, layoutIndex: 0, offset: MemoryLayout.offset(of: \Self.attributes.size)!) ] } }
-
25:28 - Populate LowLevelMesh on GPU
let inputParticleBuffer: MTLBuffer let lowLevelMesh: LowLevelMesh let commandBuffer: MTLCommandBuffer let encoder: MTLComputeCommandEncoder let populatePipeline: MTLComputePipelineState commandBuffer.enqueue() encoder.setComputePipelineState(populatePipeline) let vertexBuffer: MTLBuffer = lowLevelMesh.replace(bufferIndex: 0, using: commandBuffer) encoder.setBuffer(inputParticleBuffer, offset: 0, index: 0) encoder.setBuffer(vertexBuffer, offset: 0, index: 1) encoder.dispatchThreadgroups(/* ... */) // ... encoder.endEncoding() commandBuffer.commit()
-
27:01 - Use MeshResource extrusion to generate 3D text
// Use MeshResource(extruding:) to generate 3D text var textString = AttributedString("RealityKit") textString.font = .systemFont(ofSize: 8.0) let secondLineFont = UIFont(name: "ArialRoundedMTBold", size: 14.0) let attributes = AttributeContainer([.font: secondLineFont]) textString.append(AttributedString("\nDrawing App", attributes: attributes)) let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.alignment = .center let centerAttributes = AttributeContainer([.paragraphStyle: paragraphStyle]) textString.mergeAttributes(centerAttributes) var extrusionOptions = MeshResource.ShapeExtrusionOptions() extrusionOptions.extrusionMethod = .linear(depth: 2) extrusionOptions.materialAssignment = .init(front: 0, back: 0, extrusion: 1, frontChamfer: 1, backChamfer: 1) extrusionOptions.chamferRadius = 0.1 let textMesh = try await MeshResource(extruding: textString extrusionOptions: extrusionOptions)
-
28:25 - Use MeshResource extrusion to turn a SwiftUI Path into 3D mesh
// Use MeshResource(extruding:) to bring SwiftUI.Path to 3D let graphic = SwiftUI.Path { path in path.move(to: CGPoint(x: -0.7, y: 0.135413)) path.addCurve(to: CGPoint(x: -0.7, y: 0.042066), control1: CGPoint(x: -0.85, y: 0.067707), control2: CGPoint(x: -0.85, y: 0.021033)) // ... } var options = MeshResource.ShapeExtrusionOptions() // ... let graphicMesh = try await MeshResource(extruding: graphic extrusionOptions: options)
-
29:44 - Defining a LowLevelTexture
let descriptor = LowLevelTexture.Descriptor(pixelFormat: .rg16Float, width: textureResolution, height: textureResolution, textureUsage: [.shaderWrite, .shaderRead]) let lowLevelTexture = try LowLevelTexture(descriptor: descriptor) var textureResource = try TextureResource(from: lowLevelTexture) var material = UnlitMaterial() material.color = .init(tint: .white, texture: .init(textureResource))
-
30:27 - Update a LowLevelTexture on the GPU
let lowLevelTexture: LowLevelTexture let commandBuffer: MTLCommandBuffer let encoder: MTLComputeCommandEncoder let computePipeline: MTLComputePipelineState commandBuffer.enqueue() encoder.setComputePipelineState(computePipeline) let writeTexture: MTLTexture = lowLevelTexture.replace(using: commandBuffer) encoder.setTexture(writeTexture, index: 0) // ... encoder.dispatchThreadgroups(/* ... */) encoder.endEncoding() commandBuffer.commit()
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。