四元数到达万向节锁

Posted

技术标签:

【中文标题】四元数到达万向节锁【英文标题】:Quaternion reaching gimbal lock 【发布时间】:2015-04-30 20:33:47 【问题描述】:

为了在执行旋转时避免角度锁定,我尝试切换到四元数。不知何故,我仍然设法达到云台锁定。

我不确定这是因为我实现了数学还是设计错误,所以请指出我是否应该改变我的对象坐标方法。

我的每个对象都有一个 X、Y、Z 值和一个俯仰、偏航、滚动值。 当我更改旋转值时,对象会根据上述信息重新计算其顶点。这个逻辑如下:

    // vertex array
    vertices[x] -= /*Offset by origin point*/;

    // Quat.'s representing rotation around xyz axes
    Quaternion q1 = Quaternion(glm::vec3(1,0,0),pitch);
    Quaternion q2 = Quaternion(glm::vec3(0,1,0),yaw); 
    Quaternion q3 = Quaternion(glm::vec3(0,0,1),roll); 

    // total rotation
    Quaternion TotalRot = ( (q3 * q2) * (q1) ); 

    // conversion of original coordinates to quaternion
    Quaternion Point1 = Quaternion(0, vertices[x].x(), vertices[x].y(), vertices[x].z()); 

    // resulting rotated point
    Quaternion Point2 = Quaternion( (TotalRot * Point1) * TotalRot.inverse() );

    // placing new point back into vertices array
    vertices[x] = QVector3D(round(Point2.v.x),round(Point2.v.y),round(Point2.v.z));
    vertices[x]+= /*Undo origin point offset*/;

"vertices[]" 是对象的顶点数组。上面注释掉的原点偏移只是为了使对象围绕正确的原点旋转,因此它相对于 0,0,0 偏移,因为围绕该点发生旋转(对吗?)。

我有我的问题的图形表示,我首先偏航 90,俯仰 45,然后滚动 -90,但滚动轴变得平行于俯仰轴:

编辑:

我尝试将这 3 个轴四元数相乘,然后乘以 4x4 矩阵,然后乘以我的​​顶点,但我仍然万向节锁定/到达奇点!

    Quaternion q1 = (1,0,0,pitch);
    Quaternion q2 = (0,1,0,yaw);
    Quaternion q3 = (0,0,1,roll);
    Quaternion qtot = (q1*q2)*q3;
    Quaternion p1(0, vertices[x].x(), vertices[x].y(), vertices[x].z());
    QMatrix4x4 m;
    m.rotate(qtot);
    QVector4D v = m*p1;
    vertices[x] = QVector3D(v.x(),v.y(),v.z());

【问题讨论】:

【参考方案1】:

您的问题是,即使您使用四元数,您仍然存储三个俯仰、偏航和滚动值,而不是四元数来表示对象的方向。

这里是你应该如何使用四元数进行旋转:

    不是为每个对象存储 X、Y、Z、俯仰、偏航、滚动,而是存储 X、Y、Z、orientation 在每个对象中,orientation 是从初始值 (0, 0, 0, 1) 开始的四元数,表示没有旋转。为每个对象存储俯仰、偏航和滚动容易受到奇点(万向节锁定)的影响,因为添加小的变化时,中间旋转之一(例如,俯仰)可能导致对象平行于旋转轴(例如, yaw 轴),这样下一次围绕该轴的旋转可能会失败。

    然后,当对象旋转时,确定该对象在该帧期间发生的俯仰、偏航和滚动(假设您的输入设备以该形式提供旋转),将其转换为四元数,然后将该四元数预乘到对象的orientation 四元数中。这种方法不太容易受到奇点的影响,因为预计每帧旋转的变化非常小。

    不要在更改方向后直接修改对象的 X、Y 和 Z(您的 verticies 数组)。相反,当对象的方向发生变化时,创建一个新的旋转矩阵作为对象世界变换矩阵的一部分(以及缩放和平移;为获得最佳结果,将世界变换计算为translation * rotation * scaling)。

    李>

    每隔几帧,您应该对 orientation 四元数进行归一化,以避免由于舍入误差而导致的方向发生不希望的变化。

【讨论】:

【参考方案2】:

如果您采用欧拉角表示并将其转换为四元数只是为了进行矢量旋转,那么您仍然只是使用欧拉角。只要您在任何地方都涉及欧拉角,万向节锁定问题就会一直存在。如果你想完全消除这个问题,你需要完全切换到四元数,并且永远不要在任何地方使用欧拉角表示。

执行此操作的基本方法是,您可以在计算的远端使用欧拉角(作为原始输入或作为最终输出),如果这对您更方便(例如,欧拉角通常更“人类可读”表示)。但是,对其他所有内容使用四元数(您可以偶尔转换为旋转矩阵,因为它们对于旋转矢量更有效),并且永远不要对任何“坏”旋转表示(欧拉角、轴角等)进行中间转换.)。仅当您在所有计算中都坚持使用这两种表示时,四元数和旋转矩阵的“无万向节锁定”优势才适用。

所以,一方面,你有奇异的表示(“万向节锁”的正式术语是 singularity):

欧拉角(任何种类,顺便说一下,其中有 12 个);和 轴角。

另一方面,你有无奇点的表示:

四元数(更高效的内存和组合操作);和 旋转矩阵(对向量应用旋转更有效)。

每当您使用奇异表示进行操作(例如将多个旋转组合在一起,或移动旋转对象)时,您将不得不担心每次计算中的奇异性。当您使用无奇点表示进行相同的操作时,您不必担心(但您必须担心约束,这是四元数的单位范数约束和旋转矩阵的正确正交性)。当然,任何时候你转换成或从单一表示形式转换,你都必须担心奇点。这就是为什么您应该只在计算的远端(进入或离开时)进行转换,否则在整个计算过程中坚持使用非奇异表示。

欧拉角的唯一好处是人类可读性,句号。

【讨论】:

那么,我应该在代码中的什么时候切换到旋转矩阵?在我将3个旋转的quats组合在一起之后?我真的不知道四元数旋转矩阵是什么样子的。 我对您发布的代码的更广泛上下文一无所知,但我想这在某种程度上涉及在计算机游戏中移动 3D 对象或类似的东西,也许通过物理计算或用户输入(例如,操纵杆)。如果是通过物理模拟,那么你应该用四元数来做这一切。如果它来自操纵杆输入之类的东西,那么该输入应该立即转换为四元数。您的对象不应将旋转存储为欧拉角(就像您对“roll”、“pitch”和“yaw”值所做的那样)。 我不完全理解,这让我很烦。我的对象有旋转值,因为它们的旋转可以随心所欲地改变。我脑子里有些东西没有点击 是的,但是对象的旋转应该直接存储为四元数,而不是俯仰滚动偏航值。当您说可以“一时兴起”改变轮换时……这是什么意思?通过用户输入?如果有,是什么样的?或者通过一些运动计算? ...关键是您必须将欧拉角表示尽可能向后推(如果您需要它们)。 角度只是放入四元数,然后取角度a = a/360 * pi*2,用于确定四元数“w”分量w = cos(a/2),旋转轴矢量v = n* sin(angle/2)我已经甚至尝试将每个轴的 quats 相乘,然后将其插入旋转矩阵,乘以我的向量。还是达到了“奇点”!我用一个例子编辑了我的开篇文章

以上是关于四元数到达万向节锁的主要内容,如果未能解决你的问题,请参考以下文章

欧拉角万向节死锁与四元数的相关资料

Unity编程四元数(Quaternion)与欧拉角

Unity精华☀️四元数(Quaternion)解决万向锁

SLAM练习题(十三)—— 四元数插值

Unity编程Unity中关于四元数的API详解

Unity基础:欧拉角、四元数