在本教程中,我们将学习 Unity 中 Quaternion 的 Slerp 函数。该函数允许我们在指定时间内,从一个初始朝向过渡到一个最终朝向,实现平滑旋转。


本教程的视频版本目前尚未提供该语言版本。


视频文字稿

大家好!

在本教程中,我们将学习 Unity 中 Quaternion 的 Slerp 函数。该函数允许我们在指定时间内,从一个初始朝向过渡到一个最终朝向,实现平滑旋转。

在本教程中,我默认大家已经熟悉 Time.deltaTime。我曾在之前发布的视频教程中讲解过这个内容。如果你还不熟悉 Time.deltaTime,我强烈建议你在继续本教程之前先观看那一节视频。

本教程使用 Unity 2022 进行录制。不过,Slerp 函数在多个版本中都可以使用,并且很可能会在未来版本中继续保留。

从朝向 A 旋转到朝向 B,也就是更一般地说,在指定时间内在数值 A 和数值 B 之间进行插值,可以通过多种方式实现。数值可以按照线性变化、球面变化,或其他类型的变化方式进行插值,包括自定义方式。

在视频中,我展示了一些插值示例,例如让一个立方体围绕其竖直轴旋转 90 度。这是一个简单的示例,但能够有效说明这个概念。

旋转时间被映射到区间 [0,1]。在时刻 0 时,我们得到初始值;在时刻 1 时,我们得到最终值。区间中的中间值对应所选插值方式计算出的结果。

在这些示例中,所有立方体都在 5 秒内旋转 90 度。不过,在动画的第 4 秒,也就是区间 [0,1] 中的 0.8 时刻,由于插值方式不同,各个立方体的朝向会有所不同。

在进行旋转时,就像我们的例子一样,通常更推荐使用球面插值而不是线性插值,因为效果更加自然和美观。

在 Unity 中,球面插值通过 Slerp 函数计算,其中字母 S 代表 Spherical;而线性插值通过 Lerp 函数计算,其中字母 L 代表 Linear。

正如前面所说,Slerp 属于 Quaternion 类。Quaternion 用于通过四元数在三维空间中表示朝向和旋转。这是一个数学概念,本教程中不会对其进行讲解。

我只说明它们用于表示朝向,并向大家展示如何使用 Slerp 函数。

Slerp 用于从朝向 A 过渡到朝向 B。

它有三个参数:初始朝向、目标朝向,以及区间 [0,1] 中的某个时刻值,也就是我们希望获取插值结果的时刻。

Slerp 返回一个四元数,表示在指定时刻计算得到的朝向。

因此,当我们写:

Quaternion.Slerp(A, B, 0.2);

这表示我们要求 Unity 返回在从朝向 A 过渡到朝向 B 的旋转过程中完成五分之一时,物体应具有的朝向,因为 0.2 是 1 的五分之一。

如果我们希望动画持续 1 秒,可以在 Update 中使用 Time.deltaTime。因为区间从 0 到 1 的插值范围,可以通过在 Update 中使用 Time.deltaTime 精确转换为 1 秒的动画,我在关于 Time.deltaTime 的视频教程中已经演示过这一点。

如果我们希望动画持续任意秒数,则应将 Time.deltaTime 除以该秒数。稍后我会通过一个实际示例更详细地说明这一点。

大多数情况下,初始朝向就是物体当前的朝向,也就是 transform.rotation,因为旋转以四元数形式表示。

计算目标朝向,例如在初始朝向上增加一定角度,可能会有些复杂。不过,我们很快会在实际示例中解决这个问题。现在让我们进入实际示例。

在视频中,我展示了一个包含 3D 模型的场景。

具体来说,该模型包含 4 个元素:门框、两扇门板以及一个门把手。

虽然在我原始模型中,带把手的门板的枢轴点最初位于铰链处,因此旋转可以正确进行,但由于某种原因,Unity 将它稍微向前移动了一些。

为了解决这个问题,我在门框下创建了一个 Empty 对象作为子对象,并将其放置在需要动画的门板的一个铰链位置。

然后,我将门板设为该 Empty 的子对象。

这个新的 Empty 对象需要一个旋转脚本,我们现在就创建它,例如命名为 doorOpening。

我们希望门板在游戏开始时自动打开,围绕其本地竖直轴旋转 90 度。在本例中,该轴是 Z 轴。

初始朝向由 transform.rotation 给出。

为了方便和提高代码灵活性,我声明一个名为 startRotation 的 Quaternion 变量,并在 Start 中初始化:

startRotation = transform.rotation;

表示最终朝向的四元数应等于初始朝向加上绕本地 Z 轴旋转 90 度的结果。那么,如何用 Quaternion 定义这个旋转呢?

Quaternion.Euler 函数可以帮助我们。它有三个参数,分别表示绕 XYZ 三个轴的旋转角度,单位为度。

因此,我们定义要执行的旋转四元数为:

Quaternion rotationToBePerformed;

并在 Start 中初始化为:

rotationToBePerformed = Quaternion.Euler(0f, 0f, 90f);

这个四元数表示要执行的旋转操作,但它还不是最终朝向。

我们需要将这个旋转应用到初始朝向上来计算最终朝向。

这里有一个关键点:要将要执行的旋转添加到初始朝向上,两个四元数必须相乘。

因此,我们定义最终四元数为:

Quaternion targetRotation;

并在 Start 中初始化为:

targetRotation = startRotation * rotationToBePerformed;

接下来进入 Update 函数,该函数在每一帧调用。我们写下:

transform.rotation = Quaternion.Slerp(startRotation, targetRotation

此时先停下来,因为我们还没有定义插值评估时间的变量。

该变量应从 0 开始。因此,我们在 Update 外部定义并初始化:

float evaluation = 0f;

然后将它作为 Quaternion.Slerp 的第三个参数。

此外,由于它需要在每一帧增加,我们在 Update 中写:

evaluation += Time.deltaTime;

我们知道最终的评估时间是 1.0,因此该动画将精确持续 1 秒,因为每一帧都通过 Time.deltaTime 增加 evaluation。

现在运行游戏,我们会发现 Empty 围绕其竖直轴旋转,但门板并未打开。

这是因为我最初将该对象设置为 Static,因为当时不打算对其进行动画处理,并希望 Unity 计算全局光照。但现在需要对其进行动画,因此我在 Inspector 中取消勾选 Static,并确认对子对象也执行此操作,例如门把手。

再次运行游戏,这一次我们会看到门在 1 秒内以平滑插值方式打开。

现在如何定义不同的动画时长呢?

答案很简单。每一帧中 evaluation 的增量必须等于 Time.deltaTime 除以动画持续时间。

当仅使用 Time.deltaTime 时,动画持续 1 秒。因此,如果希望动画持续 5 秒,则需要在每一帧将 evaluation 增加 Time.deltaTime 的五分之一。

我们定义一个变量:

float openingDuration = 5f;

因为希望动画持续 5 秒。然后在 Update 中将 evaluation 的更新语句修改为:

evaluation += (Time.deltaTime / openingDuration);

保存脚本,返回 Unity 编辑器并运行游戏观察结果。这一次动画将在 5 秒内完成。

通过这个实际示例,你可以看到如何使用 Quaternion.Slerp 在指定时间内平滑地在两个朝向之间进行插值。

你可以将此脚本应用到不同场景或对象,以在 Unity 项目中实现平滑旋转和动画效果。

总结一下,本教程介绍了以下内容:如何用四元数表示要执行的旋转;如何获得目标四元数,也就是起始四元数与要执行旋转相乘的结果;如何使用 Slerp 函数进行球面插值旋转;如何借助 Time.deltaTime 在不同帧率下保持恒定速度,并实现可变时长的动画。

希望本教程对你有所帮助。我们下次再见!

本网站仅用于展示我的部分作品,不具有任何推广或商业目的。请注意,目前我不接受,也不会回复任何定制项目、咨询服务或其他形式的合作请求。


高级隐私策略信息和cookie文件的使用