Unity 旋转欧拉角及万向锁问题

article/2025/7/21 3:35:47

先说明unity 3D欧拉角的旋转顺序(父子关系)是y-x-z。即旋转y轴x和z轴都变,旋转x轴只有z轴变化,旋转z轴其它轴不变。

模型坐标系

又称物体坐标系。

与特定的物体关联,每个物体都有自己特定的坐标系。不同物体之间的坐标系相互独立,可以相同,可以不同,没有任何联系。同时,物体坐标系与物体绑定,绑定的意思就是物体发生移动或者旋转,物体坐标系发生相同的平移或者旋转,物体坐标系和物体之间运动同步,相互绑定。

世界坐标系

是一个特殊的坐标系,它建立了描述其他坐标系所需要的参考系。也就是说,可以用世界坐标系去描述其他所有坐标系或者物体的位置。所以有很多人定义世界坐标系是“我们所关心的最大坐标系”,通过这个坐标系可以去描述和刻画所有想刻画的实体。

惯性坐标系

是为了简化世界坐标系到物体坐标系的转化而产生的。惯性坐标系的原点与物体坐标系的原点重合,惯性坐标系的轴平行于世界坐标系的轴。引入了惯性坐标系之后,物体坐标系转换到惯性坐标系只需旋转,从惯性坐标系转换到世界坐标系只需平移

坐标系之间的联系

下图展示了三个坐标系之间的关系。在机器人的物体坐标系中,y轴从脚指向头而x轴指向它的左边。绕物体坐标系的原点进行旋转。知道物体坐标系的轴与世界坐标系平行就得到了惯性坐标系。最后,把惯性坐标系的原点平移到世界坐标系的原点就完成了惯性坐标系到世界坐标系的转换。
这里写图片描述

unity模型坐标系和惯性坐标系间的查看方法

 

 local为模型坐标系

 

 global为惯性坐标系

如果你不旋转模型,此时惯性坐标系和模型坐标系重合,你可以点选上面按钮切换看一下。

编写脚本辅助类,显示惯性坐标系的辅助线。

using UnityEngine;public class GizmozHelper : MonoBehaviour
{public float Length = 3;void OnDrawGizmos(){Gizmos.color = Color.red;Vector3 direction = Vector3.right * Length; //世界坐标系的 轴向xGizmos.DrawRay(transform.position, direction);Gizmos.color = Color.green;direction = Vector3.up * Length; //世界坐标系的 轴向yGizmos.DrawRay(transform.position, direction);Gizmos.color = Color.blue;direction = Vector3.forward * Length; //世界坐标系的 轴向zGizmos.DrawRay(transform.position, direction);}
}

首先将坐标切换到模型坐标,

 

开始旋转最顶层的y轴。

效果如下图:

  会发现旋转y轴是绕着惯性坐标的y轴旋转的,而不是模型的坐标。

旋转x轴,会发现x轴的变换是绕着模型坐标的x轴进行变换的。

旋转z轴,会发现z轴的变换也是绕着模型的z轴变换。

万向锁(Gimbal lock)

一旦选择±90°作为pitch(俯仰变换)角,就会导致第一次旋转和第三次旋转等价,整个旋转表示系统被限制在只能绕竖直轴旋转,丢失了一个表示维度。

为什么会共面,因为y轴变换将影响x和z轴,因为有轴处在变换的最顶层(y-x-z),最主要的是y轴变换是模型在惯性坐标里变换,而其他轴的变换是在模型轴变换。所以就会出旋转面共面(万向锁)的情况。

下面开始说明万向锁的情况。

将模型x轴旋转90度(或者-90度)(最简单的万向锁)

紧接着旋转y轴或者z轴,效果如下:

这就是万向锁

你会发现z轴旋转和y轴旋转效果是一样的,同时你会发现z轴(模型坐标系)和y轴坐标(惯性坐标系)平行。

此时y轴(惯性坐标系)旋转面和z轴(模型坐标系)旋转面共面,

从上图很容易看出旋转y轴(惯性坐标系)和旋转z轴(模型坐标系)是一样的效果,只不过方向相反或相同。
 

总结:

万向锁产生时两个旋转轴平行(旋转面共面)使模型的旋转失去一个方向的旋转。

为什么Unity3d旋转默认采用了有万向节死锁的欧拉角,而不用四元数?

Unity的底层是通过四元数记录物体旋转的,并通过矩阵和四元数实现物体的旋转及插值。

但在上层Unity提供了,向欧拉角进行转换输出,并能够通过欧拉角进设置物体旋转的功能。

一些本通过四元数进行旋转/插值计算的方法,在上层也是通过欧拉角输入对应旋转值。

这是由于相较矩阵和四元数,欧拉角是最接近人类直观思维的一种3D旋转表达模式,应该没有人想通过输入矩阵或者四元数来旋转物体吧...

底层通过矩阵/四元数,记录完成旋转,避免万向锁,但在上层提供欧拉角的转换输出和旋转设定,这应该说是当下大多数3D引擎架构都普遍采用的一种模式。


 

此外还要提两点的:

一是,限制欧拉角;

由于存在 四元数--->欧拉角 这样的底层到上层的转换输出,因而就需要引入限制欧拉角规则。

这是由于三角函数是一个周期函数,因而我们进行反三角函数运算的时候,要将输出值限制在一定的范围内,否则会有无穷多解。

其实这也是由于欧拉角的一大缺点之一,因为±360度角度值相同,导致对于同一3D方位/角位移会有无数种表示。

Unity中的欧拉角采用 Y(Heading)-->X(Pitch)-->Z(Bank) 的轴顺序,对应的限制规则,是将Y和Z轴的旋转数值限制在[-180,180],中间X轴旋转数值限制在[-90,90]。

总之就是内外转轴360度全域,中间转轴只有180度半域。

这里给出指定欧拉角的旋转矩阵:

 通过m32 = -sinP;

那么Pitch X轴的转值  P = asin(- m32)

之后对于Heading Y轴的转值H

 从而

 同理,对于Bank Z轴的转值B

 

从而

注意以上的计算的前提是在Pitch 转值不等于 ±90度,内外转轴不重合时能够成立,否则,我们解 Heading 和 Bank旋转所用到的 m12 m22 m31 m33 将全部为0,无法正常求解。

限制欧拉角规则就已经包含在了转换方法中,因为asin的值域是[-pi/2,pi/2],而atan2的值域是[-pi,pi]。

所以通过transfrom.eulerAngle获取到的欧拉角值就是经过限制后的值。

transfrom.eulerAngle.y/z 直接输出值是在 [0,360]。

transfrom.eulerAngle.x 输出值是 [0,90]U[270,360]。

如果你进行±360度,映射回[-180,180],Y和Z轴转值正好填满[-180,180]而,X轴就变为了[-90,90]。

总归是YZ 360度全域,X只有180度半域。

这种限制欧拉角就导致了一些情况下,transfrom.eulerAngle获取到的旋转角,尤其是针对X轴和预期的有些出入。

并且特别提醒,transfrom.eulerAngle获取到的值,和你在Inspector窗口的Transfrom组件中看到的旋转数值又是不一样的,Unity编辑器又经过了额外的封装。

 没错,transfrom.eulerAngle和这里是不一样的,这里是Unity编辑器又经过一次封装产生的效果,允许超限角度的显示输出和设定。

二是,Unity对万向锁的两个处理;

虽然底层通过四元数记录完成旋转,但由于提供了transfrom.eulerAngle属性,因而需要对通过该属性设置旋转角,可能遇到的万向锁时的情况进行相应的处理。

首先是阻止通过transfrom.eulerAngle角度设定插值,使物体转过万向锁的位置(像是有一道锁阻止了物体旋转)。

这是因为Unity中已经通过四元数进行了旋转的记录和完成,因而不提供欧拉角的旋转插值过渡的计算方法。

比如这段代码通过欧拉角来使物体发生绕X轴的旋转:

using UnityEngine;public class Rotate : MonoBehaviour
{// Start is called before the first frame updatevoid Start(){}// Update is called once per framevoid Update(){gameObject.transform.eulerAngles = gameObject.transform.eulerAngles + new Vector3(30 * Time.deltaTime, 0, 0);}
}

其次是,当Pitch正好为±90度时,cosP = 0, sinP = ±1,带入上述矩阵可得:

此时若想解出 Heading Y轴 和 Bank Z轴的旋转量

根据上述推导出的

H = atan2(m31 m33)

B = atan2(m12 m22)

在这种情况下是解不出来的,因为 m31 m33 m12 m22 都变成0了

而矩阵中另外四个分量中,要么只有 H-B 项,要么只有 H+B项,sin和cos的值是相互关联的。

等式/方程就一个,要解的未知数却有两个,就会有无穷多个解,

我们不得不设 Heading 或 Bank 的其中一个为0,才能解出另外一个。

在这种情况下,又出现了欧拉角不唯一的问题,不所以得不令其中一个为0,将旋转完全用另一方表示。

Unity中,是将Z轴(Bank)旋转归0,从而推导出Y轴(Heading)的旋转值。

此时如果我们通过欧拉角旋转Z轴,就会产生类似旋转实际被应用到Y轴上的效果。

 编辑器直接设置X轴旋转值为90度,来到发生万向锁位置,Z轴转置归0,输出Y轴旋转数据。此时我们通过编辑器进行Z轴旋转,可以看到transfrom.eulerAngle.z始终为0,而Y轴旋转数值发生变化,旋转可以看作被应用到了Y轴上。

如何应对万向锁

通过矩阵与四元数实现旋转

一个方法就是不要使用欧拉角来记录旋转,完成旋转的动画差值,而是在底层通过四元数/矩阵来记录/完成旋转

注意!!!这需要我们完全按照矩阵变换/四元数变换,来考虑旋转和旋转的差值,一旦我们尝试将一个欧拉角的旋转,转换到矩阵/四元数,或是尝试从矩阵/四元数反解对应的欧拉角,就一定会出现正旋反解问题。

建议使用四元数,因为四元数有 sphereLerp 方法,对旋转能够实现无任何限制的最短球面差值。

记得在上层的API,我们仍需要给出,对欧拉角进行转换输出/设置的方法。

一些旋转方法的上层调用,也应是通过欧拉角,或者轴-角式,来设置旋转量。

因为相比于矩阵/四元数,欧拉角是最接近人类直观思维的一种旋转表达模式。

可以参考一下Unity,底层通过四元数记录实现旋转,但上层却通过欧拉角的形式表现。

使用特定的轴嵌套顺序

我们可以用特定的欧拉角轴嵌套顺序,将发生万向锁的位置放到不常用的位置上,只要中间的转轴旋转值不接近±90度,欧拉角在记录表现动画差值时就是正常的。

Unity中所偏好的,左手系,Heading Y轴 --> Pitch X轴 --> Bank Z轴,就是给摄像机设计的轴嵌套方案。

它的万向锁位置被放在了摄像机冲向正上方和正下方,对于摄像机而言,很少会有对这两个角度拍摄的情况。

另一种左手系下,给飞行器设计的轴嵌套方案是 Pitch X轴 ---> Yaw Y轴 ---> roll Z轴。

它的万向锁位置是被放在了飞行器冲向正左和正右的时候。

相比于摄像机,飞行器经常需要进行X轴的旋转,起飞时的拉起就是转X轴,甚至需要殷麦曼翻转这样的机动动作。

而冲向正左正右的情况,实际并不会发生,因为目前人类的飞行器还无法做到直角转弯,在转弯时必然留有一定的转弯半径进行盘旋。


http://chatgpt.dhexx.cn/article/gs9sKkT8.shtml

相关文章

万向锁的理解 欧拉角 四元数

1 欧拉角&万向锁 欧拉角和万向锁 视频资料理解万向锁 核心是嵌套 以一个三自由度的机械臂为例,三个旋转轴互相垂直,相当于x,y,z轴 三个机械臂通过旋转关节依次串联,定义第一个关节旋转轴为J1,以此类推第二第三个关节旋转轴为…

简单研究Unity中的万向锁和欧拉角以及四元数

欧拉角是欧拉在17世纪发明引进的一个数学工具,在三维欧几里得空间内,欧拉角可以确定一个物体的朝向。在解决静态问题上,欧拉角是一个比较完美的解决方案,但在动态问题上,欧拉角有一个万向锁的瑕疵,数学界在…

Unity万向锁

结论: 当第二轴旋转90时会产生万向锁,万向锁只发生在程序中,现实世界不会发生。 发生万向锁的底层原因是由于程序中会指定旋转顺序,且前后顺序为父子包含关系。 旋转顺序:因为同样一组数据,例如{X:10&…

欧拉角与万向锁—深度解读万向锁

文章目录 问题描述:Z轴不生效(轴向重合)轴向为什么会重合初态二次变换总结:解决办法: 相信很多人听说过欧拉角与万向锁,主要问题是欧拉角为什么会产生万向锁? 有矩阵解读法,但是不够…

关于万向锁(Gimbal lock)问题的理解及解决

有时间更新一下理解,现在回过头来看写的东西真是垃圾哈哈哈哈哈~~~~ 1. Gimbal lock的理解 万向锁(Gimbal lock): 一旦选择90作为pitch角,就会导致第一次旋转和第三次旋转等价,整个旋转表示系统被限制在只…

详解万向锁

最近在看高翔老师的slam十四讲时,对万向锁这个概念不是很清晰,于是从网上查了不少博客文档,最终明白了万向锁这个概念。 在此记录一下。 解释万向锁之前首先明白一下什么是欧拉角。 什么是欧拉角? 用一句话说,欧拉角…

java uuid多少位_UUID用在MySQL中,性能到底好不好?

如果你在网上快速的做一个关于 UUID 和 MySQL 的搜索,你会得到相当多的结果。以下是一些例子: 存储 UUID 和 生成列在 MySQL 中存储 UUID 的值说明 InnoDB 中的主键模型及其对磁盘使用的影响主键选型之战 UUID vs. INTGUID / UUID 的性能突破到底需不需要…

UUID 全世界的唯一id!(唯一的接口标识符)

UUID(Universally Unique IDentifier)是一个128位数字的唯一标识。RFC 4122描述了具体的规范实现。本文尝试从它的结构一步步分析为什么它能做到唯一性?及各个版本的使用场景。 Format UUID使用16进制表示,共有36个字符(32个字母数字4个连接符"-&…

瓦片地图加载

OpenLayers之加载多源数据四:万能瓦片地图加载秘籍 不睡觉的怪叔叔 2018-08-02 13:46:59 7175 收藏 17 分类专栏: 开源GIS 目录 加载简单的瓦片地图 瓦片地图之高德地图 瓦片地图之百度地图 重新定义OpenLayers3的瓦片坐标系 分析瓦片地图坐标系…

QGIS 上加载瓦片地图(高德、ESRI、OSM等)

1. 在QGIS左侧的浏览面板中,选择XYZ Tiles节点,右键新建连接。在弹出的窗口中填入名称和瓦片地图的网址http://webrd02.is.autonavi.com/appmaptile?langzh_cn&size1&scale1&style7&x{x}&y{y}&z{z} ,保存。 2.将浏览…

QGIS二次开发:加载XYZ Tiles形式的瓦片地图

1.前言 QGIS官方提供的QGIS.exe可以加载远程地图提供商提供的基于URL格式的XYZ Tiles形式的瓦片地图。如:https://tile.openstreetmap.org/{z}/{x}/{y}.png ,开发者如果不能联网,也可以创建离线地图服务。如下:打开QGIS 3.26.2.exe,选择xyz Tiles节点,单击右键,选择“N…

Vue Mapbox 发布高德瓦片地图

一、需求来源 公司网站开发需求里需要做一个大屏看板,包含地图的功能,而且以后是要在内网使用的,也就是说不能使用在线网络。我在研究了(踩了一万个坑!!!)多个地图组件以后&#xff…

瓦片地图的入库

数据: 瓦片地图 数据库: MySQL 语言 : Java 方法: 用Java语言把瓦片地图以二进制的方式导入数据库 瓦片地图的存储格式 当我们用ArcMap切完瓦片之后, 它的存储方式是以级数,行列号的方式存储在文件夹中 级数: 行号: 列号: 一般的存储结构在数据库中存储它的 级数,行号,列…

各种瓦片地图

概述 研究了一阵子的地图,太过深奥了。写了一个小程序,可以下载瓦片地图。下面是各种瓦片图例。 瓦片地图 微软瓦片地图 图1 图2 谷歌瓦片地图 图3 图4 osm瓦片地图 图5 图6 天地图瓦片地图 图7 图8 水瓦片地图 图9 图10 后续 还…

geoserver发布TIF格式瓦片地图

一,准备tif地图 可以通过全能电子地图下载器获取地图(软件可以去淘宝下载) 二,geoserver发布tif 在geoserver安装文件夹中双击“startup.bat”打开服务(注意:为了防止端口占用,我这里更改了默…

Unity 瓦片地图

一些常见的使用类似方法绘制地图的游戏: 泰拉瑞亚: 如果有老哥玩过泰拉瑞亚的地图编辑器,其实unity瓦片地图和泰拉瑞亚地图编辑器手感类似 蔚蓝: 大名鼎鼎的蔚蓝也是如此 Untiy中制作2D俯视角游戏,往往需要使用瓦片地图…

瓦片地图下载工具

收费工具,学生党勿扰,白嫖党勿扰 收费金额:200元 MyChat:wangjianjun1018 瓦片地图下载工具 概述操作查看地图 下载链接说明后续 概述 最近花费一点时间,研究瓦片地图。在网上找了好久,有好多下载的提供商…

百度个性化瓦片地图下载

百度个性化瓦片地图下载 因为近期项目需求,客户项目在内网使用,不连接外网,因此地图不能使用在线地图,必须使用离线地图。 离线地图使用在之前的博客写过,就不在累述了。https://blog.csdn.net/weixin_42776111/arti…

Tilemap瓦片地图

可以用Tilemap工具来构建游戏世界的地图,创建瓦片地图时,Grid组件自动作为瓦片地图的父级,相比于传统使用照片搭建地图的方式,使用瓦片地图用来搭建地图可以更加迅速,而且使用传统方式搭建地图时,需要添加大量的碰撞体…

leaflet加载离线瓦片地图

首先我们要明白瓦片地图的请求原理。 其实瓦片地图并不是什么特殊的文件,就是最普通的png图片。之所以为地图,就是带有了该图片按规则组织,绘制时,按规则拼图组装而已; 打开network,查看其请求的的url就能…