Unreal Engine 中的旋转表示:FQuat 与 FRotator 全面解析
在 Unreal Engine 的 3D 开发中,旋转(Rotation) 是最基础也最容易让人困惑的概念之一。Unreal 提供了两种主要的旋转表示方式:FRotator
(欧拉角) 和 FQuat
(四元数)。它们各有优劣,适用于不同场景。本文将系统梳理它们的定义、相互转换、使用场景及常用方法,帮助你写出更健壮、高效的旋转逻辑。
一、基本定义
1. FRotator
—— 欧拉角(Euler Angles)
- 结构:由三个
float
值组成:Pitch
(俯仰,绕 Y 轴)、Yaw
(偏航,绕 Z 轴)、Roll
(翻滚,绕 X 轴)。 - 单位:度(Degrees),不是弧度!
- 可读性:高。例如
(0, 90, 0)
表示向右转 90 度,直观易懂。 - 范围:
- 默认范围:
[-360, 360]
- 可通过
GetNormalized()
归一化到[-180, 180]
- 默认范围:
FRotator Rot(30.0f, 45.0f, 0.0f); // Pitch=30°, Yaw=45°, Roll=0°
⚠️ 注意:Unreal 的坐标系是 左手坐标系,Z 轴朝上,因此 Yaw 是绕 Z 轴旋转(与 Unity 等右手系不同)。
2. FQuat
—— 四元数(Quaternion)
- 结构:由四个
float
值组成:X, Y, Z, W
,满足X² + Y² + Z² + W² = 1
(单位四元数)。 - 本质:一种数学上更优雅的旋转表示,避免了欧拉角的“万向节死锁(Gimbal Lock)”。
- 可读性:低。
FQuat(0, 0, 0.707, 0.707)
对人类来说毫无意义。 - 优势:
- 插值平滑(Slerp)
- 组合旋转高效(四元数乘法)
- 无奇异性(不会死锁)
FQuat Quat = FQuat::MakeFromEuler(FVector(0, 90, 0)); // 从欧拉角构造
二、相互转换
1. FRotator
→ FQuat
FRotator Rot(0, 90, 0);
FQuat Quat = Rot.Quaternion(); // 推荐方式
// 或
FQuat Quat2 = FQuat(Rot); // 隐式转换(内部调用 Quaternion())
2. FQuat
→ FRotator
FQuat Quat = ...;
FRotator Rot = Quat.Rotator(); // 推荐方式
✅ 重要提示:
四元数到欧拉角的转换不是一一对应的!多个四元数可能对应同一个欧拉角(因为旋转有周期性)。因此,不要期望Quat.Rotator().Quaternion() == Quat
恒成立。
三、使用场景对比
场景 | 推荐类型 | 原因 |
---|---|---|
编辑器输入 / 蓝图暴露 | FRotator | 用户友好,直观可调 |
角色控制 / 摄像机旋转 | FRotator | 逻辑清晰(如“角色面向 Yaw 方向”) |
动画 / 物理 / 插值 | FQuat | 避免死锁,插值平滑 |
组合多个旋转 | FQuat | 四元数乘法比欧拉角叠加更准确 |
网络同步旋转 | FQuat | 数据更紧凑,无角度跳变问题 |
数学计算 / 变换矩阵 | FQuat | 与 FTransform 内部一致 |
四、常用方法与技巧
1. FRotator
常用方法
// 归一化到 [-180, 180]
FRotator Normalized = Rot.GetNormalized();// 获取方向向量(常用于角色朝向)
FVector Forward = Rot.Vector(); // 等价于 FRotationMatrix(Rot).GetUnitAxis(EAxis::X)// 旋转一个向量
FVector NewDir = Rot.RotateVector(OriginalDir);// 插值(注意:可能经过长路径!)
FRotator Interp = FMath::Lerp(RotA, RotB, Alpha);
FRotator InterpShort = FMath::RInterpTo(RotA, RotB, DeltaTime, InterpSpeed); // 自动选短路径
⚠️
FMath::Lerp
对FRotator
不会自动选择最短路径!可能绕远路。建议使用FMath::RInterpTo
或先转四元数插值。
2. FQuat
常用方法
// 从轴角构造
FQuat Quat = FQuat::MakeFromAxisAngle(FVector::UpVector, FMath::DegreesToRadians(90));// 从两个向量构造(将 From 向量旋转到 To 向量)
FQuat Quat = FQuat::FindBetweenVectors(FromDir, ToDir);// 球面线性插值(Slerp)—— 平滑旋转
FQuat Interp = FMath::QInterpTo(QuatA, QuatB, DeltaTime, InterpSpeed);// 组合旋转:先 A 再 B
FQuat Combined = QuatB * QuatA; // 注意顺序!Unreal 是右乘// 旋转一个向量
FVector NewDir = Quat.RotateVector(OriginalDir);// 获取旋转轴和角度
FVector Axis;
float Angle;
Quat.ToAxisAndAngle(Axis, Angle); // Angle 是弧度!
🔁 旋转顺序注意:
在 Unreal 中,QuatB * QuatA
表示 先应用 A,再应用 B(与矩阵乘法一致)。
五、常见陷阱与最佳实践
❌ 陷阱 1:直接对 FRotator
做 Lerp
// 错误!可能从 179° 跳到 -179°,导致旋转 358° 而不是 2°
FRotator BadInterp = FMath::Lerp(RotA, RotB, Alpha);
✅ 正确做法:
FQuat GoodInterp = FMath::QInterpTo(RotA.Quaternion(), RotB.Quaternion(), DeltaTime, Speed);
FRotator Result = GoodInterp.Rotator();
❌ 陷阱 2:混淆旋转顺序
// 想让物体先绕自身 Y 轴转,再绕世界 Z 轴转?
// 错误写法:
FQuat LocalYaw = FQuat(FRotator(DeltaYaw,0 , 0)); // Pitch,Yaw,Roll -> Y,Z,X
FQuat WorldRoll = FQuat(FRotator(0, DeltaRoll, 0));
Comp->SetRelativeRotation(WorldRoll * LocalYaw); // 顺序可能不对!
✅ 明确顺序:通常局部旋转用 SetRelativeRotation
,世界旋转用 AddWorldRotation
。
✅ 最佳实践
-
内部计算用
FQuat
,对外接口用FRotator
—— 例如:Actor 的GetActorRotation()
返回FRotator
,但内部FTransform
存储的是FQuat
。 -
插值一律转四元数
—— 避免角度跳变问题。 -
网络同步优先传
FQuat
—— 更稳定,且FQuat
可压缩(如CompressedRotation
)。 -
不要手动修改
FQuat
的 X/Y/Z/W
—— 除非你非常清楚自己在做什么,否则容易破坏单位长度。
六、引擎内部如何存储?
USceneComponent
的RelativeRotation
和ComponentToWorld
中的旋转,底层都是FQuat
。FRotator
仅作为用户友好接口存在,每次设置SetRelativeRotation(FRotator)
时,引擎会立即转为FQuat
存储。
总结
特性 | FRotator | FQuat |
---|---|---|
可读性 | ⭐⭐⭐⭐⭐ | ⭐ |
插值质量 | ⭐⭐ | ⭐⭐⭐⭐⭐ |
组合旋转 | ⭐⭐ | ⭐⭐⭐⭐⭐ |
万向节死锁 | 有风险 | 无 |
适合场景 | 用户输入、简单逻辑 | 动画、物理、数学计算 |
记住一句话:
“用FRotator
思考,用FQuat
计算。”
掌握这两者的区别与转换,你就能在 Unreal 的旋转世界中游刃有余,写出既直观又健壮的代码。