旋轉矩陣與尤拉角的相互轉換

charlee44發表於2022-04-04

1. 概述

尤拉角與旋轉矩陣的相互轉換,是圖形計算中的常見問題。

2. 詳論

2.1. 尤拉角的理解

表達旋轉變換最簡單的理解是三種旋轉矩陣(繞X軸旋轉矩陣,繞Y軸旋轉矩陣以及繞Z軸旋轉矩陣)級聯。而尤拉角同樣也有三種:航向角heading,俯仰角pitch和滾轉角roll;其中,航向角heading有時也被稱為偏航角yaw。三個尤拉角定義的矩陣級聯也可以定義成旋轉矩陣,這種旋轉變換也叫做尤拉變換。

兩者並沒有絕對的對應的關係,但是絕大部分情況下,我們可以確定一個預設的檢視方向:朝向負z軸,頭部沿y軸定向,如下圖所示:
imglink1

想象一個飛機也位於上圖的座標系的預設檢視,那麼很顯然可以看出一個對應關係:

  • 航向角heading為繞Y軸旋轉
  • 俯仰角pitch為繞X軸旋轉
  • 滾轉角roll為繞Z軸旋轉

2.2. 尤拉角轉旋轉矩陣

如上節所述,確定尤拉角到底是繞哪一個軸旋轉的關鍵是確定預設的檢視方向。另一個需要確定的因素就是旋轉的順序。由於矩陣的乘法不滿足交換律,那麼矩陣級聯的順序不同,得到的旋轉矩陣也不同。一種比較常用的旋轉順序是:

\[{\textbf{E}}(h,p,r) = {\textbf{R}}_z(r){\textbf{R}}_x (p){\textbf{R}}_y(h) \]

我們使用GLM(OpenGL Mathematics)庫進行驗證一下:

#include <iostream>
#include <glm/gtx/euler_angles.hpp>

using namespace std;

static void PrintMat(const glm::mat4& m)
{
	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			printf("%.9lf\t", m[i][j]);
		}
		printf("\n");
	}
}

int main()
{
	glm::mat4 rotY = glm::eulerAngleY(glm::radians(24.0f));
	glm::mat4 rotX = glm::eulerAngleX(glm::radians(65.0f));
	glm::mat4 rotZ = glm::eulerAngleZ(glm::radians(42.0f));

	glm::mat4 rotYXZ = rotY * rotX * rotZ;
	PrintMat(rotYXZ);
	cout << endl;

	glm::mat4 rotYXZ1 = glm::eulerAngleYXZ(glm::radians(24.0f), glm::radians(65.0f), glm::radians(42.0f));
	PrintMat(rotYXZ1);
	cout << endl;

	glm::mat4 rotYXZ2 = glm::yawPitchRoll(glm::radians(24.0f), glm::radians(65.0f), glm::radians(42.0f));
	PrintMat(rotYXZ2);
}

執行結果如下:

imglink2

直接使用尤拉角旋轉矩陣相乘,與eulerAngleYXZ()函式,以及yawPitchRoll()函式三者的矩陣結果是一致的。說明在GLM中尤拉角的定義以及旋轉順序,與本文論述的一致。

2.3. 旋轉矩陣轉尤拉角

已知繞X軸、Y軸以及Z軸旋轉矩陣的公式以及它們的旋轉順序,可以很容易倒推旋轉矩陣表達的尤拉角。當然也沒有那麼容易,因為有一些特殊情況必須處理。那麼還是通過GLM進行實現:

#include <iostream>
#include <glm/gtx/euler_angles.hpp>

using namespace std;

static void PrintMat(const glm::mat4& m)
{
	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			printf("%.9lf\t", m[i][j]);
		}
		printf("\n");
	}
}

int main()
{
	glm::mat4 rotY = glm::eulerAngleY(glm::radians(24.0f));
	glm::mat4 rotX = glm::eulerAngleX(glm::radians(65.0f));
	glm::mat4 rotZ = glm::eulerAngleZ(glm::radians(42.0f));
		
	glm::mat4 rotYXZ1 = glm::eulerAngleYXZ(glm::radians(24.0f), glm::radians(65.0f), glm::radians(42.0f));
	PrintMat(rotYXZ1);
	cout << endl;

	float y = 0, x = 0, z = 0;
	glm::extractEulerAngleYXZ(rotYXZ1, y, x, z);
	cout << glm::degrees(y) << '\t' << glm::degrees(x) << '\t' << glm::degrees(z);
}

執行結果如下:
imglink3

由尤拉角引數生成的eulerAngleYXZ()與extractEulerAngleYXZ()提取的尤拉角一致。說明GLM的實現是正確的,一般的圖形矩陣計算庫應該都有類似的介面。

相關文章