大多数浏览器和
Developer App 均支持流媒体播放。
-
在 Reality Composer Pro 中编写交互式 3D 内容
探索 Reality Composer Pro 中的“时间线”视图如何栩栩如生地呈现你的 3D 内容。了解如何使用逆向运动分析、形状变形和骨骼位姿打造一个动画故事,让其中的角色和物体能够彼此之间以及与周围环境之间进行交互。我们还将介绍如何使用内置操作和自定操作、为操作排序、应用触发条件,并实现自然动作。
章节
- 0:00 - Introduction
- 2:54 - Introducing timelines
- 18:22 - Inverse kinematics
- 22:55 - Animation actions
- 27:23 - Blend Shapes animation
- 30:35 - Skeletal poses
资源
相关视频
WWDC24
WWDC23
-
下载
大家好 我叫 Marin 我是一名 RealityKit Tools 工程师 今天我想向大家介绍 Reality Composer Pro 中的 一项全新功能:时间线 它提供了一种全新的互动方式 可以让你的 3D 内容栩栩如生 去年 我们推出了 Reality Composer Pro 帮助你更轻松地预览 和准备 App 的 3D 内容 它包含一个可视化编辑器 你可以在其中编排场景 整理素材资源 添加互动操作和物理效果 还能使用 ShaderGraph 编辑器 微调材质的外观 Reality Composer Pro 功能丰富 非常强大 如果你刚刚接触 Reality Composer Pro 或者想复习一下这款工具 强烈建议你观看 去年 WWDC 大会期间 举办的这些讲座 借助 Reality Composer Pro 你可以直观地设计、编辑 并预览 RealityKit 内容 你可以在同一个地方 编排场景、配置组件、 打造复杂材质、 添加音频等等 今年 我们增加了新功能 帮助你使用时间线编辑器 创建动画 添加灯光效果 以及进行环境创作 在这场讲座中 我们会重点介绍时间线编辑器 我会展示如何使用时间线 构建交互式 App 还会介绍 RealityKit 中的 一些全新动画 API 下面我们来看一看
你可能已经在其他讲座中 看到过“BOT-anist”App 我们以这款 App 中的机器人为例 将它重新启动 打造属于我们自己的 交互式虚拟体验 在这个 App 中 有 3 棵植物开始枯萎 如果植物枯萎了 我可以轻点这棵植物 从而触发机器人移动 到植物旁 为它浇水 植物慢慢恢复健康后 机器人就会返回到 平台的中央 还有另一个机器人 在观察蝴蝶 试图在蝴蝶移动时 伸手触摸它 本讲座下方列出了示例项目的链接 你可以把它下载下来跟着一起操作 要搭建这个 App 我们首先要使用 Reality Composer Pro 中的时间线 将机器人移动到植物旁
然后 我将展示 如何利用 RealityKit 的 全身逆向运动分析系统 让机器人伸出手来 给植物浇水
之后 我将讲解 如何在代码中编写动画动作 并使用这些动作 让机器人转身 回到起始位置
接着 我将展示 如何使用形状变形动画 为植物添加动画效果
最后 我们将为 观察蝴蝶的第二个机器人 添加骨骼位姿动画
让我们先来 了解一下时间线 时间线是 Reality Composer Pro 中的一项全新功能 可让你设置动作序列 从而以特定顺序或 在特定时间执行动作 借助 Reality Composer Pro 你可以轻松编辑 和配置这些动作 左侧面板列出了 所有时间线
中间是主时间线编辑器 右侧面板则列出了 所有可用的内置动作 创建时间线后 可以让时间线 根据触发器播放动画
我们来看看第一个动画 根据我们的经验 轻点一棵植物就会触发机器人 转身并朝着你轻点的植物移动 让我来展示一下 如何构建这个时间线序列 在 Reality Composer Pro 中 你可以从底部面板找到 名为“Timelines”的新标签 在中间部分 有一个“Create Timeline”按钮 我要选择这个按钮 来创建一个时间线 左侧列出了 所有可用的时间线 中间则是 主时间线编辑器 在这里 我可以配置动作 并在时间线上为动作排序 右侧列出了 所有预构建的动作 我可以轻松将它们 拖放到时间线上 让我们将时间线 重命名为 MoveToPoppy 选中这条时间线 轻点两下 然后输入“MoveToPoppy”
首先 我要让机器人 转向刚才被轻点的植物 为此 我将使用预构建动作 列表中的旋转动作 我将旋转动作 拖到时间线上
我想让这个机器人旋转 因此需要在检查器面板的 “Target”字段下选择“Choose” 然后在层次结构或视口中 找到我的机器人实体 并将它选中 完成选择后 轻点“Done”
现在 让我们在检查器面板中 设置转数来配置 机器人旋转的幅度 我们将转数设置为 0.12
我并不想让它转一整圈 而只是稍微转身 让我们看看效果如何 时间线编辑器的顶部 有一个播放按钮 轻点这个按钮 便可看到动作的预览效果
太棒了 我看到机器人会转身 而且转身之后正好面向植物 旋转之后 我要让机器人移动起来 为此 我会使用 “Transform To”动作 从预构建动作列表中 抓取“Transform To”动作 然后将它拖放到编辑器中 我将它安排在旋转动作之后 一秒左右执行 我要将这个动作的目标 设置为机器人 因为我想让机器人 向植物方向移动 因此 我需要选择“Choose” 并在层次结构中找到我的机器人 然后轻点“Done” 现在 我可以看到机器人 目标移动位置的视觉提示 当前 它位于平台的中央 这并不是我们想要的效果 因此 我们需要使用操纵器 将机器人的目标位置 移动到虞美人的正前方
好了 我觉得这看起来不错 现在 我们来更改 转换动作的持续时间 我要将时长从默认的 1 秒 改为更长的时间 这会让机器人需要花费 更长时间才能移到植物旁边 为此 我可以 在编辑器中拖动 动作的后边缘
或者 我也可以在检查器面板中 设置持续时间
我可以拖动动作 将它从一个轨迹移至另一个轨迹 这样就可以对多个动作进行排序 让它们同时运行 我将旋转动作拖放到 稍后将使用的新轨迹上 现在 让我们轻点播放按钮 来预览一下效果
我觉得这看起来很不错 如果能在机器人向植物 移动的过程中 为它添加动画和音频 那么效果会更棒 为此 我将创建另一条时间线 用于协调行走动画 和相应的音频 轻点时间线列表底部的 加号 (+) 按钮 将这个时间线 重命名为 RobotMove 选中这条新的时间线 轻点两下 然后输入“RobotMove”
为了播放动画和音频 我将使用两个新组件 AnimationLibraryComponent 和 AudioLibraryComponent 让我们来看看这两个组件 AnimationLibraryComponent 用于存储动画 并将这些动画与需要 播放动画的实体关联起来 你可将 AnimationLibraryComponent 添加到装配实体 然后将动画资源添加到 AnimationLibraryComponent 中 Reality Composer Pro 让你能轻松地将动画添加到 AnimationLibraryComponent 中 只需轻点加号 (+) 按钮 然后选择 项目中的动画 这样就能将动画资源添加到 这个实体的 AnimationLibraryComponent 中 之后 你便可以 在代码中播放动画 或者使用 Reality Composer Pro 中的时间线来播放动画
AudioLibraryComponent 的工作原理 与 AnimationLibraryComponent 非常类似 AudioLibraryComponent 用于 存储音频资源 并将这些资源 与需要播放音频的实体关联起来 你可以将 AudioLibraryComponent 添加到实体中 然后将音频资源添加到 AudioLibraryComponent 中 要将音频资源添加到 AudioLibraryComponent 中 你需要轻点加号 (+) 按钮 然后从你的项目中选择音频文件 这会将音频资源添加到 这个实体的 AudioLibraryComponent 中
之后 你便可以 在代码中播放音频或者 使用时间线来播放音频 让我们回到 Reality Composer Pro 完成将音频和动画 添加到时间线的操作 在 RobotMove 时间线上 我将使用新的动画库组件 来添加一个动画动作 我已经将要使用的 USD 动画 添加到我的项目浏览器中 为此 我将在层次结构中 选择机器人 在检查器面板中 可以看到 系统已经为我 添加了一个动画库组件 这是因为这个实体 已经关联了 一个 USD 动画 Reality Composer Pro 会自动为我添加组件 并在动画列表中 列出它的默认动画 我也可以选择 检查器面板底部的 “Add Component”按钮 来手动添加动画库组件 如果我选择默认的子树动画 则可以使用播放按钮 预览动画 或者使用剪刀图标 将它切割成片段 这个动画 比我想要的稍长一些 因此 我们要将它分成两个片段 即较短的开始动画和结束动画 还要让它们连续播放 首先 抓住播放头 将它拖动到第一个片段的 分割位置 然后 点按剪刀图标 这样就创建了两个片段 现在我们再进行一次分割 这样就可以只使用结尾部分 再次将播放头拖移到 要进行第二次分割的位置 然后点按剪刀图标
太好了 现在我有了两个 可以连续播放的片段 将这两个片段重命名为 “startWalk”和“endWalk”
接下来选择“RobotMove”时间线 以便将动画依次排序 抓取“Animation”动作 将它拖到时间线上
我要将这个动作的目标 设置为我的机器人 因此我将选择“Choose” 然后在层次结构中选取机器人 接着 将“Animation” 设置为“startWalk”片段
再将另一个“Animation”动作 拖到时间线上 用于第二个片段
让这个动画紧跟 第一个动画之后播放 同样将这个动作的目标 设置为我的机器人
并将“Animation” 设置为“endWalk”片段
在动画运行时 我还想同时播放 一个音频片段 为此 我需要使用 AudioLibraryComponent 以及一个已经添加到 项目中的音频文件 在层次结构中选择机器人 在检查器面板中选择 “Add Component”按钮 来添加一个新组件 然后从列表中选择 “Audio Library”组件
轻点加号 (+) 按钮
找到所需的音频文件 我会添加两个音频文件 一个用于机器人转身场景 一个用于机器人移动场景
现在 让我们回到 “RobotMove”时间线 并设置音频动作的 运行时间 将一个音频动作 拖到时间线上 我希望机器人发出音频声效 因此选择机器人作为发射器 并且我想让它播放刚刚添加的 walk 音频片段 因此将音频资源设置为“walk” 然后拖动音频动作 使它与 walk 动画 同时播放
好了 现在让我们来播放一下
太棒了 动画 和音频一起播放了 现在 我准备 将 RobotMove 时间线 插入 MoveToPoppy 时间线 首先选择“MoveToPoppy”时间线 然后右键点按 “RobotMove”时间线 并选择 “Insert into Timeline”选项 这会将 RobotMove 时间线 插入 MoveToPoppy 时间线 如此便实现了在一个时间线 内播放另一个时间线 我还可以拖动 这条嵌套时间线 将它移动到任何位置 我希望在旋转结束后 立即播放动画
并将它与转换动作 搭配使用 从而使动画和转换 同时进行 我可以更改 转换动作的持续时间 使它在动画开始后 稍微延迟开始移动 并在动画结束前 稍微提前停止移动 我还将添加一段音频 在机器人旋转时播放 我将从预构建动作列表中 拖动一个音频动作 将它放置在与旋转动作 同时发生的位置 将机器人设置为发射器 并将音频资源 设置为旋转音频
下面我们来预览一下 看看效果如何
太棒了 机器人向植物移动 同时也在播放动画和音频 我们已经创建了时间线 那么接下来如何 让时间线开始播放呢 有两种方法 可供选择 一是在代码中 使用 RealityKit API 针对时间线的 动画资源实例调用 entity.playAnimation 二是使用 Reality Composer Pro 的 UI 添加新的“Behaviors”组件 这个组件可启动时间线 让它开始播放 而无需编写任何代码 要使用 Reality Composer Pro 来启动时间线播放 需要向用来触发时间线的 实体添加一个 “Behaviors”组件 这里有一些不同类型的触发器 均可用于 启动时间线播放 比如轻点、碰撞、 将实体添加到场景 或者收到代码中 发布的通知 让我们回到 Reality Composer Pro 在植物上设置一个 启动时间线运行的轻点手势 要设置轻点手势 首先选择 要轻点的植物 在本例中 我将选择 层次结构中的虞美人 我还要添加一个新组件 方法是轻点检查器面板中的 “Add Component”按钮 找到“Behaviors”组件 然后 我可以轻点加号 (+) 按钮 并选择轻点触发器 因为我希望这个植物 对用户的轻点操作做出响应 当我轻点植物时 我希望轻点手势 能触发 MoveToPoppy 时间线播放 因此我将选择列表中的 “MoveToPoppy”时间线 现在 我们需要告知 RealityView 对轻点手势作出响应 让我们进入 Xcode 在 RealityView 中 我要添加一个轻点手势
轻点手势的属性将为 targetedToAnyEntity 轻点结束后 当你抬起手指时 我们将从传递给我们的值中 获取实体 并将轻点行为应用于这个实体 这意味着 当我轻点实体时 也就是本例中的虞美人 将会应用 Reality Composer 中 指定的轻点行为 即“MoveToPoppy”时间线 现在 我可以构建并运行来试试效果 让我们轻点虞美人 看看会发生什么
太棒了!当我轻点植物时 可以看到机器人转身 走向了植物 我们成功地创建了 第一个动画 并使它根据用户的输入 开始播放 这真是太酷了! 现在开始制作第二个动画 机器人到达植物位置后 我想让机器人 将手臂伸向植物 为此 我将添加 一个通知动作 并在代码中侦听这个通知动作 当通知触发时 我会编写一些自定代码 让机器人向植物伸出手臂 让我们返回 Reality Composer Pro 添加一个通知动作
从预构建动作列表中 将一个通知动作拖放到 时间线的末尾
将这个通知动作的目标 设置为虞美人 然后 将这个通知的标识符 设置为 ReachToPoppy
这个字符串将在 通知触发时传递给我们 我们已经在 Reality Composer Pro 中设置了通知 接下来让我们添加代码 以便在沉浸式视图中 侦听这个通知 回到 Xcode 在沉浸式视图中 添加通知名称 “ReachToPoppy” 然后为通知 添加发布者
接下来 使用 onReceive 处理 RealityView 视图上的 通知接收 当我从输出中 接收到通知时 可以从用户信息字典中 获取源实体 然后编写一些代码 让机器人 将手伸向植物 让我们来看看 伸手动画 应该是什么样子 当机器人到达植物的位置时 我想让它伸出手臂
为此 我将使用 RealityKit 的 全身逆向运动分析系统 逆向运动分析 是指利用运动分析方程 来确定装配骨骼结构中的关节 应如何运动 以达到所需位置 你可以指定目标位置 以及对受影响关节的 任何约束 系统会为你计算 中间的关节位置 这方面的一个常见示例是 将手移动到所需位置 然后肘部会自动调整 以达到这个位置 你可以使用这种方法 来实现角色的自然运动效果 我将通过示意图 来讲解全新的 Inverse Kinematics API 然后展示如何在代码中使用它 首先 我将实例化一个 IKRig IKRig 定义了 逆向运动分析求解器的 工作方式 我将为 IKRig 提供模型骨骼 以及迭代次数等 配置 如果要在同一实体上 播放动画 则需要为 动画和 IK 赋予权重 这样求解器就会知道要覆盖哪些内容 然后 我将添加一些约束条件 约束是 对关节变化的限制 这可用于 防止不自然的动作 比如向错误的方向 弯曲肘部
接着 我将从定义的 IKRig 创建一个 IKResource IKResource 是 IK 求解器 用于处理的运行时数据 我将从 IKResource 创建一个 IKComponent 将它添加到一个实体中 你可以在实例化这个组件时 设置求解器 并在运行时对它进行更新 频率可以达到每帧更新一次
需要注意的是 RealityKit 的 IK 求解器 会同时对整个角色的 骨骼进行求解 而不仅仅是针对 关节层次结构的一个子集 现在 让我们利用 逆向运动分析来编写代码 从而让机器人将手伸向植物
要设置逆向运动分析求解器 首先要初始化一个空的 rig 并传入模型骨架 然后 更新全局 rig 设置 经过反复试验 我发现 将 maxIterations 设置为 30 并将全局正向运动分析权重 设置为 0.02 的效果最佳 同时 我将引用 rig 骨架上的 关节名称 在本例中 我们需要移动 机器人的臀部、胸部和手部 以实现机器人 将手臂伸向 植物的自然动画效果
然后 我将定义针对 rig 的约束条件 本例设置了两个父约束 和一个点约束 父约束会限制 关节的位置和方向 点约束 会限制关节的位置 接下来 我将创建一个 包含 rig 的资源 并添加一个带有 给定资源的 IKComponent 为了在每一帧更新 IK 目标 我将在场景中找到实体 并确定它的位置
首先 我要获取 手应该伸到的位置 也就是相对于 带有 IKComponent 的 实体而言的位置 然后 更改 reachPosition 的 x、y 和 z 轴位置 使它在每一帧都略有不同
我还想在每一帧更新 对左手的约束 因此 我将从实体中 获取 IKComponent 设置左手的 目标平移和位置 数值 0 表示 IK 对约束没有影响 而数值 1 表示 IK 对约束有完全影响
为了在 IK 开始和结束时 流畅地设置机器人手臂的动画效果 我会随着时间的推移 递增和递减 IK 对位置的驱动权重 以及基础位姿对位置的驱动权重 然后 将更新后的值 提交到组件中
我们来运行代码看看效果如何
不错! 我们实现了期望的动画效果 在机器人伸出手臂后 我使用粒子效果来 浇灌植物 现在 机器人 已经为植物浇完水 我们需要让机器人 回到起始位置 为此 我们将使用动画动作 动画动作能让你设置 动作发生的时间 并按顺序组合 一段时间内的动作 这类似于游戏中的 场景切换动画 但有了动画动作 你可以实时做到这一点 一个常见的用例是 角色行走时的 落脚事件 角色的脚每次落地 都会发出声音 让我们使用动画动作 让机器人 回到平台中央
动作 API 包含内置动作 和自定动作 内置动作包括大量 易于配置的 预构建动作 例如 SpinAction 或 PlayAnimationAction 这是 Reality Composer Pro 时间线功能的 底层 API 如果内置动作 无法满足你的需求 还可以使用自定动作 这样你就可以用代码 编写自己的动作 并将它同步到时间线上 要使用内置动作 API 首先需要使用 内置动作 为实体创建动画定义 然后 根据定义创建 动画资源 对你希望同时播放的 动画资源进行分组 按一定顺序排列 要播放的动画资源 最后 在实体上调用 播放动画 让我们来看看如何 用代码来完成这一过程
这几个包含逻辑的方法 能够定义动画 并且会返回 用于旋转、移动和对齐 机器人的动画资源 这些动画资源 实例的逻辑 由现有的 RealityKit API 定义 我将这些动画按顺序组合在一起 依次是 RotateAnimation、 WalkAndMoveAnimation、 AlignAtHomeAction 和 RobotTravelHomeCompleteAction 然后 我将使用现有的 playAnimation API 按顺序开始播放这些动画
对于自定动作 首先需要 创建你自己的 EntityAction 协议合规动作 然后用 makeActionAnimation API 从中创建一个 AnimationResource 将这个动画资源 与其他动画资源分组 或者对它进行排序 以便按一定顺序执行
最后 在实体上调用 playAnimation 在运行时 你可以订阅 自定动作的 开始和结束事件
让我们看看如何 在代码中实现这些步骤 从而让机器人 在给植物浇完水后 返回起始位置 我将创建一个自定动作 名为 RobotMoveToHomeComplete 用于在完成 move-to-home 程序后通知我们 然后 创建一个 RobotMoveToHomeComplete EntityAction 实例 使用 makeActionAnimation API 从中生成一个 AnimationResource
需要注意的一点是 你可以使用自定动作 对多个动画进行 排序或分组 由于这里只有一个动画 我们无需进行任何排序
然后使用 playAnimation API 播放动画资源
我订阅了 EntityAction 启动事件 当动作开始时 将调用订阅闭包 当开始事件触发时 我们便知道机器人 已经到达它的目的地 因此我将停止播放控制器 来停止动画 我还订阅了 RobotMoveToHomeComplete EntityAction 结束事件 当系统调用闭包时 我将机器人 转换到 .arrivedHome 状态
现在 让我们来运行这个动画序列
太好了 我们现在进展顺利
接下来 我们重点了解一下植物动画 为了实现期望的效果 我会用到形状变形动画 利用形状变形动画 可以从一个位姿 平滑过渡到另一个位姿 从而创作出栩栩如生的动作 这些动画可融合 一系列不同的形状 通常用于为角色的面部或身体 添加动画效果 在我们的示例中 我将为植物制作 从枯萎到健康状态的动画 以及从健康到枯萎的动画
形状变形动画 API 包含 BlendShapeWeightsMapping、 BlendShapeWeightsComponent 以及两种执行动画的方式 也就是以程序化方式执行 或通过 USD 动画执行 利用 BlendShapeWeightsMapping 可设置一个 与融合目标相关的权重 你可以在实体上设置融合目标 每个目标都可以有 与之关联的权重 可以设置 0-1 之间的权重 在我们的示例中 权重 1 表示植物枯萎 权重 0.5 表示处于中间状态 权重 0 表示植物健康 为了针对植物制作 从枯萎到恢复健康的动画效果 我将随着时间的推移更新权重值 要使用形状变形动画 API 首先需定义 BlendShapeWeightsMapping 然后根据映射创建一个 BlendShapeWeightsComponent 并将这个组件 添加到实体中 将组件添加到实体后 我便可以随时查询 BlendShapeWeightsComponent 以获取权重值并进行更新 现在 我就能以程序化的方式 创建 FromToBy 或 Sampled 形状变形动画 或者 我也可以使用现有的 RealityKit playAnimation API 来播放 USD 形状变形动画 让我们用代码来创建这个动画 首先 从实体层次结构中 找到拥有模型组件的 模型实体 获取它的网格资源 并从这个网格资源创建 BlendShapeWeightsMapping 然后 根据映射创建 BlendShapeWeightsComponent
为了在运行时更新融合权重 我将从实体中获取 BlendShapeWeightsComponent
获取 BlendShapeWeightsSet 的副本 以便将权重值 分配到这个集合
通过 BlendShapeWeightsSet 中的 blendWeightIndex 更新所有权重值 并将它设置为 0 这表示植物处于健康状态 在本例中 我们要将植物的状态设置为 保持健康状态 要从健康状态变为枯萎状态 可以使用缓动函数 逐渐增加或减少这个值
然后将新的权重值分配给 BlendShapeWeightsComponent 来应用这些值
让我们运行代码 来唤醒这些花朵
看到花朵恢复生机 真是太好了 现在我们来看看 第二个机器人在做什么
这个机器人正在利用 逆向运动分析 将手伸向蝴蝶 蝴蝶在空中飞来飞去 这是因为 我们在 Reality Composer Pro 中 使用时间线 创建了动画动作 并使用了一个自定组件 来调整蝴蝶的位置 聚焦机器人的头部 可以看到头部正跟随 蝴蝶的飞行轨迹而移动 要实现这一动作 我们可以使用 全新的骨骼位姿 API 典型的 3D 装配角色 由骨骼结构组成 而构成骨骼结构的是彼此相连的骨骼 每根骨骼对应 角色或对象的 不同部分
要为角色添加动画效果 需要旋转角色的关节 从而将对象摆出各种位姿 并呈现出动画效果 这通常用于 使角色行走或奔跑
骨骼位姿 API 添加了一个 新的 SkeletalPosesComponent 你可以使用 由 RealityKit 制作的动画 或者 你也可以在运行时修改骨骼 通过一个接口 以编程方式 针对特定关节查询 SkeletalPosesComponent 并更新它的变换
我们将使用骨骼位姿 API 使机器人的颈部骨骼旋转 仿佛在观赏 蝴蝶飞舞一样
凡是从 USD 文件 导入到 RealityKit 的 蒙皮网格都自带 附加到实体的 SkeletalPosesComponent 因此无需对它进行初始化 而只需从实体中获取 SkeletalPosesComponent 更新关节的旋转 需要注意的是 这是在局部空间中进行的 为了在每一帧 更新颈部旋转 我将从 RealityKit 的更新函数中 调用这段代码 RealityKit 会就每一帧 在所有注册系统上调用更新函数 并将更新的值 提交到组件中 我可以更新整个关节变换 也可以仅更新平移、 缩放或旋转 最多每帧更新一次 让我们来运行代码 看看机器人 观赏蝴蝶飞舞的样子
不知道你们怎么想 但这一刻让我心动不已
今天我们介绍了很多内容 让我们来回顾一下探讨的所有内容
我们学习了如何利用 Reality Composer Pro 的时间线功能 在时间线上对动作进行排序 并通过触发器启动这些动作 我们使用新的 Full Body Inverse Kinematics API 让机器人的手臂伸向对象 我们还学习了如何使用内置动作和 自定动作 将动作按顺序组合在一起 我们了解了如何 使用形状变形动画 为植物添加动画效果 我们还了解了如何通过旋转关节 让对象摆出各种位姿 从而将对象摆出各种位姿 并呈现出动画效果
除了这些功能之外 Reality Composer Pro 今年还推出了其他激动人心的功能 例如视频对接 部署到 iOS、macOS 和 visionOS 的能力 环境创作 以及灯光效果
要进一步了解这些主题 请观看以下讲座 “在自定环境中提升 媒体观赏体验的沉浸度” 以及“探索适用于 iOS、macOS 和 visionOS 的 RealityKit API” 这些新功能可帮助你 打造互动感十足的 3D 内容 我迫不及待地想看到 你借此构建的出色作品 感谢观看 祝你在 WWDC 度过美妙的时光
-
-
20:31 - Setup IKComponent
// Setup IKComponent import RealityKit struct HeroRobotRuntimeComponent: Component { var rig = try? IKRig(for: modelSkeleton) rig.maxIterations = 30 rig.globalFkWeight = 0.02 let hipsJointName = "root/hips" let chestJointName = "root/hips/spine1/spine2/chest" let leftHandJointName = "root/hips/spine1/spine2/chest/…/L_arm3/L_arm4/L_arm5/L_wrist" rig.constraints = [ .parent(named: "hips_constraint", on: hipsJointName, positionWeight: SIMD3(repeating: 90.0), orientationWeight: SIMD3(repeating: 90.0)), .parent(named: "chest_constraint", on: chestJointName, positionWeight: SIMD3(repeating: 120.0), orientationWeight: SIMD3(repeating: 120.0)), .point(named: "left_hand_constraint", on: leftHandJointName, positionWeight: SIMD3(repeating: 10.0)) ] let resource = try? IKResource(rig: rig) modelComponentEntity.components.set(IKComponent(resource: resource)) }
-
21:33 - Update IKComponent
// Update IKComponent import RealityKit struct HeroRobotRuntimeComponent: Component { guard let reachTarget = sceneRoot.findEntity(named: "reachTargetName") else { return } var reachPosition = reachTarget.position(relativeTo: entity) let time = sin(simTime) reachPosition.x += (20.0 + 50.0 * time) reachPosition.y += (40.0 + 30.0 * abs(time)) reachPosition.z += (20.0 + 20.0 * abs(time)) guard let ikComponent = modelComponentEntity.components[IKComponent.self] else { return } var reachPosition = reachTarget.position(relativeTo: entity) ... var leftHandConstraint = ikComponent.solvers.first?.constraints["left_hand_constraint"] leftHandConstraint?.target.translation = reachPosition // A blendValue = 0 means no influence on the constraint. // A blendValue = 1 means full influence on the constraint. var blendValue = isEnabled ? (time / totalBlendTime) : (1.0 - time / totalBlendTime) leftHandConstraint?.animationOverrideWeight.position = blendValue modelComponentEntity.components.set(ikComponent) }
-
24:36 - Sequence and play animation actions
// Play Animation Actions import RealityKit struct HeroRobotRuntimeComponent: Component { let rotateAnimationResource = createRotateAnimationResource() let walkAndMoveAnimationGroup = createWalkAndMoveAnimationGroup() let alignAtHomeActionResource = createAlignAtHomeActionResource() let robotTravelHomeCompleteActionResource = createRobotTravelHomeCompleteAction() // Build a sequence of the rotate, move and align animations/actions to play. let moveHomeSequence = try? AnimationResource.sequence(with: [rotateAnimationResource, walkAndMoveAnimationGroup, alignAtHomeActionResource, robotTravelHomeCompleteActionResource]) // Play the move-to-home sequence. _ = robotEntity.playAnimation(moveHomeSequence) }
-
25:59 - Setup EntityActions
// Setup EntityActions import RealityKit struct HeroRobotRuntimeComponent: Component { struct RobotMoveToHomeComplete: EntityAction { var animatedValueType: (any AnimatableData.Type)? { nil } } let travelCompleteAction = RobotMoveToHomeComplete() let actionResource = try! AnimationResource.makeActionAnimation(for: travelCompleteAction, duration: 0.1) let _ = robotEntity.playAnimation(actionResource) }
-
26:39 - EntityAction subscription
// EntityAction subscription import RealityKit struct HeroRobotRuntimeComponent: Component { // Subscribe to know when the EntityAction has started. RobotMoveToHomeComplete.subscribe(to: .started) { event in if event.playbackController.entity != nil { event.playbackController.stop() } } // Possible states of the robot. public enum HeroRobotState: String, Codable { case available … case arrivedHome } // Subscribe to know when the EntityAction has ended. RobotMoveToHomeComplete.subscribe(to: .ended) { event in if let robotEntity = event.playbackController.entity, var component = robotEntity.components[HeroRobotRuntimeComponent.self] { component.setState(newState:.arrivedHome) } } }
-
29:17 - Setup BlendshapeWeightsComponent
// Setup BlendShapeWeightsComponent import RealityKit struct HeroPlantComponent: Component, Codable { guard let modelComponentEntity = findModelComponentEntity(entity: entity), let modelComponent = modelComponentEntity.components[ModelComponent.self] else { return } let blendShapeWeightsMapping = BlendShapeWeightsMapping(meshResource: modelComponent.mesh) // Create the blend shape weights component. entity.components.set(BlendShapeWeightsComponent(weightsMapping: blendShapeWeightsMapping)) }
-
29:38 - Update BlendshapeWeightsComponent
// Update BlendShapeWeightsComponent struct HeroPlantComponent: Component, Codable { guard let component = entity.components[BlendShapeWeightsComponent.self] else { return } var blendWeightSet = blendShapeComponent.weightSet // Update the weights in the BlendShapeWeightsSet for weightIndex in 0..<blendWeightSet[blendWeightsIndex].weights.count { blendWeightSet[blendWeightsIndex].weights[weightIndex] = 0.0 } // Assign the new weights to the blend shape component. for index in 0..<blendWeightSet.count { component?.weightSet[blendWeightsIndex].weights = blendWeightSet[index].weights } }
-
32:01 - Setup and update Skeletal Poses
// Update Skeletal Poses import RealityKit struct StationaryRobotRuntimeComponent: Component { guard var component = entity.components[SkeletalPosesComponent.self] else { return } let neckRotation = calculateRotation() component.poses.default?.jointTransforms[neckJointIndex].rotation = neckRotation }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。