一、歐拉角
歐拉角是用於描述物體在三維空間中旋轉的方法之一。它由三個連續旋轉組成,分別是繞x軸旋轉角度φ、繞y軸旋轉角度θ和繞z軸旋轉角度ψ,其旋轉順序可以是xyz、xzy、yxz、yzx、zxy、zyx等。
歐拉角的優點是直觀易懂,可以通過簡單的三個角度來描述旋轉情況。但缺點也是很明顯的,就是在某些情況下會出現萬向鎖問題。另外,由於歐拉角旋轉的過程是連續的,一旦多次旋轉累積起來,會導致誤差的積累。
//歐拉角轉為旋轉矩陣 function euler2mat(phi, theta, psi) { const cosPhi = Math.cos(phi); const sinPhi = Math.sin(phi); const cosTheta = Math.cos(theta); const sinTheta = Math.sin(theta); const cosPsi = Math.cos(psi); const sinPsi = Math.sin(psi); const mat = [ [cosTheta * cosPsi, -cosPhi * sinPsi + sinPhi * sinTheta * cosPsi, sinPhi * sinPsi + cosPhi * sinTheta * cosPsi], [cosTheta * sinPsi, cosPhi * cosPsi + sinPhi * sinTheta * sinPsi, -sinPhi * cosPsi + cosPhi * sinTheta * sinPsi], [-sinTheta, sinPhi * cosTheta, cosPhi * cosTheta] ]; return mat; }
二、四元數
四元數是一種擴展了複數的數學結構,它由一個實部和三個虛部構成。可以用於表示旋轉操作和空間旋轉中的旋轉向量,同時解決了歐拉角的兩個問題,即避免萬向鎖和誤差積累。
四元數的乘法可以看作是兩個旋轉的合成,因此可以很方便地進行多次旋轉的疊加。而且只需要4個數就可以表示旋轉狀態,比歐拉角更加緊湊和高效。
//四元數旋轉矩陣轉歐拉角 function mat2euler(mat) { const theta1 = -Math.asin(mat[2][0]); const cosTheta1 = Math.cos(theta1); const phi1 = Math.atan2(mat[2][1] / cosTheta1, mat[2][2] / cosTheta1); const psi1 = Math.atan2(mat[1][0] / cosTheta1, mat[0][0] / cosTheta1); return [phi1, theta1, psi1]; } //四元數類 class Quaternion { constructor(w, x, y, z) { this.w = w || 1.0; this.x = x || 0.0; this.y = y || 0.0; this.z = z || 0.0; } normalize() { const norm = Math.sqrt(this.w ** 2 + this.x ** 2 + this.y ** 2 + this.z ** 2); if (norm === 0) return; this.w /= norm; this.x /= norm; this.y /= norm; this.z /= norm; } multiply(q) { const w1 = this.w, x1 = this.x, y1 = this.y, z1 = this.z; const w2 = q.w, x2 = q.x, y2 = q.y, z2 = q.z; this.w = w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2; this.x = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2; this.y = w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2; this.z = w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2; this.normalize(); } }
三、應用實例
在遊戲開發中,我們通常會使用三維渲染引擎來展現一個三維場景。在這個場景中,有很多需要旋轉的物體,比如角色、相機和物體等。下面我們以相機為例,展示歐拉角和四元數在相機旋轉中的應用。
1、歐拉角控制相機旋轉
在WebGL中,我們可以使用歐拉角來控制相機的旋轉。假設我們有一個相機對象,可以通過鼠標控制其繞x和y軸的旋轉角度,代碼如下:
class Camera { constructor() { this.position = [0, 0, 5]; this.target = [0, 0, 0]; this.up = [0, 1, 0]; this.dist = Math.sqrt(this.position[0] ** 2 + this.position[1] ** 2 + this.position[2] ** 2); this.phi = Math.atan2(this.position[1], this.position[0]); this.theta = Math.acos(this.position[2] / this.dist); } rotate(deltaPhi, deltaTheta) { this.phi += deltaPhi; this.theta += deltaTheta; if (this.theta Math.PI - 0.01) this.theta = Math.PI - 0.01; const x = this.dist * Math.sin(this.theta) * Math.cos(this.phi); const y = this.dist * Math.sin(this.theta) * Math.sin(this.phi); const z = this.dist * Math.cos(this.theta); this.position = [x, y, z]; } getViewMatrix() { const mat = euler2mat(this.theta, this.phi, 0); const zAxis = [mat[0][2], mat[1][2], mat[2][2]]; const xAxis = [mat[0][0], mat[1][0], mat[2][0]]; const yAxis = [mat[0][1], mat[1][1], mat[2][1]]; const tx = -xAxis[0] * this.position[0] - xAxis[1] * this.position[1] - xAxis[2] * this.position[2]; const ty = -yAxis[0] * this.position[0] - yAxis[1] * this.position[1] - yAxis[2] * this.position[2]; const tz = -zAxis[0] * this.position[0] - zAxis[1] * this.position[1] - zAxis[2] * this.position[2]; return [ xAxis[0], yAxis[0], zAxis[0], 0, xAxis[1], yAxis[1], zAxis[1], 0, xAxis[2], yAxis[2], zAxis[2], 0, tx, ty, tz, 1 ]; } }
2、四元數控制相機旋轉
除了使用歐拉角之外,我們還可以使用四元數來控制相機的旋轉。這裡假設我們有一個相機對象和一個旋轉四元數q,可以通過鼠標操作旋轉四元數,然後計算出相機的旋轉矩陣,代碼如下:
class Camera {
constructor() {
this.position = [0, 0, 5];
this.target = [0, 0, 0];
this.up = [0, 1, 0];
this.dist = Math.sqrt(this.position[0] ** 2 + this.position[1] ** 2 + this.position[2] ** 2);
this.phi = Math.atan2(this.position[1], this.position[0]);
this.theta = Math.acos(this.position[2] / this.dist);
this.quaternion = new Quaternion();
}
rotate(deltaPhi, deltaTheta) {
this.phi += deltaPhi;
this.theta += deltaTheta;
if (this.theta Math.PI - 0.01) this.theta = Math.PI - 0.01;const x = this.dist * Math.sin(this.theta) * Math.cos(this.phi);
const y = this.dist * Math.sin(this.theta) * Math.sin(this.phi);
const z = this.dist * Math.cos(this.theta);this.position = [x, y, z];
const mat = euler2mat(this.theta, this.phi, 0);
this.quaternion = new Quaternion().fromMat(mat);
}
getViewMatrix() {
const mat = this.quaternion.toMat();
const zAxis = [mat[0][2], mat[1][2], mat[2][2]];
const xAxis = [mat[0][0], mat[1][0], mat[2][0]];
const yAxis = [mat[0][1], mat[1][1], mat[2][1]];const tx = -xAxis[0] * this.position[0] - xAxis[1] * this.position[1] - xAxis[2] * this.position[2];
const ty = -yAxis[0] * this.position[0] - yAxis[1] * this.position[1] - yAxis[2] * this.position[2];
const tz = -zAxis[0] * this.position[0] - zAxis[1] * this.position[1] - zAxis[2] * this.position[2];return [
xAxis[0], yAxis[0], zAxis[0], 0,
xAxis[1], yAxis[1], zAxis[1], 0,
xAxis[2], yAxis[2], zAxis[2], 0,
tx, ty, tz, 1
];
}
}class Quaternion {
constructor(w, x, y, z) {
this.w = w || 1.0;
this.x = x || 0.0;
this.y = y || 0.0;
this.z = z || 0.0;
}
normalize() {
const norm = Math.sqrt(this.w ** 2 + this.x ** 2 + this.y ** 2 + this.z ** 2);
if (norm === 0) return;
this.w /= norm;
this.x /= norm;
this.y /= norm;
this.z /= norm;
}
multiply(q) {
const w1 = this.w,
x1 = this.x,
y1 = this.y,
z1 = this.z;
const w2 = q.w,
x2 = q.x,
y2 = q.y,
z2 = q.z;
this.w = w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2;
this.x = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2;
this.y = w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2;
this.z = w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2;
this.normalize();
}
fromMat(mat) {
const trace = mat[0][0] + mat[1][1] + mat[2][2];
if (trace >= 0) {
const s = Math.sqrt(trace + 1) * 2;
const w = 0.25 * s;
const x = (mat[2][1] - mat[1][2]) / s;
const y = (mat[0][2] - mat[2][0]) / s;
const z = (mat[1][0] - mat[0][1]) / s;
this.w = w;
this.x = x;
this.y = y;
this.z = z;
} else if (mat[0][0] > mat[1][1] && mat[0][0] > mat[2][2]) {
const s = Math.sqrt(1 + mat[0][0] - mat[1][1] - mat[2][2]) * 2;
const w = (mat[2][1] - mat[1][2]) / s;
const x = 0.25 * s;
const y = (mat[0][1] + mat[1][0]) / s;
const z = (mat[0][2] + mat[2][0]) / s;
this.w = w;
this.x = x;
this.y = y;
this.z = z;
} else if原創文章,作者:AJNLU,如若轉載,請註明出處:https://www.506064.com/zh-hant/n/333194.html