9-4 三维旋转与缩放矩阵
三维旋转与缩放矩阵(3D Rotation and Scale Matrices)
将二维的齐次坐标思想扩展到三维:三维点 (x, y, z) 表示为齐次坐标 (x, y, z, 1),使用 4x4 矩阵统一描述所有三维仿射变换。三维旋转比二维复杂得多——二维只有一个旋转轴(垂直于平面的 z 轴),而三维可以绕任意轴旋转,通常通过绕三个坐标轴(x、y、z)的旋转来组合。
4x4 齐次坐标矩阵的一般形式为:
| a b c tx | | x | | ax + by + cz + tx |
| d e f ty | × | y | = | dx + ey + fz + ty |
| g h i tz | | z | | gx + hy + iz + tz |
| 0 0 0 1 | | 1 | | 1 |
左上角 3x3 子矩阵负责旋转、缩放和剪切,右侧列 [tx, ty, tz] 负责平移。第四行始终为 [0, 0, 0, 1]。
三维缩放矩阵(3D Scale Matrix)
三维缩放矩阵分别沿 x、y、z 三个轴缩放:
| sx 0 0 0 | | x | | sx * x |
| 0 sy 0 0 | × | y | = | sy * y |
| 0 0 sz 0 | | z | | sz * z |
| 0 0 0 1 | | 1 | | 1 |
例如,将点 (2, 3, 4) 缩放 (2, 1, 3):
| 2 0 0 0 | | 2 | | 4 |
| 0 1 0 0 | × | 3 | = | 3 |
| 0 0 3 0 | | 4 | | 12 |
| 0 0 0 1 | | 1 | | 1 |
结果为 (4, 3, 12)。x 轴放大 2 倍,y 轴不变,z 轴放大 3 倍。
均匀缩放(Uniform Scale):当 sx = sy = sz = s 时,所有方向等比缩放,形状不变,只有大小改变。
三维旋转矩阵(3D Rotation Matrices)
三维旋转需要指定旋转轴。最基本的三个旋转矩阵分别绕 x 轴、y 轴和 z 轴旋转,称为基本旋转矩阵(Basic Rotation Matrices)。
绕 Z 轴旋转 Rz(θ)
绕 z 轴旋转时,z 坐标不变,xy 平面上的旋转与二维完全相同:
| cosθ -sinθ 0 0 |
| sinθ cosθ 0 0 |
| 0 0 1 0 |
| 0 0 0 1 |
验证:旋转点 (1, 0, 0) 绕 z 轴旋转 90 度:
| 0 -1 0 0 | | 1 | | 0 |
| 1 0 0 0 | × | 0 | = | 1 |
| 0 0 1 0 | | 0 | | 0 |
| 0 0 0 1 | | 1 | | 1 |
结果为 (0, 1, 0)。x 轴上的点旋转 90 度后到达 y 轴,z 不变。
绕 X 轴旋转 Rx(θ)
绕 x 轴旋转时,x 坐标不变,yz 平面上的旋转:
| 1 0 0 0 |
| 0 cosθ -sinθ 0 |
| 0 sinθ cosθ 0 |
| 0 0 0 1 |
验证:旋转点 (0, 1, 0) 绕 x 轴旋转 90 度:
| 1 0 0 0 | | 0 | | 0 |
| 0 0 -1 0 | × | 1 | = | 0 |
| 0 1 0 0 | | 0 | | 1 |
| 0 0 0 1 | | 1 | | 1 |
结果为 (0, 0, 1)。y 轴上的点绕 x 轴旋转 90 度后到达 z 轴。
绕 Y 轴旋转 Ry(θ)
绕 y 轴旋转时,y 坐标不变,xz 平面上的旋转:
| cosθ 0 sinθ 0 |
| 0 1 0 0 |
| -sinθ 0 cosθ 0 |
| 0 0 0 1 |
注意 Ry 的符号模式与 Rx、Rz 不同:sinθ 出现在第 3 行第 1 列(负号)和第 1 行第 3 列(正号)。这是因为 y 轴旋转的方向约定。
验证:旋转点 (0, 0, 1) 绕 y 轴旋转 90 度:
| 0 0 1 0 | | 0 | | 1 |
| 0 1 0 0 | × | 0 | = | 0 |
|-1 0 0 0 | | 1 | | 0 |
| 0 0 0 1 | | 1 | | 1 |
结果为 (1, 0, 0)。z 轴上的点绕 y 轴旋转 90 度后到达 x 轴。
记忆口诀
三个旋转矩阵的结构可以用一个简单的规律来记忆:
绕哪个轴旋转,那个轴的行列在矩阵中就是 [1, 0, 0] 和 [0, 0, 1] 的标准形式。
剩下的 2x2 子矩阵就是一个二维旋转矩阵:
| cos -sin |
| sin cos |
Rz: 不动的是第 1、2 行列(xy 平面)
Rx: 不动的是第 1 行列(x),旋转在第 2、3 行列(yz 平面)
Ry: 不动的是第 2 行列(y),旋转在第 1、3 行列(xz 平面)— 注意 sin 符号
组合旋转与欧拉角(Euler Angles)
三个基本旋转可以通过矩阵乘法组合。欧拉角(Euler Angles)用三个角度 (α, β, γ) 描述任意三维旋转:
R = Rz(α) × Ry(β) × Rx(γ)
这称为 Z-Y-X 欧拉角(也称为 yaw-pitch-roll),是航空航天和游戏开发中最常用的约定:
- Yaw(偏航) α:绕世界 z 轴旋转(左右转头)
- Pitch(俯仰) β:绕新 y 轴旋转(上下点头)
- Roll(翻滚) γ:绕新 x 轴旋转(歪脖子)
以具体例子说明:将点 (1, 0, 0) 先绕 x 轴旋转 90 度,再绕 y 轴旋转 90 度:
Step 1: Rx(90) × (1, 0, 0)
| 1 0 0 0 | | 1 | | 1 |
| 0 0 -1 0 | × | 0 | = | 0 |
| 0 1 0 0 | | 0 | | 0 |
| 0 0 0 1 | | 1 | | 1 |
Step 2: Ry(90) × (1, 0, 0)
| 0 0 1 0 | | 1 | | 0 |
| 0 1 0 0 | × | 0 | = | 0 |
|-1 0 0 0 | | 0 | |-1 |
| 0 0 0 1 | | 1 | | 1 |
组合矩阵 R = Ry(90) × Rx(90):
| 0 0 1 0 | | 1 0 0 0 | | 0 1 0 0 |
| 0 1 0 0 | × | 0 0 -1 0 | = | 0 0 -1 0 |
|-1 0 0 0 | | 0 1 0 0 | |-1 0 0 0 |
| 0 0 0 1 | | 0 0 0 1 | | 0 0 0 1 |
验证:R × (1, 0, 0, 1) = (0, 0, -1, 1),与分步结果一致。
重要:旋转的顺序至关重要。Rx(90) × Ry(90) 和 Ry(90) × Rx(90) 的结果完全不同,因为三维旋转不满足交换律。
万向节锁(Gimbal Lock)
欧拉角有一个著名的问题——万向节锁(Gimbal Lock)。当中间旋转角(pitch)为 ±90 度时,第一个和第三个旋转轴会重合,丢失一个自由度:
当 pitch = 90 度时:
Rz(yaw) × Ry(90) × Rx(roll)
此时 yaw 和 roll 绕的是同一个轴,无法独立控制
这意味着某些旋转方向无法通过欧拉角直接表达。解决方案包括:
- 四元数(Quaternions):用 4 个数表示旋转,无万向节锁问题
- 轴角表示(Axis-Angle):用旋转轴 + 旋转角度表示
- 旋转矩阵直接插值:避免中间的欧拉角表示
C++ 实现
#include <iostream>
#include <cmath>
#include <iomanip>
using namespace std;
// 4x4 homogeneous transformation matrix
struct Mat4 {
double m[4][4];
static Mat4 identity() {
Mat4 r;
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
r.m[i][j] = (i == j) ? 1 : 0;
return r;
}
static Mat4 scale(double sx, double sy, double sz) {
Mat4 r = identity();
r.m[0][0] = sx;
r.m[1][1] = sy;
r.m[2][2] = sz;
return r;
}
static Mat4 rotateX(double angle) {
Mat4 r = identity();
double c = cos(angle), s = sin(angle);
r.m[1][1] = c; r.m[1][2] = -s;
r.m[2][1] = s; r.m[2][2] = c;
return r;
}
static Mat4 rotateY(double angle) {
Mat4 r = identity();
double c = cos(angle), s = sin(angle);
r.m[0][0] = c; r.m[0][2] = s;
r.m[2][0] = -s; r.m[2][2] = c;
return r;
}
static Mat4 rotateZ(double angle) {
Mat4 r = identity();
double c = cos(angle), s = sin(angle);
r.m[0][0] = c; r.m[0][1] = -s;
r.m[1][0] = s; r.m[1][1] = c;
return r;
}
Mat4 multiply(const Mat4& other) const {
Mat4 result;
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++) {
result.m[i][j] = 0;
for (int k = 0; k < 4; k++)
result.m[i][j] += m[i][k] * other.m[k][j];
}
return result;
}
void transform(double x, double y, double z,
double& ox, double& oy, double& oz) const {
ox = m[0][0]*x + m[0][1]*y + m[0][2]*z + m[0][3];
oy = m[1][0]*x + m[1][1]*y + m[1][2]*z + m[1][3];
oz = m[2][0]*x + m[2][1]*y + m[2][2]*z + m[2][3];
}
};
int main() {
cout << fixed << setprecision(4);
// Example 1: Scale
double px = 2, py = 3, pz = 4;
Mat4 S = Mat4::scale(2, 1, 3);
double ox, oy, oz;
S.transform(px, py, pz, ox, oy, oz);
cout << "Scale (2,1,3) on (2,3,4): (" << ox << ", " << oy << ", " << oz << ")" << endl;
// Example 2: Rotate Z 90 deg
Mat4 Rz90 = Mat4::rotateZ(M_PI / 2);
Rz90.transform(1, 0, 0, ox, oy, oz);
cout << "Rz(90) on (1,0,0): (" << round(ox) << ", " << round(oy) << ", " << round(oz) << ")" << endl;
// Example 3: Rotate X 90 deg
Mat4 Rx90 = Mat4::rotateX(M_PI / 2);
Rx90.transform(0, 1, 0, ox, oy, oz);
cout << "Rx(90) on (0,1,0): (" << round(ox) << ", " << round(oy) << ", " << round(oz) << ")" << endl;
// Example 4: Rotate Y 90 deg
Mat4 Ry90 = Mat4::rotateY(M_PI / 2);
Ry90.transform(0, 0, 1, ox, oy, oz);
cout << "Ry(90) on (0,0,1): (" << round(ox) << ", " << round(oy) << ", " << round(oz) << ")" << endl;
// Example 5: Composite Ry(90) * Rx(90)
Mat4 composite = Ry90.multiply(Rx90);
composite.transform(1, 0, 0, ox, oy, oz);
cout << "Ry(90)*Rx(90) on (1,0,0): (" << round(ox) << ", " << round(oy) << ", " << round(oz) << ")" << endl;
return 0;
}
C 实现
#include <stdio.h>
#include <math.h>
typedef double Mat4[4][4];
void mat4_identity(Mat4 m) {
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
m[i][j] = (i == j) ? 1 : 0;
}
void mat4_scale(double sx, double sy, double sz, Mat4 m) {
mat4_identity(m);
m[0][0] = sx;
m[1][1] = sy;
m[2][2] = sz;
}
void mat4_rotateX(double angle, Mat4 m) {
mat4_identity(m);
double c = cos(angle), s = sin(angle);
m[1][1] = c; m[1][2] = -s;
m[2][1] = s; m[2][2] = c;
}
void mat4_rotateY(double angle, Mat4 m) {
mat4_identity(m);
double c = cos(angle), s = sin(angle);
m[0][0] = c; m[0][2] = s;
m[2][0] = -s; m[2][2] = c;
}
void mat4_rotateZ(double angle, Mat4 m) {
mat4_identity(m);
double c = cos(angle), s = sin(angle);
m[0][0] = c; m[0][1] = -s;
m[1][0] = s; m[1][1] = c;
}
void mat4_multiply(Mat4 A, Mat4 B, Mat4 result) {
Mat4 temp = {{0}};
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
for (int k = 0; k < 4; k++)
temp[i][j] += A[i][k] * B[k][j];
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
result[i][j] = temp[i][j];
}
void mat4_transform(Mat4 m, double x, double y, double z,
double* ox, double* oy, double* oz) {
*ox = m[0][0]*x + m[0][1]*y + m[0][2]*z + m[0][3];
*oy = m[1][0]*x + m[1][1]*y + m[1][2]*z + m[1][3];
*oz = m[2][0]*x + m[2][1]*y + m[2][2]*z + m[2][3];
}
int main() {
double ox, oy, oz;
Mat4 S, Rz90, Rx90, Ry90, composite;
// Scale
mat4_scale(2, 1, 3, S);
mat4_transform(S, 2, 3, 4, &ox, &oy, &oz);
printf("Scale (2,1,3) on (2,3,4): (%g, %g, %g)\n", ox, oy, oz);
// Rotate Z 90
mat4_rotateZ(M_PI / 2, Rz90);
mat4_transform(Rz90, 1, 0, 0, &ox, &oy, &oz);
printf("Rz(90) on (1,0,0): (%g, %g, %g)\n",
round(ox), round(oy), round(oz));
// Rotate X 90
mat4_rotateX(M_PI / 2, Rx90);
mat4_transform(Rx90, 0, 1, 0, &ox, &oy, &oz);
printf("Rx(90) on (0,1,0): (%g, %g, %g)\n",
round(ox), round(oy), round(oz));
// Rotate Y 90
mat4_rotateY(M_PI / 2, Ry90);
mat4_transform(Ry90, 0, 0, 1, &ox, &oy, &oz);
printf("Ry(90) on (0,0,1): (%g, %g, %g)\n",
round(ox), round(oy), round(oz));
// Composite Ry(90) * Rx(90)
mat4_multiply(Ry90, Rx90, composite);
mat4_transform(composite, 1, 0, 0, &ox, &oy, &oz);
printf("Ry(90)*Rx(90) on (1,0,0): (%g, %g, %g)\n",
round(ox), round(oy), round(oz));
return 0;
}
Python 实现
import math
class Mat4:
"""4x4 homogeneous transformation matrix."""
def __init__(self):
self.m = [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]
@staticmethod
def identity():
return Mat4()
@staticmethod
def scale(sx, sy, sz):
r = Mat4()
r.m[0][0] = sx
r.m[1][1] = sy
r.m[2][2] = sz
return r
@staticmethod
def rotate_x(angle):
r = Mat4()
c, s = math.cos(angle), math.sin(angle)
r.m[1][1] = c; r.m[1][2] = -s
r.m[2][1] = s; r.m[2][2] = c
return r
@staticmethod
def rotate_y(angle):
r = Mat4()
c, s = math.cos(angle), math.sin(angle)
r.m[0][0] = c; r.m[0][2] = s
r.m[2][0] = -s; r.m[2][2] = c
return r
@staticmethod
def rotate_z(angle):
r = Mat4()
c, s = math.cos(angle), math.sin(angle)
r.m[0][0] = c; r.m[0][1] = -s
r.m[1][0] = s; r.m[1][1] = c
return r
def multiply(self, other):
result = Mat4()
for i in range(4):
for j in range(4):
result.m[i][j] = sum(
self.m[i][k] * other.m[k][j] for k in range(4)
)
return result
def transform(self, x, y, z):
ox = self.m[0][0]*x + self.m[0][1]*y + self.m[0][2]*z + self.m[0][3]
oy = self.m[1][0]*x + self.m[1][1]*y + self.m[1][2]*z + self.m[1][3]
oz = self.m[2][0]*x + self.m[2][1]*y + self.m[2][2]*z + self.m[2][3]
return ox, oy, oz
# Scale
S = Mat4.scale(2, 1, 3)
print(f"Scale (2,1,3) on (2,3,4): {S.transform(2, 3, 4)}")
# Rotate Z 90
Rz90 = Mat4.rotate_z(math.pi / 2)
ox, oy, oz = Rz90.transform(1, 0, 0)
print(f"Rz(90) on (1,0,0): ({round(ox)}, {round(oy)}, {round(oz)})")
# Rotate X 90
Rx90 = Mat4.rotate_x(math.pi / 2)
ox, oy, oz = Rx90.transform(0, 1, 0)
print(f"Rx(90) on (0,1,0): ({round(ox)}, {round(oy)}, {round(oz)})")
# Rotate Y 90
Ry90 = Mat4.rotate_y(math.pi / 2)
ox, oy, oz = Ry90.transform(0, 0, 1)
print(f"Ry(90) on (0,0,1): ({round(ox)}, {round(oy)}, {round(oz)})")
# Composite Ry(90) * Rx(90)
composite = Ry90.multiply(Rx90)
ox, oy, oz = composite.transform(1, 0, 0)
print(f"Ry(90)*Rx(90) on (1,0,0): ({round(ox)}, {round(oy)}, {round(oz)})")
Go 实现
package main
import (
"fmt"
"math"
)
// Mat4 represents a 4x4 homogeneous transformation matrix
type Mat4 [4][4]float64
func mat4Identity() Mat4 {
return Mat4{
{1, 0, 0, 0},
{0, 1, 0, 0},
{0, 0, 1, 0},
{0, 0, 0, 1},
}
}
func mat4Scale(sx, sy, sz float64) Mat4 {
m := mat4Identity()
m[0][0] = sx
m[1][1] = sy
m[2][2] = sz
return m
}
func mat4RotateX(angle float64) Mat4 {
m := mat4Identity()
c, s := math.Cos(angle), math.Sin(angle)
m[1][1] = c; m[1][2] = -s
m[2][1] = s; m[2][2] = c
return m
}
func mat4RotateY(angle float64) Mat4 {
m := mat4Identity()
c, s := math.Cos(angle), math.Sin(angle)
m[0][0] = c; m[0][2] = s
m[2][0] = -s; m[2][2] = c
return m
}
func mat4RotateZ(angle float64) Mat4 {
m := mat4Identity()
c, s := math.Cos(angle), math.Sin(angle)
m[0][0] = c; m[0][1] = -s
m[1][0] = s; m[1][1] = c
return m
}
func (a Mat4) multiply(b Mat4) Mat4 {
var result Mat4
for i := 0; i < 4; i++ {
for j := 0; j < 4; j++ {
for k := 0; k < 4; k++ {
result[i][j] += a[i][k] * b[k][j]
}
}
}
return result
}
func (m Mat4) transform(x, y, z float64) (float64, float64, float64) {
ox := m[0][0]*x + m[0][1]*y + m[0][2]*z + m[0][3]
oy := m[1][0]*x + m[1][1]*y + m[1][2]*z + m[1][3]
oz := m[2][0]*x + m[2][1]*y + m[2][2]*z + m[2][3]
return ox, oy, oz
}
func main() {
// Scale
S := mat4Scale(2, 1, 3)
ox, oy, oz := S.transform(2, 3, 4)
fmt.Printf("Scale (2,1,3) on (2,3,4): (%g, %g, %g)\n", ox, oy, oz)
// Rotate Z 90
Rz90 := mat4RotateZ(math.Pi / 2)
ox, oy, oz = Rz90.transform(1, 0, 0)
fmt.Printf("Rz(90) on (1,0,0): (%g, %g, %g)\n",
math.Round(ox), math.Round(oy), math.Round(oz))
// Rotate X 90
Rx90 := mat4RotateX(math.Pi / 2)
ox, oy, oz = Rx90.transform(0, 1, 0)
fmt.Printf("Rx(90) on (0,1,0): (%g, %g, %g)\n",
math.Round(ox), math.Round(oy), math.Round(oz))
// Rotate Y 90
Ry90 := mat4RotateY(math.Pi / 2)
ox, oy, oz = Ry90.transform(0, 0, 1)
fmt.Printf("Ry(90) on (0,0,1): (%g, %g, %g)\n",
math.Round(ox), math.Round(oy), math.Round(oz))
// Composite Ry(90) * Rx(90)
composite := Ry90.multiply(Rx90)
ox, oy, oz = composite.transform(1, 0, 0)
fmt.Printf("Ry(90)*Rx(90) on (1,0,0): (%g, %g, %g)\n",
math.Round(ox), math.Round(oy), math.Round(oz))
}
运行该程序将输出:
Scale (2,1,3) on (2,3,4): (4, 3, 12)
Rz(90) on (1,0,0): (0, 1, 0)
Rx(90) on (0,1,0): (0, 0, 1)
Ry(90) on (0,0,1): (1, 0, 0)
Ry(90)*Rx(90) on (1,0,0): (0, 0, -1)
三维旋转与缩放矩阵的性质
旋转矩阵的性质
三维旋转矩阵(Rotation Matrix)是正交矩阵(Orthogonal Matrix),具有以下性质:
- 正交性:R^T = R^(-1),转置即逆,无需真正的矩阵求逆
- 行列式为 1:det(R) = 1,保持体积不变
- 保持距离:||R × v|| = ||v||,旋转不改变向量长度
- 保持角度:v1 · v2 = (R × v1) · (R × v2),旋转不改变向量间夹角
缩放矩阵的性质
- 对角矩阵:缩放矩阵是对角矩阵,计算高效
- 均匀缩放:当 s = sx = sy = sz 时,与旋转矩阵可交换 S(s) × R = R × S(s)
- 非均匀缩放:与旋转矩阵不满足交换律
旋转组合
| 性质 | 说明 |
|---|---|
| 封闭性 | 两个旋转的组合仍为旋转 |
| 结合律 | (R1 × R2) × R3 = R1 × (R2 × R3) |
| 不满足交换律 | R1 × R2 ≠ R2 × R1(三维旋转) |
| 单位元 | I(单位矩阵) |
| 逆元 | R^(-1) = R^T |
4x4 矩阵布局总结
| Rxx Rxy Rxz Tx | R: 3x3 旋转/缩放/剪切子矩阵
| Ryx Ryy Ryz Ty | T: 3x1 平移向量
| Rzx Rzy Rzz Tz | 0: 1x3 齐次坐标行
| 0 0 0 1 | 1: 齐次坐标常数
| 变换类型 | R 子矩阵 | T 列 |
|---|---|---|
| 单位矩阵 | I(单位) | (0, 0, 0) |
| 平移 | I | (tx, ty, tz) |
| 缩放 | 对角 (sx, sy, sz) | (0, 0, 0) |
| 绕 X 轴旋转 | yz 平面 2D 旋转 | (0, 0, 0) |
| 绕 Y 轴旋转 | xz 平面 2D 旋转 | (0, 0, 0) |
| 绕 Z 轴旋转 | xy 平面 2D 旋转 | (0, 0, 0) |
| 组合 | R1 × R2 | 取决于组合方式 |

浙公网安备 33010602011771号