วิธีการแยกมุมออยเลอร์จากเมทริกซ์การแปลง?


12

ฉันมีความเข้าใจง่าย ๆ เกี่ยวกับเอ็นจิ้นเกมเอนทิตี / องค์ประกอบ
องค์ประกอบการแปลงมีวิธีการในการกำหนดตำแหน่งท้องถิ่นการหมุนในท้องถิ่นตำแหน่งทั่วโลกและการหมุนทั่วโลก

หากการแปลงกำลังถูกกำหนดตำแหน่งใหม่ระดับโลกดังนั้นตำแหน่งในท้องถิ่นก็จะเปลี่ยนแปลงเช่นกันเพื่ออัปเดตตำแหน่งในกรณีเช่นนี้ฉันแค่ใช้การแปลงเมทริกซ์โลคอลปัจจุบันไปยังเมทริกซ์การแปลงของผู้ปกครอง

ก่อนหน้านี้ฉันไม่มีปัญหาฉันสามารถรับเมทริกซ์การแปลงเฉพาะที่ได้
แต่ฉันกำลังดิ้นรนกับวิธีอัปเดตตำแหน่งในท้องถิ่นและค่าการหมุนในการแปลงสภาพ ทางออกเดียวที่ฉันมีในใจคือการแยกค่าการแปลและการหมุนจาก localMatrix ของการแปลง

สำหรับการแปลมันค่อนข้างง่าย - ฉันแค่เอาค่าคอลัมน์ที่ 4 แต่การหมุนคืออะไร
วิธีการแยกมุมออยเลอร์จากเมทริกซ์การแปลง?

วิธีแก้ปัญหานั้นถูกต้องหรือไม่:
เพื่อหาการหมุนรอบแกน Z เราสามารถหาความแตกต่างระหว่างเวกเตอร์แกน X ของ localTransform และเวกเตอร์แกน X ของ parent.localTransform และเก็บผลลัพธ์ในเดลต้าแล้ว: localRotation.z = atan2 (Delta.y, Delta .x);

เช่นเดียวกับการหมุนรอบ X & Y เพียงแค่ต้องสลับแกน

คำตอบ:


10

โดยปกติแล้วฉันเก็บวัตถุทั้งหมดเป็น 4x4 Matrices (คุณสามารถทำ 3x3 ได้ แต่ง่ายขึ้นสำหรับฉันที่จะมี 1 คลาส) แทนที่จะแปลไปมาระหว่าง vector3s 4x4 และ 3 ชุด (Translation, Rotation, Scale) มุมของออยเลอร์นั้นยากต่อการจัดการในบางสถานการณ์ดังนั้นฉันขอแนะนำให้ใช้ 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 asin เพื่อใช้ในการขว้าง

นอกจากนี้มันจะช่วยฉันได้อย่างไรถ้าฉันจะเก็บส่วนประกอบแต่ละอย่างใน mat4x4 แยกกัน จากนั้นฉันจะได้รับและเช่นมุมเอาต์พุตของการหมุนรอบแกนได้อย่างไร

คำถามเดิมของคุณทำให้ฉันเชื่อว่าคุณกำลังจัดเก็บวัตถุของคุณเป็น 3 vector3s: การแปลการหมุนและการสเกล จากนั้นเมื่อคุณสร้าง localTransform จากสิ่งที่ทำงานบางอย่างแล้วพยายามแปลง (localTransform * globalTransform) กลับสู่ 3 vector3s ฉันอาจผิดทั้งหมดฉันเพิ่งได้รับความประทับใจนั้น
NtscCobalt

ใช่ฉันไม่รู้คณิตศาสตร์ดีพอเพราะเหตุใด pitch จึงเสร็จสิ้นด้วย ASIN แต่คำถามที่เชื่อมโยงใช้คณิตศาสตร์เดียวกันดังนั้นฉันจึงเชื่อว่าถูกต้อง ฉันใช้ฟังก์ชั่นนี้มาระยะหนึ่งแล้วโดยไม่มีปัญหา
NtscCobalt

มีเหตุผลใดบ้างสำหรับการใช้ atan2f ในสองกรณีแรกถ้าเป็นกรณีและ atan2 ในกรณีที่สามหรือเป็นแบบพิมพ์ผิด?
Mattias F

10

มีการเขียนที่ยอดเยี่ยมเกี่ยวกับกระบวนการนี้โดย Mike Day: https://d3cw3dd2w32x2b.cloudfront.net/wp-content/uploads/2012/07/euler-angles1.pdf

ตอนนี้มันถูกนำไปใช้งานใน glm ด้วยเช่นรุ่น 0.9.7.0, 02/08/2015 ตรวจสอบการดำเนินการ

เพื่อให้เข้าใจคณิตศาสตร์คุณควรดูค่าที่อยู่ในเมทริกซ์การหมุนของคุณ นอกจากนี้คุณต้องทราบลำดับที่การหมุนถูกนำไปใช้เพื่อสร้างเมทริกซ์ของคุณเพื่อดึงค่าอย่างถูกต้อง

เมทริกซ์การหมุนจากมุมออยเลอร์เกิดขึ้นจากการรวมการหมุนรอบแกน 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)และ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 แตกต่างจากคำตอบก่อนหน้า เนื่องจาก ณ จุดนี้เราทราบจำนวนxและyการหมุนเราจึงสามารถสร้างเมทริกซ์การหมุน XY และค้นหาปริมาณzการหมุนที่จำเป็นเพื่อให้ตรงกับเมทริกซ์เป้าหมาย RxRyเมทริกซ์มีลักษณะเช่นนี้

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

เนื่องจากเรารู้ว่าRxRy* Rzเท่ากับเมทริกซ์อินพุตของMเราเราสามารถใช้เมทริกซ์นี้เพื่อกลับไปที่Rz:

M = RxRy * Rz

inverse(RxRy) * M = Rz

ผกผันของเมทริกซ์หมุน transpose ของตนเพื่อให้เราสามารถขยายนี้ไปที่:

 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 ฉันเชื่อว่านี่เทียบเท่ากับปัญหาล็อก gimbal
Pieter Geerkens

@PieterGeerkens คุณพูดถูกนั่นคือล็อก gimbal BTW ความคิดเห็นของคุณเปิดเผยว่าฉันมีการพิมพ์ผิดในส่วนนั้น ผมหมายถึงดัชนีเมทริกซ์กับคนแรกที่ 0 และเนื่องจากพวกเขาเป็น 3x3 เมทริกซ์ดัชนีสุดท้ายคือ 2 ไม่ 3. ฉันได้รับการแก้ไขM[1][3]ด้วยM[1][2]และมีM[2][3] M[2][2]
Chris

ฉันค่อนข้างแน่ใจว่าคอลัมน์ที่สองในคอลัมน์แรกของเมทริกซ์รวมตัวอย่างคือ SxSyCz + CxSz ไม่ใช่ SxSySz + CxSz!
ทะเลสาบ

@ ทะเลสาบคุณถูกต้อง แก้ไข
คริส
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.