Có một bài viết tuyệt vời về quá trình này của Mike Day:
https://d3cw3dd2w32x2b.cloudfront.net/wp-content/uploads/2012/07/euler-angles1.pdf
Hiện tại nó cũng được triển khai trong glm, kể từ phiên bản 0.9.7.0, 02/08/2015. Kiểm tra việc thực hiện .
Để hiểu toán, bạn nên nhìn vào các giá trị trong ma trận xoay vòng của bạn. Ngoài ra, bạn phải biết thứ tự các phép quay được áp dụng để tạo ma trận của bạn để trích xuất đúng các giá trị.
Một ma trận xoay từ các góc Euler được hình thành bằng cách kết hợp các phép quay quanh các trục x, y- và z. Ví dụ, xoay θ độ quanh Z có thể được thực hiện với ma trận
┌ cosθ -sinθ 0 ┐
Rz = │ sinθ cosθ 0 │
└ 0 0 1 ┘
Các ma trận tương tự tồn tại để quay quanh trục X và Y:
┌ 1 0 0 ┐
Rx = │ 0 cosθ -sinθ │
└ 0 sinθ cosθ ┘
┌ cosθ 0 sinθ ┐
Ry = │ 0 1 0 │
└ -sinθ 0 cosθ ┘
Chúng ta có thể nhân các ma trận này với nhau để tạo ra một ma trận là kết quả của cả ba phép quay. Điều quan trọng cần lưu ý là thứ tự các ma trận này được nhân với nhau là rất quan trọng, vì phép nhân ma trận không giao hoán . Điều này có nghĩa là Rx*Ry*Rz ≠ Rz*Ry*Rx
. Hãy xem xét một thứ tự xoay có thể, zyx. Khi ba ma trận được kết hợp, nó sẽ tạo ra một ma trận giống như sau:
┌ CyCz -CySz Sy ┐
RxRyRz = │ SxSyCz + CxSz -SxSySz + CxCz -SxCy │
└ -CxSyCz + SxSz CxSySz + SxCz CxCy ┘
trong đó Cx
cosin của x
góc quay, Sx
là sin của x
góc quay, v.v.
Bây giờ, thách thức là để trích xuất các gốc x
, y
và z
giá trị mà đã đi vào ma trận.
Trước tiên hãy lấy x
góc nhìn ra. Nếu chúng ta biết sin(x)
và cos(x)
, chúng ta có thể sử dụng hàm tiếp tuyến nghịch đảo atan2
để đưa chúng ta trở lại góc của chúng ta. Thật không may, những giá trị đó không tự xuất hiện trong ma trận của chúng tôi. Nhưng, nếu chúng ta xem xét kỹ hơn các yếu tố M[1][2]
và M[2][2]
, chúng ta có thể thấy chúng ta cũng biết -sin(x)*cos(y)
như vậy cos(x)*cos(y)
. Vì hàm tiếp tuyến là tỷ lệ của các cạnh đối diện và liền kề của một tam giác, nên chia tỷ lệ cả hai giá trị theo cùng một lượng (trong trường hợp này cos(y)
) sẽ mang lại kết quả như nhau. Như vậy
x = atan2(-M[1][2], M[2][2])
Bây giờ chúng ta hãy cố gắng để có được y
. Chúng tôi biết sin(y)
từ M[0][2]
. Nếu chúng ta có cos (y), chúng ta có thể sử dụng atan2
lại, nhưng chúng ta không có giá trị đó trong ma trận của mình. Tuy nhiên, do bản sắc Pythagore , chúng tôi biết rằng:
cosY = sqrt(1 - M[0][2])
Vì vậy, chúng ta có thể tính toán y
:
y = atan2(M[0][2], cosY)
Cuối cùng, chúng ta cần tính toán z
. Đây là cách tiếp cận của Mike Day khác với câu trả lời trước đó. Vì tại thời điểm này, chúng ta biết lượng x
và y
xoay, chúng ta có thể xây dựng ma trận xoay XY và tìm lượng z
xoay cần thiết để khớp với ma trận đích. Các RxRy
ma trận trông như thế này:
┌ Cy 0 Sy ┐
RxRy = │ SxSy Cx -SxCy │
└ -CxSy Sx CxCy ┘
Vì chúng tôi biết rằng RxRy
* Rz
bằng với ma trận đầu vào của chúng tôi M
, chúng tôi có thể sử dụng ma trận này để quay lại Rz
:
M = RxRy * Rz
inverse(RxRy) * M = Rz
Các nghịch đảo của một ma trận xoay là transpose của nó , vì vậy chúng tôi có thể mở rộng này để:
┌ 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 ┘
Bây giờ chúng ta có thể giải quyết sinZ
và cosZ
bằng cách thực hiện phép nhân ma trận. Chúng ta chỉ cần tính toán các yếu tố [1][0]
và [1][1]
.
sinZ = cosX * M[1][0] + sinX * M[2][0]
cosZ = coxX * M[1][1] + sinX * M[2][1]
z = atan2(sinZ, cosZ)
Đây là một triển khai đầy đủ để tham khảo:
#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;
}