news 2026/10/17 5:36:16

欧拉角实现原理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
欧拉角实现原理

一句话:欧拉角就是把任意一个旋转,拆成"绕 3 根轴依次转 3 次"。记录的只是 3 个角度,真正旋转物体时,会把它们变成 3 个旋转矩阵按顺序相乘。

还是按直觉 → 原理 → 代码 → 坑的顺序,尽量讲简单。


一、直觉:飞机的三个动作

想象你在开飞机,所有姿态都能由三个动作组合出来:

动作英文绕哪根轴Unity 中
左右转头(机头左右摆)Yaw 偏航竖直轴Y
抬头低头(机头上下摆)Pitch 俯仰左右轴X
左右翻滚(机翼一高一低)Roll 翻滚前后轴Z
Y(偏航) ↑ │ │ ●──────→ X(俯仰) ╱ ╱ ↙ Z(翻滚,机头方向)

Inspector 里 Rotation 显示的(30, 45, 0),就是"绕 X 转 30°、绕 Y 转 45°、绕 Z 转 0°"。

优点:人类非常好理解,美术、策划直接填数字就行。


二、原理第一步:绕一根轴怎么转?

1. 先看 2D:平面上转一个点

一个点(x, y)绕原点转 θ 角,新位置是:

x' = x·cosθ − y·sinθ y' = x·sinθ + y·cosθ

不用死记,看两个特殊点就懂了:

y ↑ (−sinθ, cosθ) ● ● (cosθ, sinθ) ← (1,0) 转 θ 后到这里 ╲ │ ╱ ╲ │ ╱ θ ╲ │ ╱ ──────────●───────────→ x
  • (1, 0)转 θ 后到了(cosθ, sinθ)
  • (0, 1)转 θ 后到了(−sinθ, cosθ)

任意点(x, y)= x 个(1,0)+ y 个(0,1),旋转后就是 x 个新位置 + y 个新位置,展开就是上面的公式。

2. 推广到 3D:绕哪根轴,哪根轴的坐标不变

绕 Z 轴转,z 坐标不动,只有 x、y 在平面里转——就是上面的 2D 旋转。其他两根轴同理:

绕 X 轴(x 不变) 绕 Y 轴(y 不变) 绕 Z 轴(z 不变) ┌ 1 0 0 ┐ ┌ cos 0 sin ┐ ┌ cos −sin 0 ┐ │ 0 cos −sin │ │ 0 1 0 │ │ sin cos 0 │ └ 0 sin cos ┘ └ −sin 0 cos ┘ └ 0 0 1 ┘ Rx Ry Rz

看规律:不变的那根轴所在的行和列是 1 和 0,剩下 4 个格子就是 2D 旋转。


三、原理第二步:三次旋转按顺序合并

1. 顺序非常重要

拿起手机试一下(屏幕朝你):

方案 A:先绕 X 转 90°(向后倒,屏幕朝天)→ 再绕 Y 转 90° 方案 B:先绕 Y 转 90°(屏幕朝右) → 再绕 X 转 90°

两次动作完全一样,只是顺序反了,手机最终的朝向完全不同。

数学上也一样:矩阵乘法不满足交换律,Rx × Ry ≠ Ry × Rx。

所以每个引擎都必须规定一个固定顺序。

2. Unity 的顺序

Unity 文档的说法:先绕 Z,再绕 X,最后绕 Y(绕的是世界坐标轴)。写成矩阵:

R = Ry × Rx × Rz ↑ 最右边的最先作用在点上 新位置 = Ry × Rx × Rz × 原位置

为什么这么选?因为它等价于按飞机自身的轴理解成:先转头(Y)→ 再抬头(X)→ 最后翻滚(Z)。这最符合角色和相机的操作习惯:先决定朝哪个方向,再决定抬头多少。

引擎 / 软件默认顺序(各家描述方式不同)
UnityZXY(世界轴)= Y→X→Z(自身轴)
UnrealYaw-Pitch-Roll
Maya / Blender默认 XYZ,可在属性里切换

从一个软件导出动画到另一个软件,旋转"全乱了",很多时候就是顺序不一致。

3. 代码:手写欧拉角转矩阵(和 Unity 结果一致)

usingUnityEngine;publicstaticclassEulerMath{staticMatrix4x4RotX(floatdeg){floatr=deg*Mathf.Deg2Rad,c=Mathf.Cos(r),s=Mathf.Sin(r);varm=Matrix4x4.identity;m.m11=c;m.m12=-s;m.m21=s;m.m22=c;returnm;}staticMatrix4x4RotY(floatdeg){floatr=deg*Mathf.Deg2Rad,c=Mathf.Cos(r),s=Mathf.Sin(r);varm=Matrix4x4.identity;m.m00=c;m.m02=s;m.m20=-s;m.m22=c;returnm;}staticMatrix4x4RotZ(floatdeg){floatr=deg*Mathf.Deg2Rad,c=Mathf.Cos(r),s=Mathf.Sin(r);varm=Matrix4x4.identity;m.m00=c;m.m01=-s;m.m10=s;m.m11=c;returnm;}// Unity 的顺序:R = Ry * Rx * RzpublicstaticMatrix4x4EulerToMatrix(Vector3e){returnRotY(e.y)*RotX(e.x)*RotZ(e.z);}}// 验证:两者结果应该相同// Matrix4x4 a = EulerMath.EulerToMatrix(new Vector3(30, 45, 60));// Matrix4x4 b = Matrix4x4.Rotate(Quaternion.Euler(30, 45, 60));

四、反过来:从旋转矩阵算回欧拉角

把Ry × Rx × Rz乘开(cx 表示 cos(x),sx 表示 sin(x),依此类推),挑出几个简单的格子:

m12 = −sx → 只和 x 有关 m02 = sy·cx, m22 = cy·cx → 相除后只剩 y m10 = cx·sz, m11 = cx·cz → 相除后只剩 z

于是:

publicstaticVector3MatrixToEuler(Matrix4x4m){floatx=Mathf.Asin(Mathf.Clamp(-m.m12,-1f,1f));floaty=Mathf.Atan2(m.m02,m.m22);floatz=Mathf.Atan2(m.m10,m.m11);returnnewVector3(x,y,z)*Mathf.Rad2Deg;}

注意看:算 y 和 z 时,分子分母都带着cx。当x = ±90°时cx = 0,就变成Atan2(0, 0)——无法确定 y 和 z。这就是下面要讲的万向节死锁,在数学上露出的马脚。


五、欧拉角最大的问题:万向节死锁(Gimbal Lock)

1. 直觉

还是开飞机:把机头拉到正上方(俯仰 90°)。

↑ 机头朝天 ┃ ┃ ═╋═ 机翼 ┃

这时候:

  • “左右转头”(绕竖直轴)→ 飞机原地绕着朝天的机身打转
  • “左右翻滚”(绕机身轴)→ 飞机原地绕着朝天的机身打转

两个操作效果一模一样!三个旋转轴里有两个重合了,原本 3 个自由度只剩 2 个。此时无论怎么调 Y 和 Z,都没法让机头往"侧面"偏,这个方向的旋转被"锁死"了。

2. 数学上的表现

当 x = 90° 时,Ry(a) × Rx(90°) × Rz(b)乘出来的结果只和 a、b 的组合有关(一个加一个减),比如(90, 30, 0)和(90, 0, -30)是同一个姿态。两个参数,只控制了一个效果。

3. 在游戏里会造成什么

现象场景
相机抬头到顶时突然乱转、翻转自由视角相机没有限制俯仰角
动画在某帧突然"甩一圈"骨骼动画用欧拉角插值,经过 90° 附近
飞行游戏竖直爬升时操作失灵飞机姿态用欧拉角存储

注意:死锁不是"物体真的卡住了",而是"用欧拉角这种描述方式表达不了某些连续变化"。物体本身完全可以那样转,只是这 3 个数字说不清楚。


六、欧拉角的其他坑

1. 同一个姿态,有多种写法

transform.eulerAngles=newVector3(180,0,180);Debug.Log(transform.eulerAngles);// 可能输出 (0, 180, 0)

(180, 0, 180)和(0, 180, 0)是完全相同的朝向。Unity 内部用四元数存储,读回欧拉角时只会给出其中一种,所以写进去的值和读出来的值可能不一样。

2. 插值会"绕远路"

从 350° 转到 10°,实际只差 20° 直接 Lerp(350, 10) → 350 → 300 → ... → 10,倒着转了 340° ❌

3. 不要单独累加读回的分量

// ❌ 错误:读回的 x 可能跳变(例如超过 90° 后被换成另一种写法),导致抖动Vector3e=transform.eulerAngles;e.x+=1f;transform.eulerAngles=e;

七、实际项目中怎么用

原则:欧拉角用来"给人看、给人填",计算和存储交给四元数。

场景推荐做法
Inspector 填旋转、配置表填角度欧拉角 ✅
物体持续旋转transform.Rotate(...)或四元数相乘
两个朝向之间平滑过渡Quaternion.Slerp/RotateTowards
朝向某个目标Quaternion.LookRotation
第一/第三人称相机自己保存yaw、pitch 两个变量,限制 pitch,再组合

标准的鼠标视角相机写法

usingUnityEngine;publicclassMouseLook:MonoBehaviour{publicfloatsensitivity=3f;floatyaw,pitch;// 自己维护,不从 transform 读回voidUpdate(){yaw+=Input.GetAxis("Mouse X")*sensitivity;pitch-=Input.GetAxis("Mouse Y")*sensitivity;pitch=Mathf.Clamp(pitch,-89f,89f);// 不到 90°,避开死锁transform.rotation=Quaternion.Euler(pitch,yaw,0f);}}

这段代码同时避开了三个坑:

  1. 角度自己存,不读回eulerAngles→ 不会出现写法跳变
  2. 俯仰限制在 ±89° → 不会进入万向节死锁
  3. 最终转成四元数赋值 → 计算稳定

一句话总结

欧拉角 = 绕 3 根轴依次旋转的 3 个角度。实现上把每个角度变成一个旋转矩阵(绕哪根轴,哪根轴不动),再按固定顺序相乘(Unity 是Ry × Rx × Rz),顺序不同结果就不同。

它直观好用,但当中间那次旋转到90°时,另外两根轴会重合,丢失一个自由度,这就是万向节死锁。所以实际项目里:人填欧拉角,程序算四元数。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 5:36:14

小世界网络+多智能体:如何用AI头脑风暴挖掘AI风险?

1. 从标题拆解入手:这个项目到底在做什么“Small-world Networks of Agents Brainstorm AI Risks to Support Ideation”这个标题,第一次读会觉得有点绕。拆开来看,核心其实就三块:小世界网络、智能体头脑风暴、AI风险构思支持。翻…

作者头像 李华
网站建设 2026/10/17 5:34:33

Codex AI编程智能体详解:从安装配置到接入DeepSeek及报错排查

最近这半年,AI 编程圈子里热度涨得最快的名字,除了 Claude Code,就是 Codex。我后台收到的私信里,一大半都在问:Codex 到底怎么装、怎么登录、怎么接入 DeepSeek、为什么一跑就报错。正好 VideCoding 这个系列第一期&a…

作者头像 李华
网站建设 2026/10/17 5:34:23

无虚拟化环境下安装Docker:Windows容器与远程主机两种方案

如果你在一台硬件虚拟化没开启的 Windows 机器上装过 Docker Desktop,大概率会遇到一个很尴尬的场景:安装界面一切正常,跑起来之后各种“虚拟化已禁用”的提示就冒出来了。尤其是公司的老办公机、部分云主机里的 Windows 系统,或者…

作者头像 李华
网站建设 2026/10/17 5:34:23

图转PPT:绕过AI语义瓶颈的高保真交付新路径

1. 为什么“AI一键生成PPT”至今没人敢说真正落地?“用AI一键生成PPT”这八个字,过去三年在办公软件发布会、自媒体标题、销售话术里高频出现,几乎成了效率工具的标配宣传语。但如果你真把这句话当真——打开某款标榜“输入一句话&#xff0c…

作者头像 李华
网站建设 2026/10/17 5:34:18

Kafka接入AI实战指南:三条落地路径与工程架构解析

1. Kafka“接入AI”:这到底是个噱头,还是一条真实的技术路线最近圈子里聊得最凶的一句话就是“Kafka已正式接入AI”。先说结论:这确实不是一个营销话术,而是过去半年里我在多个项目里真金白银踩出来的一条落地路径。但很多朋友第一…

作者头像 李华