大多数浏览器和
Developer App 均支持流媒体播放。
-
深入了解 RealityKit 2
使用 RealityKit 2 创建引人入胜的 AR 体验现在前所未有的简单。探索 RealityKit 框架的最新增强功能,深入研究这个水下示例项目。我们将带您了解改进的实体组件系统、流畅的动画制作管线以及增强了面部网格和音频功能的即插即用角色控制器。
资源
- Applying realistic material and lighting effects to entities
- Building an Immersive Experience with RealityKit
- Creating a Fog Effect Using Scene Depth
- Creating an App for Face-Painting in AR
- Explore the RealityKit Developer Forums
- PhysicallyBasedMaterial
- RealityKit
相关视频
WWDC23
WWDC22
WWDC21
WWDC20
WWDC19
-
下载
♪ ♪ 嗨 我是阿曼达 待会我的同事 奥利维尔会加入我们 本讲座中 我们会探索 我们在2021年 添加到RealityKit的功能 RealityKit是2019年推出的 增强现实创作框架 聚焦于写实的渲染 和让人轻松创建AR应用程序 利用ARKit读取设备的传感器数据 RealityKit让你能将3D内容 放在真实世界环境中 并让内容看起来 尽可能地真实 这里有些RealityKit体验的 好例子 在真实世界中参与寻物游戏 用虚拟方式和朋友比赛打保龄球 甚至成为 博物馆中的雕像 和找缤纷的虫子 在过去两年 我们看到了一些 用RealityKit创建 很棒的应用程序 也获得了能让这个框架变得更好 很好的反馈
我们聆听了大家的反馈 我们很高兴宣布RealityKit 2推出了 一堆新功能 帮助你 做出更身临其境的AR应用程序和游戏
本专题讲座中 我们会强调其中一些功能 包括最多人要求的功能 像是自定义着色器和材料 自定义系统 和全新的角色控制器概念 所以戴上你的潜水面具 我们潜入主题吧 我小时候住在中东时 我在波斯湾学会水肺潜水 虽然我那时没有机会戴 这种超级可爱的蒸气庞克头盔 我很爱看那些缤纷的鱼游在一起 我想说若能在我的客厅里 重建水底世界的感觉 应该会很好玩 奥利维尔和我写了这个演示 其中用了一堆 我们会在本专题讲座中示范的功能 也会在本周的第二场RealityKit 专题讲座进一步介绍 我们用后处理创建深度雾效果 和水波焦散 用自定义几何体修改器 让海带随着海浪舞动 还有更多 基本上 RealityKit 2让你现在能 自定义好多东西 你可以上developer.apple.com 下载并试用这段样本代码
我们今天会讲五大主题 我们会复习什么是ECS 还有我们如何用 新的自定义系统功能 在应用程序中实现鱼的聚集行为 我们会展示 你可以对材料还有动画做的事情 有哪些进步 示范新的角色控制器 也就是我们让潜水员 流畅地和客厅的AR网格 交互的方法 还有介绍现在可以如何 在运行时生成资源
我们先从ECS开始 ECS 全名为实体组件系统 是组织数据和行为的方式 它常用在游戏和模拟 它和面向对象编程不同 面向对象编程通常会 将一个物体模拟为 一个封装包 内含物体的功能 和与物体有关联的状态 但若用ECS会有三个分支: 实体 组件和系统 功能会放在系统内 状态放在组件 而实体是一组组件的标识符 今年的RealityKit 2中 我们迈向 更纯粹的ECS实现 导引你用我们的全新自定义系统 在系统层中保留更多你的功能
实体对于我们的意义是什么? 一个实体代表你的场景中的一样东西 这些实体代表我们场景中的海洋生物 一个实体可以有子实体 让你能够使用图结构 例如 转换组件使用 父实体的转换 将它自己的位置添加上去 实体本身不会在屏幕上渲染任何东西 要做到那点 你必须给它一个模型组件 或创建一个模型实体 它会帮你做这部分工作
要添加特性、属性和行为 将组件添加到你的实体 说到这 我们谈谈组件
组件的用途是存储帧与帧之间的状态 还有标记实体在系统中的参与与否 不过你不需要在这里 包括任何处理状态的逻辑 逻辑和行为要放在自定义系统里
有些组件原本就会存在 你创建的任何实体中 这里没有显示内置组件:转换组件 和同步组件 这三个实体上都有这两个组件 还有其他你通常会想添加的组件 像是模型组件 它包含让你的实体出现在屏幕上的 网格和材质 你也可以在运行时 从实体添加和移除组件 如果你想动态地改变它们的行为
我们在聚集系统中 将第一条鱼标记为参与 然后告诉它 它喜欢吃藻类 第二条鱼 它也会和第一条鱼一起聚集 但它现在比较喜欢吃浮游生物 第三个家伙是浮游生物 它会是第二条鱼的食物 它应该时时警觉 因为在我们的应用程序中 有些很饿的生物 我们知道哪几只肚子饿 因为它们身上有吃藻类组件 或吃浮游生物组件 每一帧 我们的进食系统 都会调用更新函数 在这里 它找到场景中 所有拥有两者其中一个组件的实体 加上所有是食物的实体 所以它能 将饥饿的鱼引到它们喜欢的食物那里 但进食系统有什么高性能的方式 能判断哪些实体肚子饿 哪些是食物 又有哪些两者都不是? 我们不想要必须穿过实体图 并检查每一个实体的组件 取而代之 我们执行实体查询 让RealityKit为你做簿记 聚集系统想要找到 所有有聚集组件的实体 而进食系统想要两种饥饿实体 加上是一种食物的实体 让我们仔细检视 当系统使用实体查询时会发生什么事 系统会在每一帧调用更新函数 我们看看黄高鳍刺尾鱼的 聚集系统 我们在这一帧暂停 看看发生什么事 在聚集系统中的更新函数中 我们查询场景中所有 同时有聚集组件和运动组件的实体 很多东西有运动组件 但我们不想要全部 我们只想要鱼群 我们的查询返回聚集的鱼 所以现在我们可以通过对鱼群中的 每条鱼应用经典的Boids模拟 驱动我们的自定义游戏物理 运动组件是存储帧与帧之间的状态 的地方 我们在每条鱼的运动组件上添加力 游在一起的力 偏好保持一定距离的力 尽量让鱼的鼻子指向同个方向的力
运动系统运行时 在同一帧里 但在聚集系统运行后 它会整合所有这些力 决定 一条鱼新的加速度 速率和位置 它不在意是哪个别的系统添加它们 还有其他系统 像是进食系统 和恐惧系统 同样在运动组件上执行 将鱼推往各种方向 我们来看看代码
这是我们的聚集系统的轮廓 这是一个类 符合RealityKit.System协议 当你在应用程序启动时注册 自定义系统 你是告诉引擎你要它 在应用程序的每个场景中 实例化一个这个类 init是必要的 你也可以 提供一个deinit 我们可以指定依赖项 这个系统应该永远在 运动系统之前先运行 这就是为什么我们在这里 用了枚举值.before 在我们的更新函数中 我们会修改存储在运动组件中的状态 而运动系统会根据 我们提供的状态行动 所以我们需要确保 聚集系统会在运动系统之前先运行 有点类似生产者与消费者的关系 你也可以用.after选项 如果你不指定依赖项 你的系统的更新函数会以 你注册的顺序执行
我们的实体查询表示我们想要所有 有聚集和运动组件的实体 这是静态let 因为它不会 在我们的模拟期间中改变 在多人AR体验中 符合Codable协议的组件 会自动通过网络同步 不过 系统中的数据不会自动 通过网络同步 通常数据应该存储在组件中 现在我们深入探讨聚集系统的 更新函数 它用一个SceneUpdateContext 里面含有那一帧的增量时间 和场景本身的引用 首先 我们对场景执行 实体查询 它会返回查询结果 让我们可以进行迭代 找出有聚集组件的实体
我们获得每一个的运动组件 我们要修改它 为什么我们没有获得聚集组件本身? 因为它没有任何和它有关联的数据 我们把那个当作标记一样使用 表示它是 鱼群中的一员 然后我们对它们运行标准Boids模拟 以导引鱼群 修改运动组件中的 力的集合 最后 既然我们为每条鱼添加了力 以把它推向 我们想要的方向 还有因为组件是Swift结构 是值类型 我们需要将运动组件 存回它来自的实体 系统不需要实现自定义更新函数 创建一个只提供一个init的系统 也可能很有帮助 像是为场景事件注册事件处理器 目前为止 我们一直在谈 实体 组件和自定义系统之间的关系 现在让我们把画面拉远 谈谈RealityKit 2中 推出的一些高层次架构变更 之前 你会用每一帧都调用的闭包 订阅SceneEvents.update事件 这种事件处理器通常会住在 或至少注册在类似游戏管理器的类里 与其用这样的闭包 你现在可以让你的更新逻辑 干净地分离并正式排序 在分开的系统更新函数中
所以这代表你的游戏管理器 可以做得比较少 与其在那里进行所有事件更新的注册 然后管理游戏中所有东西的 调用更新的顺序 现在游戏管理器只需要添加 组件到实体 以向你的系统指示 那些实体应该被包含在询问中
先前 你会在你的实体子类上 声明遵循协议 以表示 那个实体类型有某个特定组件 现在你不需要再子类化实体 因为实体也不用负责那么多事了
它可以仅是对象的标识符 它的特性可以建成组件 因为当你不为实体创建子类 就不会让对象永远跟那些组件 绑在一起 你能在体验期间 自由添加或移除组件 所以在RealityKit 2 你的自定义组件 好用多了 因为现在有自定义系统 但你还是两种方式都可以用 这就是游戏开发的美妙 你可以随心所欲 在我们的水底演示中 我们两种方法都用到了
我们也添加了一个新类型的组件: 瞬态组件 假设你的鱼害怕章鱼 但只有在它们看过章鱼时才会怕 当你复制一个新的鱼实体 你可能不想 复制鱼继承那条鱼对章鱼的恐惧 你可以让恐惧组件 符合瞬态组件 这样它就不会出现在新实体上
不过如果瞬态组件符合Codable 它还是会包含在网络同步里 就像任何其他种类的组件
另一个新功能是 Cancellable上的新扩展 你不再需要手动管理 为实体取消订阅事件 当你使用storeWhileEntityActive 我们会为你做这件事
这里 我们正在处理一个鱼实体的 相撞事件 我们不需要这个订阅比鱼本身活得久 所以我们用storeWhileEntityActive 跟往常一样 打造游戏时 一定有一堆你希望能临时调整 而不需要重新编译的设置 在我们的游戏中 我们在SwiftUI中 构建了一个设置视图 然后将它的支持模型传递到不同的 自定义系统之中 方式是将它们包在自定义组件里面
我们将设置实例创建为@StateObject 并将它作为environmentObject 传递到ARViewContainer 以及SwiftUI视图之中
我们把设置对象 包在CustomComponent里 那是一个SettingsComponent 然后当我们创建鱼实体 我们给它一个SettingsComponent 这样当出现任何 想要这些设置的自定义系统时 它可以从那里读取 像是采用“最高速度”值 用来作为每条鱼的最高速率 现在我要把画面交给我的同事 奥利维尔 他会介绍材质
谢谢阿曼达 今年 我们为材质添加了新的API 我们原本就已经有几种 例如SimpleMaterial 它有基色、粗糙度和金属属性 我们还有UnlitMaterial 只有一个颜色 没有照光 我们有OcclusionMaterial 可以当作隐藏虚拟对象的蒙板 去年我们推出了VideoMaterial 也就是用视频作为颜色的 UnlitMaterial 注意今年我们添加了透明度支持 如果视频文件包含透明度 它会被用来渲染对象 今年我们添加了新的API 让你对材质 能有更多高级控制 第一个类型是 PhysicallyBasedMaterial 它非常类似 USD中材质的架构 它是SimpleMaterial的超集 拥有大部分 可以在其他渲染器中找到的 标准PBR属性 这是会出现在 从USD加载的实体上的材质 例如 你可以加载小丑鱼的USD 然后修改材质上的个别属性 把它变成金色或紫色
在材质的属性中 你可以 例如说 改变法线贴图 添加不是网格一部分的小细节 你也可以分配一种纹理 定义模型的透明度 默认情况下 透明度使用 alpha混合 但如果你也分配 一个opacityThreshold 在阈值之下的所有着色 都会被放弃
你可以设置环境光遮蔽的纹理 定义模型中的模糊阴影 一个更高级的属性例子是清漆 它会在材质上模拟 一层额外的反光漆料 PhysicallyBasedMaterial类型上 还有好多其他属性可用
我们也添加了一种新类型 叫做自定义材质 让你用自己的Metal代码制作材质 我们就是用这个 在章鱼模型上做出 颜色转换效果 关于着色器和自定义材质 我们会在关于渲染的 第二场讲座中解释
除了材质 我们也在RealityKit中 添加了对于动画的更多控制
首先 我们先复习动画的既有API 这些大部分都是关于 播放从USD加载的动画 如果你从USD加载 你可以播放一次 你也可以让它重复 无限循环 也就是我们想让这个潜水员的 空闲动画做的 你也可以暂停 继续或停止动画
最后 播放新的动画时 你可以指定过渡持续时间 如果未指定 角色会 立即切换成新动画 如果你指定过渡持续时间 RealityKit会在持续时间内 混合旧动画和新动画 这很有用 例如当在 潜水员的走路和空闲循环之间过渡 但我们还是可以改善脚部的动画
我们可以用混合层的新API 让动画更写实 我们在两个分开的混合层 播放走路动画和空闲动画 既然我们在顶层播放走路动画 那是我们目前唯一能看到的动画 但我们可以变更走路动画的混合因子 显现出底下的空闲动画 注意随着混合因子变小 脚步也会变小
我们也可以改变动画的播放速度 让潜水员走更快或更慢 这里的潜水员以半速走路
最后 我们用角色相对于地面的速度 控制这两个值 这样我们可以让动画更流畅 并降低和地面对比 脚滑行的情形
目前为止 我们用了多个动画片段 例如空闲和走路循环 这些会作为动画资源 存储在RealityKit中 有多个从USD文件加载它们的方式 第一个方式是 让每个片段各一个USD文件 我们可以将每个USD作为实体加载 以动画资源形式获得它的动画 然后动画资源可在任何实体上播放 只要它的骨架中的接点名称 匹配动画的名称
另一个加载多个动画片段的方式 是将它们放在同一条时间线上的 单一USD中 然后用AnimationViews 将这个时间线切成多个片段
这个方式需要知道每个片段之间的 时间码
每个动画视图可以接着被转换成 一个动画资源 并和前一个方法一模一样的方式使用 现在我们来谈应用程序中章鱼的动画 章鱼躲起来了 但当玩家靠近 它会吓到并移动到新的躲藏地点 我们来看看怎么设置动画 首先加载章鱼的骨架动画: 跳跃、游泳和着陆 这些动画是从一个USD加载 就像我们为潜水员做的那样 但我们也想设置章鱼的转换动画 让它从一个位置移动到下一个位置 要设置转换动画 我们用一个新API 以编程方式 创建FromToByAnimation类型的动画
这样我们就可以设置位置动画 来看看在章鱼身上是什么样子
为了更有趣 我们也对旋转进行动画处理
现在章鱼会在移动时旋转 但它往侧边游 这样不太真实 我们可以通过制作一系列动画 改善这点 首先 我们让章鱼往新的位置旋转 然后我们转换到新的位置 最后 我们让章鱼 回头往摄像头的方向旋转 这是完整的动画
除了新的动画API外 我们也添加了管理角色物理的方式 叫做角色控制器 这让我们能创建 可以和场景中的碰撞器 实体交互的角色 这里 我们看到潜水员从地板 跳到沙发 在上面走路 这是通过 为潜水员添加一个角色控制器 这样做 潜水员会自动 和由LiDAR传感器生成的 环境的网格交互 创建角色控制器很简单 你只需要定义一个 匹配你的角色形状的胶囊 创建时 你必须指定胶囊的高度 和它的半径 角色控制器被分配到实体后 你可以在每一帧调用move(to:)函数 这会让角色移动到你想要的位置 但不会穿过障碍物 另一方面 如果你想要忽视障碍物 你可以用瞬移函数 现在我要把画面交给阿曼达 她会再介绍 我们这次为RealityKit添加的 几个好玩的功能 好的 谢谢奥利维尔 好 我会强调两个新的API 它们能用来随意立刻创造资源 而不需要从磁盘加载
首先我会展示如何 从SceneUnderstanding 取得一个人脸的网格 然后我会讲解如何生成音频 这些能为程序生成的艺术 打开和大海一样无边无际的可能 首先是人脸网格 我们的演示应用程序中那只 紫色和橘色的章鱼的样貌 让我超有灵感 我尝试在我的脸上画一只 不过是用虚拟的方式画 方式是使用新的人脸网格功能 SceneUnderstanding现在可以给你 代表人脸的实体 那些实体上有 模型组件 代表你可以换掉 人脸实体网格上的材质属性 我们玩得很开心 用现场画的图 立即生成应用在人脸网格上的纹理 我们来看看代码 SceneUnderstandingComponent 现在有个枚举属性 叫做entityType 它由SceneUnderstandingSystem设置 可以取两个值的其中一个:face 意思是它代表真实世界的人脸 或是meshChunk 意思是它是 再造世界网格的其他部分 它也可以是nil 意思是它的类型还未知 这又是个实体查询 你可以查询 有SceneUnderstandingComponent 的实体 并检查entityType 以找到人脸 然后 你可以从那些实体 取得模型组件 想拿来做什么就做什么 在我们的人脸彩绘范例中 我们用PencilKit 让人画在画布上 然后通过用那个CGImage 创建一个纹理资源 将产生的CGImage包在人脸实体上 我们用PhysicallyBasedMaterial 让人脸彩绘看起来 尽可能地逼真 我们在上面设置了几个属性 让我们的样子能有极致表现 要制造闪光漆效果 我们用法线贴图纹理 这会告诉基于物理的渲染器 这表面反射光的样子应该不同于 如果我们单纯让材料维持金属 然后我们给材质加上铅笔画的 纹理资源 并设置在实体上 这就是使用新的生成资源的 方式之一 另一种现在可以生成的资源 是AudioBufferResource 你可以用各种方式获得 AVAudioBuffer: 通过录制麦克风输入 自己以程序生成 或是用AVSpeechSynthesizer 然后你可以用AVAudioBuffer创建 AudioBufferResource 并用来在应用程序中播放声音
以下是我们将文字转成语音的方式 我们写了AVSpeechUtterance 到AVSpeechSynthesizer 我们在回调中 收到一个AVAudioBuffer 这里 我们创建一个 AudioBufferResource 并将它的inputMode设置为.spatial 以利用3D位置音频 其他可用inputMode为nonSpatial 和ambient 然后我们叫实体播放那段音频 当然 你可以用花俏的技巧 处理音频缓冲区 让你的鱼听起来 像是在水底说话时吹泡泡 或是其他你能想到的有趣声音
以上就是今年RealityKit 一些新功能的概览 我们全心投入于让你更能控制 场景的外观和行为 我们修改了我们的ECS 以提供自定义系统给你 让你在架构应用程序的行为时 灵活度大幅提升 我们添加了很多改进 到材质和动画API 我们推出了角色控制器 让你的实体能轻易和 真实世界的环境交互 最后 我们强调了几个 能够实时生成资源的方式 但RealityKit 2的新功能 绝对不只这些 在本周稍晚第二场 RealityKit专题讲座中 你可以了解更多新的渲染功能 并看到我们在水底演示中 某些东西实现的方法 我们用几何体修改器 设置海带的动画 章鱼用表面着色器 让它在颜色之间美丽转换 蓝色深度雾效果还有水波焦散 都是用后处理创建 而关于生成性资源的主题中 你会学到如何使用动态网格 若想复习 你可能想看 2019年的 《利用 RealityKit 构建 App》讲座 谢谢收看 我们很期待看到 你用这些API挥洒的创意 [欢快的音乐]
-
-
7:10 - FlockingSystem
class FlockingSystem: RealityKit.System { required init(scene: RealityKit.Scene) { } static var dependencies: [SystemDependency] { [.before(MotionSystem.self)] } private static let query = EntityQuery(where: .has(FlockingComponent.self) && .has(MotionComponent.self) && .has(SettingsComponent.self))
-
8:34 - FlockingSystem.update
func update(context: SceneUpdateContext) { context.scene.performQuery(Self.query).forEach { entity in guard var motion: MotionComponent = entity.components[MotionComponent.self] else { continue } // ... Using a Boids simulation, add forces to the MotionComponent motion.forces.append(/* separation, cohesion, alignment forces */) entity.components[MotionComponent.self] = motion } }
-
11:58 - Store Subscription While Entity Active
arView.scene.subscribe(to: CollisionEvents.Began.self, on: fish) { [weak self] event in // ... handle collisions with this particular fish }.storeWhileEntityActive(fish)
-
12:36 - SwiftUI + RealityKit Settings Instance
class Settings: ObservableObject { @Published var separationWeight: Float = 1.6 // ... } struct ContentView : View { @StateObject var settings = Settings() var body: some View { ZStack { ARViewContainer(settings: settings) MovementSettingsView() .environmentObject(settings) } } } struct SettingsComponent: RealityKit.Component { var settings: Settings } class UnderwaterView: ARView { let settings: Settings private func addEntity(_ entity: Entity) { entity.components[SettingsComponent.self] = SettingsComponent(settings: self.settings) } }
-
21:26 - FaceMesh
static let sceneUnderstandingQuery = EntityQuery(where: .has(SceneUnderstandingComponent.self) && .has(ModelComponent.self)) func findFaceEntity(scene: RealityKit.Scene) -> HasModel? { let faceEntity = scene.performQuery(sceneUnderstandingQuery).first { $0.components[SceneUnderstandingComponent.self]?.entityType == .face } return faceEntity as? HasModel }
-
22:03 - FaceMesh - Painting material
func updateFaceEntityTextureUsing(cgImage: CGImage) { guard let faceEntity = self.faceEntity else { return } guard let faceTexture = try? TextureResource.generate(from: cgImage, options: .init(semantic: .color)) else { return } var faceMaterial = PhysicallyBasedMaterial() faceMaterial.roughness = 0.1 faceMaterial.metallic = 1.0 faceMaterial.blending = .transparent(opacity: .init(scale: 1.0)) let sparklyNormalMap = try! TextureResource.load(named: "sparkly") faceMaterial.normal.texture = PhysicallyBasedMaterial.Texture.init(sparklyNormalMap) faceMaterial.baseColor.texture = PhysicallyBasedMaterial.Texture.init(faceTexture) faceEntity.model!.materials = [faceMaterial] }
-
23:09 - AudioBufferResource
let synthesizer = AVSpeechSynthesizer() func speakText(_ text: String, forEntity entity: Entity) { let utterance = AVSpeechUtterance(string: text) utterance.voice = AVSpeechSynthesisVoice(language: "en-IE") synthesizer.write(utterance) { audioBuffer in guard let audioResource = try? AudioBufferResource(buffer: audioBuffer, inputMode: .spatial, shouldLoop: true) else { return } entity.playAudio(audioResource) } }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。