변환 행렬에서 오일러 각을 추출하는 방법은 무엇입니까?


12

엔터티 / 구성 요소 게임 엔진을 간단하게 구현했습니다.
변형 컴포넌트에는 로컬 위치, 로컬 회전, 글로벌 위치 및 글로벌 회전을 설정하는 방법이 있습니다.

변환이 새로운 전역 위치로 설정되면 로컬 위치도 변경됩니다.이 경우 로컬 위치를 업데이트하기 위해 현재 변환 로컬 매트릭스를 부모의 변환 월드 매트릭스에 적용하고 있습니다.

그때까지 아무런 문제가 없을 때 로컬 변환 매트릭스를 업데이트 할 수 있습니다.
그러나 변환에서 로컬 위치 및 회전 값을 업데이트하는 방법에 어려움을 겪고 있습니다. 내가 생각하는 유일한 해결책은 변환의 localMatrix에서 변환 및 회전 값을 추출하는 것입니다.

번역은 매우 쉽습니다. 나는 단지 4 번째 열 값을 취합니다. 그러나 회전은 어떻습니까?
변환 행렬에서 오일러 각을 추출하는 방법은 무엇입니까?

그런 해결책이 맞습니까? :
Z 축 주위의 회전을 찾으려면 localTransform의 X 축 벡터와 parent.localTransform의 X 축 벡터 사이의 차이를 찾아 델타에 결과를 저장 한 다음 localRotation.z = atan2 (Delta.y, Delta .엑스);

X & Y를 중심으로 한 회전과 동일하며 축만 교체하면됩니다.

답변:


10

일반적으로 4x4와 3 세트의 vector3 (번역, 회전, 스케일) 사이를 앞뒤로 변환하는 대신 모든 객체를 4x4 행렬로 저장합니다 (3x3을 수행 할 수는 있지만 1 클래스 만 사용하는 것이 더 쉽습니다). 오일러 각도는 특정 시나리오에서 다루기 어려운 것으로 악명 높으므로 행렬 대신 구성 요소를 실제로 저장하려면 Quaternions를 사용하는 것이 좋습니다.

그러나 여기에 내가 찾은 코드가 있습니다. 불행히도 이것이 이것을 찾은 원본이 없기를 바랍니다. 어떤 이상한 시나리오에서 작동하지 않을지 모르겠습니다. 현재 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의 astea2 asin이 왜 피치에 사용되는지 모르겠습니다.

또한 각 구성 요소를 별도의 mat4x4에 저장하면 어떻게 도움이됩니까? 그러면 어떻게 축 주위에서 출력 회전 각을 얻을 수 있습니까?

원래 질문은 객체를 3 개의 벡터 3 : 번역, 회전 및 스케일로 저장한다고 믿습니다. 그런 다음 일부 작업을 수행하고 나중에 (localTransform * globalTransform)을 3 개의 vector3로 다시 변환하려고 시도하는 사람들로부터 localTransform을 만들 때. 나는 단지 그 인상을 받고 있었다 완전히 잘못 될 수 있습니다.
NtscCobalt

그래, 왜 ASIN으로 피치가 이루어 졌는지에 대한 수학을 충분히 알지 못하지만 연결된 질문은 동일한 수학을 사용하므로 정확하다고 생각합니다. 나는이 기능을 아무런 문제없이 한동안 사용 해왔다.
NtscCobalt

첫 번째 경우 atan2f를 사용하고 세 번째 경우 atan2를 사용하는 특별한 이유가 있습니까, 아니면 오타입니까?
Mattias 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 

여기서 Cx의 코사인 인 x회전 각도, Sx의 사인이다 x회전 각도 등

이제 과제는 원본을 추출하는 것입니다 x, y그리고 z매트릭스에 들어갔다 값.

먼저 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 

지금 우리가 해결할 수 sinZcosZ행렬 곱셈을 수행하여. 우리는 요소를 계산해야 [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

@PieterGeerkens, 맞습니다. 짐벌 락입니다. BTW, 귀하의 의견에 따르면 해당 섹션에 오타가 있음이 밝혀졌습니다. 나는 0에서 첫 번째로 행렬 인덱스를 참조하십시오, 그들은 3 × 3 행렬이기 때문에, 마지막 인덱스는, 내가 수정 한하지 3. 2 M[1][3]M[1][2]하고, M[2][3]M[2][2].
Chris

예제 결합 행렬의 두 번째 행 첫 번째 열은 SxSySz + CxSz가 아니라 SxSyCz + CxSz입니다!
Lake

@ 호수, 당신이 맞아요. 편집했습니다.
Chris
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.