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 取决于组合方式
posted @ 2026-04-17 23:42  游翔  阅读(35)  评论(0)    收藏  举报