如何从变换矩阵中提取欧拉角?


12

我有一个实体/组件游戏引擎的简单实现。
变换组件具有设置局部位置,局部旋转,全局位置和全局旋转的方法。

如果将变换设置为新的全局位置,则局部位置也会更改,以在这种情况下更新局部位置,我只是将当前的变换局部矩阵应用于父级的变换世界矩阵。

在那之前,我没有任何问题,我可以获得更新的局部变换矩阵。
但是我正在努力如何在变换中更新局部位置和旋转值。我想到的唯一解决方案是从变换的localMatrix提取平移和旋转值。

对于翻译来说,这很容易-我只取第4列的值。但是轮换呢?
如何从变换矩阵中提取欧拉角?

是这样的解决方案右?:
要查找围绕Z轴的旋转,我们可以发现localTransform和X的X轴矢量之间的差值轴线parent.localTransform的矢量,结果存储在德尔塔,则:localRotation.z = ATAN2(Delta.y,德尔塔。X);

围绕X和Y旋转相同,只需要交换轴即可。

Answers:


10

通常,我将所有对象存储为4x4矩阵(您可以执行3x3矩阵,但对于我来说,只拥有1个类就更容易了),而不是在4x4和3套vector3(平移,旋转,缩放)之间来回转换。众所周知,在某些情况下,欧拉角很难处理,因此如果您确实想存储分量而不是矩阵,我建议使用四元数。

但是这里有一些我发现可以使用的代码。我希望这会有所帮助,不幸的是,我没有找到该文件的原始来源。我不知道它可能无法在什么奇怪的情况下工作。我目前正在使用它来旋转YawPitchRoll旋转的左手4x4矩阵。

   union {
        struct 
        {
            float        _11, _12, _13, _14;
            float        _21, _22, _23, _24;
            float        _31, _32, _33, _34;
            float        _41, _42, _43, _44;
        };
        float m[4][4];
        float m2[16];
    };

    inline void GetRotation(float& Yaw, float& Pitch, float& Roll) const
    {
        if (_11 == 1.0f)
        {
            Yaw = atan2f(_13, _34);
            Pitch = 0;
            Roll = 0;

        }else if (_11 == -1.0f)
        {
            Yaw = atan2f(_13, _34);
            Pitch = 0;
            Roll = 0;
        }else 
        {

            Yaw = atan2(-_31,_11);
            Pitch = asin(_21);
            Roll = atan2(-_23,_22);
        }
    }

这是我在尝试回答您的问题时发现的另一个线程,看起来与我的结果类似。

/programming/1996957/conversion-euler-to-matrix-and-matrix-to-euler


似乎我提出的解决方案几乎是正确的,只是不知道为什么将atan2 asin的istead用于音调。

另外,如果我将每个组件存储在单独的mat4x4中,对我有什么帮助?然后如何获得并输出围绕某个轴的旋转角度?

您最初的问题使我相信您将对象存储为3个vector3:平移,旋转和缩放。然后,当您从做些工作的人员中创建localTransform时,随后又尝试将(localTransform * globalTransform)转换回3个vector3。我可能完全错了,我只是得到了那种印象。
NtscCobalt 2013年

是的,我对数学的理解不够,为什么为什么要用ASIN完成音调,但是链接的问题也使用相同的数学,所以我认为这是正确的。我使用该功能已有一段时间了,没有任何问题。
NtscCobalt

如果在前两种情况下使用atan2f,并且在第三种情况下使用atan2,是否有任何特殊的原因,或者是错字?
马提亚斯F

10

Mike Day在此过程中撰写了大量文章:https : //d3cw3dd2w32x2b.cloudfront.net/wp-content/uploads/2012/07/euler-angles1.pdf

从0.9.7.0版本(2015年2月8日)起,现在也可以在glm中实现该功能。 检查实施

要了解数学,您应该查看旋转矩阵中的值。另外,您必须知道应用旋转创建矩阵的顺序,以便正确提取值。

来自欧拉角的旋转矩阵是通过组合围绕x轴,y轴和z轴的旋转而形成的。例如,围绕Z旋转θ度可以通过矩阵完成

      cosθ  -sinθ   0 
Rz =  sinθ   cosθ   0 
        0      0    1 

存在绕X和Y轴旋转的类似矩阵:

       1    0     0   
Rx =   0  cosθ  -sinθ 
       0  sinθ   cosθ 

       cosθ  0   sinθ 
Ry =    0    1    0   
      -sinθ  0   cosθ 

我们可以将这些矩阵相乘以创建一个矩阵,该矩阵是所有三个旋转的结果。请务必注意,将这些矩阵相乘的顺序很重要,因为矩阵乘法不是可交换的。这意味着Rx*Ry*Rz ≠ Rz*Ry*Rx。让我们考虑一种可能的旋转顺序zyx。将这三个矩阵合并后,将得到一个如下所示的矩阵:

               CyCz              -CySz        Sy  
RxRyRz =   SxSyCz + CxSz   -SxSySz + CxCz   -SxCy 
          -CxSyCz + SxSz    CxSySz + SxCz    CxCy 

哪里Cxx旋转角度的余弦,哪里是旋转角度Sx的正弦x,等等。

现在的挑战是提取原xyz那走进矩阵值。

让我们首先确定x角度。如果我们知道sin(x)and cos(x),则可以使用反正切函数atan2来返回角度。不幸的是,这些值本身不会出现在我们的矩阵中。但是,如果我们仔细研究元素M[1][2]M[2][2],我们可以知道我们也知道-sin(x)*cos(y)cos(x)*cos(y)。由于切线函数是三角形的相对边和相邻边的比率,因此将两个值缩放相同的量(在这种情况下cos(y))将产生相同的结果。从而,

x = atan2(-M[1][2], M[2][2])

现在让我们尝试获取y。我们sin(y)从知道M[0][2]。如果我们有cos(y),则可以atan2再次使用,但是矩阵中没有该值。但是,由于毕达哥拉斯身份,我们知道:

cosY = sqrt(1 - M[0][2])

因此,我们可以计算y

y = atan2(M[0][2], cosY)

最后,我们需要计算z。这就是Mike Day的方法与先前答案不同的地方。由于在这一点上,我们知道的量xy旋转,我们可以构造一个XY旋转矩阵,并找到量z旋转需要将目标矩阵相匹配。该RxRy矩阵如下所示:

          Cy     0     Sy  
RxRy =   SxSy   Cx   -SxCy 
        -CxSy   Sx    CxCy 

由于我们知道RxRy* Rz等于输入矩阵M,因此可以使用此矩阵返回Rz

M = RxRy * Rz

inverse(RxRy) * M = Rz

旋转矩阵逆是其转置,因此我们可以将扩展为:

 Cy   SxSy  -CxSy ┐┌M00  M01  M02    cosZ  -sinZ  0 
  0    Cx     Sx  ││M10  M11  M12 =  sinZ   cosZ  0 
 Sy  -SxCy   CxCy ┘└M20  M21  M22      0      0   1 

现在,我们可以解决sinZ,并cosZ通过执行矩阵乘法。我们只需要计算元素[1][0][1][1]

sinZ = cosX * M[1][0] + sinX * M[2][0]
cosZ = coxX * M[1][1] + sinX * M[2][1]
z = atan2(sinZ, cosZ)

这是一个完整的实现,以供参考:

#include <iostream>
#include <cmath>

class Vec4 {
public:
    Vec4(float x, float y, float z, float w) :
        x(x), y(y), z(z), w(w) {}

    float dot(const Vec4& other) const {
        return x * other.x +
            y * other.y +
            z * other.z +
            w * other.w;
    };

    float x, y, z, w;
};

class Mat4x4 {
public:
    Mat4x4() {}

    Mat4x4(float v00, float v01, float v02, float v03,
            float v10, float v11, float v12, float v13,
            float v20, float v21, float v22, float v23,
            float v30, float v31, float v32, float v33) {
        values[0] =  v00;
        values[1] =  v01;
        values[2] =  v02;
        values[3] =  v03;
        values[4] =  v10;
        values[5] =  v11;
        values[6] =  v12;
        values[7] =  v13;
        values[8] =  v20;
        values[9] =  v21;
        values[10] = v22;
        values[11] = v23;
        values[12] = v30;
        values[13] = v31;
        values[14] = v32;
        values[15] = v33;
    }

    Vec4 row(const int row) const {
        return Vec4(
            values[row*4],
            values[row*4+1],
            values[row*4+2],
            values[row*4+3]
        );
    }

    Vec4 column(const int column) const {
        return Vec4(
            values[column],
            values[column + 4],
            values[column + 8],
            values[column + 12]
        );
    }

    Mat4x4 multiply(const Mat4x4& other) const {
        Mat4x4 result;
        for (int row = 0; row < 4; ++row) {
            for (int column = 0; column < 4; ++column) {
                result.values[row*4+column] = this->row(row).dot(other.column(column));
            }
        }
        return result;
    }

    void extractEulerAngleXYZ(float& rotXangle, float& rotYangle, float& rotZangle) const {
        rotXangle = atan2(-row(1).z, row(2).z);
        float cosYangle = sqrt(pow(row(0).x, 2) + pow(row(0).y, 2));
        rotYangle = atan2(row(0).z, cosYangle);
        float sinXangle = sin(rotXangle);
        float cosXangle = cos(rotXangle);
        rotZangle = atan2(cosXangle * row(1).x + sinXangle * row(2).x, cosXangle * row(1).y + sinXangle * row(2).y);
    }

    float values[16];
};

float toRadians(float degrees) {
    return degrees * (M_PI / 180);
}

float toDegrees(float radians) {
    return radians * (180 / M_PI);
}

int main() {
    float rotXangle = toRadians(15);
    float rotYangle = toRadians(30);
    float rotZangle = toRadians(60);

    Mat4x4 rotX(
        1, 0,               0,              0,
        0, cos(rotXangle), -sin(rotXangle), 0,
        0, sin(rotXangle),  cos(rotXangle), 0,
        0, 0,               0,              1
    );
    Mat4x4 rotY(
         cos(rotYangle), 0, sin(rotYangle), 0,
         0,              1, 0,              0,
        -sin(rotYangle), 0, cos(rotYangle), 0,
        0,               0, 0,              1
    );
    Mat4x4 rotZ(
        cos(rotZangle), -sin(rotZangle), 0, 0,
        sin(rotZangle),  cos(rotZangle), 0, 0,
        0,               0,              1, 0,
        0,               0,              0, 1
    );

    Mat4x4 concatenatedRotationMatrix =
        rotX.multiply(rotY.multiply(rotZ));

    float extractedXangle = 0, extractedYangle = 0, extractedZangle = 0;
    concatenatedRotationMatrix.extractEulerAngleXYZ(
        extractedXangle, extractedYangle, extractedZangle
    );

    std::cout << toDegrees(extractedXangle) << ' ' <<
        toDegrees(extractedYangle) << ' ' <<
        toDegrees(extractedZangle) << std::endl;

    return 0;
}

但是请注意,当y = pi / 2从而cos(y)== 0时出现问题。那么并非不是可以使用M [1] [3]和M [2] [3]来获得x因为比率是不确定的,因此无法获得atan2值。我认为这等同于万向节锁定问题。
Pieter Geerkens 2015年

@PieterGeerkens,你是对的,那就是万向节锁。顺便说一句,您的评论显示我在该部分中有一个错字。我指的是第一个为0的矩阵索引,由于它们是3x3矩阵,所以最后一个索引是2,而不是3。我已经M[1][3]使用M[1][2]M[2][3]进行了更正M[2][2]
克里斯

我很确定示例组合矩阵的第二行第一列是SxSyCz + CxSz,而不是SxSySz + CxSz!

@Lake,你是对的。编辑。
克里斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.