9-2 二维旋转与平移矩阵

二维旋转与平移矩阵(2D Rotation and Translation Matrices)

在二维图形变换中,旋转(Rotation)和平移(Translation)是最基本的两种操作。单独使用 2x2 矩阵可以实现旋转,但无法表示平移——因为平移本质上是加法操作,而非乘法。为了在同一次矩阵乘法中同时完成旋转和平移,我们引入齐次坐标(Homogeneous Coordinates):将二维点 (x, y) 表示为 (x, y, 1),从而使用 3x3 矩阵统一描述所有仿射变换(Affine Transformation)。

这一技巧是计算机图形学(Computer Graphics)、机器人学(Robotics)和游戏开发中的基础工具。掌握它之后,任意复杂的二维变换都可以通过矩阵连乘来组合。


齐次坐标(Homogeneous Coordinates)

齐次坐标的核心思想很简单:在二维坐标末尾添加一个分量 1,将 (x, y) 扩展为 (x, y, 1)。这样一来:

  • 旋转:用 3x3 矩阵的左上角 2x2 子矩阵表示,第三行和第三列保持不变
  • 平移:用 3x3 矩阵的第三列前两行表示,这是 2x2 矩阵做不到的
  • 缩放(Scaling):用 3x3 矩阵的对角线元素表示
  • 剪切(Shearing):用 3x3 矩阵的非对角线元素表示

关键优势在于:所有这些变换都可以通过 3x3 矩阵乘法来组合。例如,先旋转再平移,只需将两个 3x3 矩阵相乘,再用结果矩阵乘以点的齐次坐标即可。

普通 2D 坐标:       齐次坐标:
(x, y)       →     (x, y, 1)

2D 点作为列向量:

    | x |              | x |
    | y |       →      | y |
                       | 1 |

矩阵变换的一般形式:

| a  b  tx |   | x |   | ax + by + tx |
| c  d  ty | × | y | = | cx + dy + ty |
| 0  0   1 |   | 1 |   |      1       |

其中左上角的 2x2 子矩阵 [[a,b],[c,d]] 负责旋转、缩放和剪切,右侧的列 [tx, ty] 负责平移。


平移矩阵(Translation Matrix)

平移矩阵(Translation Matrix)的作用是将点沿着 x 轴移动 tx、沿着 y 轴移动 ty:

| 1  0  tx |   | x |   | x + tx |
| 0  1  ty | × | y | = | y + ty |
| 0  0   1 |   | 1 |   |    1   |

例如,将点 (2, 3) 平移 tx=5, ty=1:

| 1  0  5 |   | 2 |   | 2 + 5 |   | 7 |
| 0  1  1 | × | 3 | = | 3 + 1 | = | 4 |
| 0  0  1 |   | 1 |   |   1   |   | 1 |

结果为 (7, 4)。齐次坐标使平移变成了矩阵乘法,而非单独的加法操作。

C++ 实现

#include <iostream>
#include <cmath>
using namespace std;

// Translate point (x,y) by (tx,ty) using homogeneous coordinates
void translate(double x, double y, double tx, double ty, double& ox, double& oy) {
    // Matrix: | 1  0  tx |
    //         | 0  1  ty |
    //         | 0  0   1 |
    ox = 1 * x + 0 * y + tx * 1;
    oy = 0 * x + 1 * y + ty * 1;
}

int main() {
    double x = 2, y = 3;
    double tx = 5, ty = 1;

    double ox, oy;
    translate(x, y, tx, ty, ox, oy);

    cout << "Original point: (" << x << ", " << y << ")" << endl;
    cout << "Translation:    tx=" << tx << ", ty=" << ty << endl;
    cout << "Result:         (" << ox << ", " << oy << ")" << endl;

    return 0;
}

C 实现

#include <stdio.h>

// Translate point (x,y) by (tx,ty) using homogeneous coordinates
void translate(double x, double y, double tx, double ty, double* ox, double* oy) {
    // Matrix: | 1  0  tx |
    //         | 0  1  ty |
    //         | 0  0   1 |
    *ox = 1 * x + 0 * y + tx * 1;
    *oy = 0 * x + 1 * y + ty * 1;
}

int main() {
    double x = 2, y = 3;
    double tx = 5, ty = 1;

    double ox, oy;
    translate(x, y, tx, ty, &ox, &oy);

    printf("Original point: (%g, %g)\n", x, y);
    printf("Translation:    tx=%g, ty=%g\n", tx, ty);
    printf("Result:         (%g, %g)\n", ox, oy);

    return 0;
}

Python 实现

import math

def translate(x, y, tx, ty):
    """Translate point (x,y) by (tx,ty) using homogeneous coordinates."""
    # Matrix: | 1  0  tx |
    #         | 0  1  ty |
    #         | 0  0   1 |
    ox = 1 * x + 0 * y + tx * 1
    oy = 0 * x + 1 * y + ty * 1
    return ox, oy


x, y = 2, 3
tx, ty = 5, 1

ox, oy = translate(x, y, tx, ty)

print(f"Original point: ({x}, {y})")
print(f"Translation:    tx={tx}, ty={ty}")
print(f"Result:         ({ox}, {oy})")

Go 实现

package main

import "fmt"

// translate moves point (x,y) by (tx,ty) using homogeneous coordinates
func translate(x, y, tx, ty float64) (float64, float64) {
	// Matrix: | 1  0  tx |
	//         | 0  1  ty |
	//         | 0  0   1 |
	ox := 1*x + 0*y + tx*1
	oy := 0*x + 1*y + ty*1
	return ox, oy
}

func main() {
	x, y := 2.0, 3.0
	tx, ty := 5.0, 1.0

	ox, oy := translate(x, y, tx, ty)

	fmt.Printf("Original point: (%g, %g)\n", x, y)
	fmt.Printf("Translation:    tx=%g, ty=%g\n", tx, ty)
	fmt.Printf("Result:         (%g, %g)\n", ox, oy)
}

以上四个版本都将平移操作显式展开为齐次坐标矩阵乘法。虽然直接做加法 x + tx 更简单,但这里刻意使用矩阵形式,是为了与后续的旋转、缩放等变换保持统一的接口——最终所有变换都可以通过 3x3 矩阵乘法来组合。

运行该程序将输出:

Original point: (2, 3)
Translation:    tx=5, ty=1
Result:         (7, 4)

旋转矩阵(齐次坐标版)

二维旋转矩阵(Rotation Matrix)使用齐次坐标的 3x3 形式如下:

| cosθ  -sinθ  0 |   | x |   | x·cosθ - y·sinθ |
| sinθ   cosθ  0 | × | y | = | x·sinθ + y·cosθ |
|   0      0   1 |   | 1 |   |        1        |

与 2x2 旋转矩阵相比,3x3 版本多了一行一列,旋转的核心计算完全相同。但 3x3 形式可以与平移矩阵相乘组合,这是 2x2 矩阵做不到的。

以 90 度旋转为例:cos(90) = 0, sin(90) = 1,旋转点 (3, 1):

| 0  -1  0 |   | 3 |   | -1 |
| 1   0  0 | × | 1 | = |  3 |
| 0   0  1 |   | 1 |   |  1 |

结果为 (-1, 3),即点 (3, 1) 绕原点逆时针旋转 90 度后到达 (-1, 3)。

C++ 实现

#include <iostream>
#include <cmath>
using namespace std;

// Rotate point (x,y) by angle (radians) around origin using 3x3 homogeneous matrix
void rotate(double x, double y, double angle, double& ox, double& oy) {
    double c = cos(angle);
    double s = sin(angle);
    // Matrix: | c  -s  0 |
    //         | s   c  0 |
    //         | 0   0  1 |
    ox = c * x + (-s) * y;
    oy = s * x + c * y;
}

int main() {
    double x = 3, y = 1;
    double angle = M_PI / 2;  // 90 degrees in radians

    double ox, oy;
    rotate(x, y, angle, ox, oy);

    cout << "Original point: (" << x << ", " << y << ")" << endl;
    cout << "Rotation angle: 90 degrees" << endl;
    cout << "Result:         (" << round(ox) << ", " << round(oy) << ")" << endl;

    return 0;
}

C 实现

#include <stdio.h>
#include <math.h>

// Rotate point (x,y) by angle (radians) around origin using 3x3 homogeneous matrix
void rotate(double x, double y, double angle, double* ox, double* oy) {
    double c = cos(angle);
    double s = sin(angle);
    // Matrix: | c  -s  0 |
    //         | s   c  0 |
    //         | 0   0  1 |
    *ox = c * x + (-s) * y;
    *oy = s * x + c * y;
}

int main() {
    double x = 3, y = 1;
    double angle = M_PI / 2;  // 90 degrees in radians

    double ox, oy;
    rotate(x, y, angle, &ox, &oy);

    printf("Original point: (%g, %g)\n", x, y);
    printf("Rotation angle: 90 degrees\n");
    printf("Result:         (%g, %g)\n", round(ox), round(oy));

    return 0;
}

Python 实现

import math

def rotate(x, y, angle):
    """Rotate point (x,y) by angle (radians) around origin using 3x3 homogeneous matrix."""
    c = math.cos(angle)
    s = math.sin(angle)
    # Matrix: | c  -s  0 |
    #         | s   c  0 |
    #         | 0   0  1 |
    ox = c * x + (-s) * y
    oy = s * x + c * y
    return ox, oy


x, y = 3, 1
angle = math.pi / 2  # 90 degrees in radians

ox, oy = rotate(x, y, angle)

print(f"Original point: ({x}, {y})")
print(f"Rotation angle: 90 degrees")
print(f"Result:         ({round(ox)}, {round(oy)})")

Go 实现

package main

import (
	"fmt"
	"math"
)

// rotate rotates point (x,y) by angle (radians) around origin using 3x3 homogeneous matrix
func rotate(x, y, angle float64) (float64, float64) {
	c := math.Cos(angle)
	s := math.Sin(angle)
	// Matrix: | c  -s  0 |
	//         | s   c  0 |
	//         | 0   0  1 |
	ox := c*x + (-s)*y
	oy := s*x + c*y
	return ox, oy
}

func main() {
	x, y := 3.0, 1.0
	angle := math.Pi / 2 // 90 degrees in radians

	ox, oy := rotate(x, y, angle)

	fmt.Printf("Original point: (%g, %g)\n", x, y)
	fmt.Println("Rotation angle: 90 degrees")
	fmt.Printf("Result:         (%g, %g)\n", math.Round(ox), math.Round(oy))
}

旋转矩阵的核心计算与 2x2 版本完全相同——都是 x' = x*cos - y*siny' = x*sin + y*cos。3x3 形式的价值在于:它可以与平移矩阵通过矩阵乘法组合,实现"绕任意点旋转"等复合变换。

运行该程序将输出:

Original point: (3, 1)
Rotation angle: 90 degrees
Result:         (-1, 3)

组合变换:旋转+平移

齐次坐标最核心的应用就是组合变换(Composite Transformation)。最经典的例子是"绕任意点旋转":将点 (cx, cy) 平移到原点,执行旋转,再平移回去。

数学表达式为:

T(cx,cy) × R(θ) × T(-cx,-cy) × point

展开为矩阵:

| 1  0  cx |   | c  -s  0 |   | 1  0  -cx |   | x |
| 0  1  cy | × | s   c  0 | × | 0  1  -cy | × | y |
| 0  0   1 |   | 0   0  1 |   | 0  0    1 |   | 1 |

以具体例子说明:将点 (2, 1) 绕中心点 (1, 1) 逆时针旋转 90 度。

Step 1: 平移到原点(减去中心坐标)
  T(-1,-1) × (2,1,1) = (1, 0, 1)

Step 2: 绕原点旋转 90 度
  R(90) × (1, 0, 1) = (0, 1, 1)

Step 3: 平移回去(加回中心坐标)
  T(1,1) × (0, 1, 1) = (1, 2, 1)

结果为 (1, 2)。

C++ 实现

#include <iostream>
#include <cmath>
using namespace std;

// 3x3 matrix multiplication: result = A × B
void mat3Multiply(double A[3][3], double B[3][3], double result[3][3]) {
    double temp[3][3] = {{0}};
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            for (int k = 0; k < 3; k++) {
                temp[i][j] += A[i][k] * B[k][j];
            }
        }
    }
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++)
            result[i][j] = temp[i][j];
}

// Apply 3x3 transformation matrix to point (x,y)
void transformPoint(double M[3][3], double x, double y, double& ox, double& oy) {
    double p[3] = {x, y, 1};
    ox = M[0][0] * p[0] + M[0][1] * p[1] + M[0][2] * p[2];
    oy = M[1][0] * p[0] + M[1][1] * p[1] + M[1][2] * p[2];
}

int main() {
    double cx = 1, cy = 1;              // Center of rotation
    double angle = M_PI / 2;            // 90 degrees
    double c = cos(angle), s = sin(angle);

    // T(cx,cy): translate to center
    double T1[3][3] = {{1, 0, cx}, {0, 1, cy}, {0, 0, 1}};
    // R(theta): rotate
    double R[3][3] = {{c, -s, 0}, {s, c, 0}, {0, 0, 1}};
    // T(-cx,-cy): translate to origin
    double T2[3][3] = {{1, 0, -cx}, {0, 1, -cy}, {0, 0, 1}};

    // Composite: T1 × R × T2
    double temp[3][3], composite[3][3];
    mat3Multiply(R, T2, temp);          // R × T2
    mat3Multiply(T1, temp, composite);  // T1 × (R × T2)

    double x = 2, y = 1;
    double ox, oy;
    transformPoint(composite, x, y, ox, oy);

    cout << "Rotate (" << x << ", " << y << ") 90 deg around (" << cx << ", " << cy << ")" << endl;
    cout << "Result: (" << round(ox) << ", " << round(oy) << ")" << endl;

    return 0;
}

C 实现

#include <stdio.h>
#include <math.h>

// 3x3 matrix multiplication: result = A * B
void mat3_multiply(double A[3][3], double B[3][3], double result[3][3]) {
    double temp[3][3] = {{0}};
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            for (int k = 0; k < 3; k++) {
                temp[i][j] += A[i][k] * B[k][j];
            }
        }
    }
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++)
            result[i][j] = temp[i][j];
}

// Apply 3x3 transformation matrix to point (x,y)
void transform_point(double M[3][3], double x, double y, double* ox, double* oy) {
    double p[3] = {x, y, 1};
    *ox = M[0][0]*p[0] + M[0][1]*p[1] + M[0][2]*p[2];
    *oy = M[1][0]*p[0] + M[1][1]*p[1] + M[1][2]*p[2];
}

int main() {
    double cx = 1, cy = 1;
    double angle = M_PI / 2;
    double c = cos(angle), s = sin(angle);

    // T(cx,cy): translate to center
    double T1[3][3] = {{1, 0, cx}, {0, 1, cy}, {0, 0, 1}};
    // R(theta): rotate
    double R[3][3] = {{c, -s, 0}, {s, c, 0}, {0, 0, 1}};
    // T(-cx,-cy): translate to origin
    double T2[3][3] = {{1, 0, -cx}, {0, 1, -cy}, {0, 0, 1}};

    // Composite: T1 * R * T2
    double temp[3][3], composite[3][3];
    mat3_multiply(R, T2, temp);
    mat3_multiply(T1, temp, composite);

    double x = 2, y = 1;
    double ox, oy;
    transform_point(composite, x, y, &ox, &oy);

    printf("Rotate (%g, %g) 90 deg around (%g, %g)\n", x, y, cx, cy);
    printf("Result: (%g, %g)\n", round(ox), round(oy));

    return 0;
}

Python 实现

import math

def mat3_multiply(A, B):
    """Multiply two 3x3 matrices."""
    result = [[0]*3 for _ in range(3)]
    for i in range(3):
        for j in range(3):
            for k in range(3):
                result[i][j] += A[i][k] * B[k][j]
    return result

def transform_point(M, x, y):
    """Apply 3x3 transformation matrix to point (x,y)."""
    ox = M[0][0]*x + M[0][1]*y + M[0][2]*1
    oy = M[1][0]*x + M[1][1]*y + M[1][2]*1
    return ox, oy


cx, cy = 1, 1
angle = math.pi / 2
c, s = math.cos(angle), math.sin(angle)

# T(cx,cy): translate to center
T1 = [[1, 0, cx], [0, 1, cy], [0, 0, 1]]
# R(theta): rotate
R = [[c, -s, 0], [s, c, 0], [0, 0, 1]]
# T(-cx,-cy): translate to origin
T2 = [[1, 0, -cx], [0, 1, -cy], [0, 0, 1]]

# Composite: T1 * R * T2
temp = mat3_multiply(R, T2)
composite = mat3_multiply(T1, temp)

x, y = 2, 1
ox, oy = transform_point(composite, x, y)

print(f"Rotate ({x}, {y}) 90 deg around ({cx}, {cy})")
print(f"Result: ({round(ox)}, {round(oy)})")

Go 实现

package main

import (
	"fmt"
	"math"
)

// mat3Multiply multiplies two 3x3 matrices
func mat3Multiply(A, B [3][3]float64) [3][3]float64 {
	var result [3][3]float64
	for i := 0; i < 3; i++ {
		for j := 0; j < 3; j++ {
			for k := 0; k < 3; k++ {
				result[i][j] += A[i][k] * B[k][j]
			}
		}
	}
	return result
}

// transformPoint applies 3x3 transformation matrix to point (x,y)
func transformPoint(M [3][3]float64, x, y float64) (float64, float64) {
	ox := M[0][0]*x + M[0][1]*y + M[0][2]*1
	oy := M[1][0]*x + M[1][1]*y + M[1][2]*1
	return ox, oy
}

func main() {
	cx, cy := 1.0, 1.0
	angle := math.Pi / 2
	c, s := math.Cos(angle), math.Sin(angle)

	// T(cx,cy): translate to center
	T1 := [3][3]float64{{1, 0, cx}, {0, 1, cy}, {0, 0, 1}}
	// R(theta): rotate
	R := [3][3]float64{{c, -s, 0}, {s, c, 0}, {0, 0, 1}}
	// T(-cx,-cy): translate to origin
	T2 := [3][3]float64{{1, 0, -cx}, {0, 1, -cy}, {0, 0, 1}}

	// Composite: T1 * R * T2
	temp := mat3Multiply(R, T2)
	composite := mat3Multiply(T1, temp)

	x, y := 2.0, 1.0
	ox, oy := transformPoint(composite, x, y)

	fmt.Printf("Rotate (%g, %g) 90 deg around (%g, %g)\n", x, y, cx, cy)
	fmt.Printf("Result: (%g, %g)\n", math.Round(ox), math.Round(oy))
}

这段代码展示了齐次坐标最核心的应用。三个矩阵按从右到左的顺序作用于点:先 T(-cx,-cy) 将中心移到原点,再 R 执行旋转,最后 T(cx,cy) 移回原位。通过 mat3Multiply 将三个矩阵合并为一个组合矩阵,只需一次矩阵-向量乘法即可完成全部变换。

运行该程序将输出:

Rotate (2, 1) 90 deg around (1, 1)
Result: (1, 2)

组合变换:平移+旋转+缩放

更一般的仿射变换可以同时包含平移(Translation)、旋转(Rotation)和缩放(Scaling)。缩放矩阵(Scale Matrix)的齐次坐标形式为:

| sx  0   0 |   | x |   | sx * x |
| 0   sy  0 | × | y | = | sy * y |
| 0   0   1 |   | 1 |   |    1   |

完整的组合变换顺序为:先缩放,再旋转,最后平移。对应的矩阵表达式:

T(tx,ty) × R(θ) × S(sx,sy) × point

矩阵乘法的顺序非常重要:从右到左依次作用于点。先执行最右边的缩放,然后旋转,最后平移。如果改变顺序,结果会完全不同。

以具体例子说明:对点 (1, 0) 先缩放 (2, 3),再旋转 90 度,最后平移 (5, 1)。

Step 1: 缩放
  S(2,3) × (1,0,1) = (2, 0, 1)

Step 2: 旋转 90 度
  R(90) × (2, 0, 1) = (0, 2, 1)

Step 3: 平移
  T(5,1) × (0, 2, 1) = (5, 3, 1)

结果为 (5, 3)。如果先平移再缩放,结果将完全不同。

C++ 实现

#include <iostream>
#include <cmath>
using namespace std;

// 3x3 matrix multiplication: result = A * B
void mat3Multiply(double A[3][3], double B[3][3], double result[3][3]) {
    double temp[3][3] = {{0}};
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++)
            for (int k = 0; k < 3; k++)
                temp[i][j] += A[i][k] * B[k][j];
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++)
            result[i][j] = temp[i][j];
}

// Apply 3x3 transformation matrix to point (x,y)
void transformPoint(double M[3][3], double x, double y, double& ox, double& oy) {
    ox = M[0][0]*x + M[0][1]*y + M[0][2];
    oy = M[1][0]*x + M[1][1]*y + M[1][2];
}

int main() {
    double tx = 5, ty = 1;              // Translation
    double angle = M_PI / 2;            // 90 degrees
    double sx = 2, sy = 3;             // Scaling
    double c = cos(angle), s = sin(angle);

    // T(tx,ty): translate
    double T[3][3] = {{1, 0, tx}, {0, 1, ty}, {0, 0, 1}};
    // R(theta): rotate
    double R[3][3] = {{c, -s, 0}, {s, c, 0}, {0, 0, 1}};
    // S(sx,sy): scale
    double S[3][3] = {{sx, 0, 0}, {0, sy, 0}, {0, 0, 1}};

    // Composite: T * R * S (applied right to left: scale -> rotate -> translate)
    double temp[3][3], composite[3][3];
    mat3Multiply(R, S, temp);           // R * S
    mat3Multiply(T, temp, composite);   // T * (R * S)

    double x = 1, y = 0;
    double ox, oy;
    transformPoint(composite, x, y, ox, oy);

    cout << "Point: (" << x << ", " << y << ")" << endl;
    cout << "Scale (" << sx << ", " << sy << "), Rotate 90 deg, Translate (" << tx << ", " << ty << ")" << endl;
    cout << "Result: (" << round(ox) << ", " << round(oy) << ")" << endl;

    return 0;
}

C 实现

#include <stdio.h>
#include <math.h>

// 3x3 matrix multiplication: result = A * B
void mat3_multiply(double A[3][3], double B[3][3], double result[3][3]) {
    double temp[3][3] = {{0}};
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++)
            for (int k = 0; k < 3; k++)
                temp[i][j] += A[i][k] * B[k][j];
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++)
            result[i][j] = temp[i][j];
}

// Apply 3x3 transformation matrix to point (x,y)
void transform_point(double M[3][3], double x, double y, double* ox, double* oy) {
    *ox = M[0][0]*x + M[0][1]*y + M[0][2];
    *oy = M[1][0]*x + M[1][1]*y + M[1][2];
}

int main() {
    double tx = 5, ty = 1;
    double angle = M_PI / 2;
    double sx = 2, sy = 3;
    double c = cos(angle), s = sin(angle);

    double T[3][3] = {{1, 0, tx}, {0, 1, ty}, {0, 0, 1}};
    double R[3][3] = {{c, -s, 0}, {s, c, 0}, {0, 0, 1}};
    double S[3][3] = {{sx, 0, 0}, {0, sy, 0}, {0, 0, 1}};

    double temp[3][3], composite[3][3];
    mat3_multiply(R, S, temp);
    mat3_multiply(T, temp, composite);

    double x = 1, y = 0;
    double ox, oy;
    transform_point(composite, x, y, &ox, &oy);

    printf("Point: (%g, %g)\n", x, y);
    printf("Scale (%g, %g), Rotate 90 deg, Translate (%g, %g)\n", sx, sy, tx, ty);
    printf("Result: (%g, %g)\n", round(ox), round(oy));

    return 0;
}

Python 实现

import math

def mat3_multiply(A, B):
    """Multiply two 3x3 matrices."""
    result = [[0]*3 for _ in range(3)]
    for i in range(3):
        for j in range(3):
            for k in range(3):
                result[i][j] += A[i][k] * B[k][j]
    return result

def transform_point(M, x, y):
    """Apply 3x3 transformation matrix to point (x,y)."""
    ox = M[0][0]*x + M[0][1]*y + M[0][2]
    oy = M[1][0]*x + M[1][1]*y + M[1][2]
    return ox, oy


tx, ty = 5, 1
angle = math.pi / 2
sx, sy = 2, 3
c, s = math.cos(angle), math.sin(angle)

T = [[1, 0, tx], [0, 1, ty], [0, 0, 1]]
R = [[c, -s, 0], [s, c, 0], [0, 0, 1]]
S = [[sx, 0, 0], [0, sy, 0], [0, 0, 1]]

# Composite: T * R * S (scale -> rotate -> translate)
temp = mat3_multiply(R, S)
composite = mat3_multiply(T, temp)

x, y = 1, 0
ox, oy = transform_point(composite, x, y)

print(f"Point: ({x}, {y})")
print(f"Scale ({sx}, {sy}), Rotate 90 deg, Translate ({tx}, {ty})")
print(f"Result: ({round(ox)}, {round(oy)})")

Go 实现

package main

import (
	"fmt"
	"math"
)

// mat3Multiply multiplies two 3x3 matrices
func mat3Multiply(A, B [3][3]float64) [3][3]float64 {
	var result [3][3]float64
	for i := 0; i < 3; i++ {
		for j := 0; j < 3; j++ {
			for k := 0; k < 3; k++ {
				result[i][j] += A[i][k] * B[k][j]
			}
		}
	}
	return result
}

// transformPoint applies 3x3 transformation matrix to point (x,y)
func transformPoint(M [3][3]float64, x, y float64) (float64, float64) {
	ox := M[0][0]*x + M[0][1]*y + M[0][2]
	oy := M[1][0]*x + M[1][1]*y + M[1][2]
	return ox, oy
}

func main() {
	tx, ty := 5.0, 1.0
	angle := math.Pi / 2
	sx, sy := 2.0, 3.0
	c, s := math.Cos(angle), math.Sin(angle)

	T := [3][3]float64{{1, 0, tx}, {0, 1, ty}, {0, 0, 1}}
	R := [3][3]float64{{c, -s, 0}, {s, c, 0}, {0, 0, 1}}
	S := [3][3]float64{{sx, 0, 0}, {0, sy, 0}, {0, 0, 1}}

	// Composite: T * R * S (scale -> rotate -> translate)
	temp := mat3Multiply(R, S)
	composite := mat3Multiply(T, temp)

	x, y := 1.0, 0.0
	ox, oy := transformPoint(composite, x, y)

	fmt.Printf("Point: (%g, %g)\n", x, y)
	fmt.Printf("Scale (%g, %g), Rotate 90 deg, Translate (%g, %g)\n", sx, sy, tx, ty)
	fmt.Printf("Result: (%g, %g)\n", math.Round(ox), math.Round(oy))
}

这段代码演示了三种基本变换的组合。关键点在于矩阵乘法的顺序:T * R * S 表示先缩放(S),再旋转(R),最后平移(T)。矩阵从右向左依次作用于点向量。通过预先将三个矩阵合并为一个组合矩阵,无论包含多少步变换,对每个点的处理都只需一次矩阵乘法。

运行该程序将输出:

Point: (1, 0)
Scale (2, 3), Rotate 90 deg, Translate (5, 1)
Result: (5, 3)

完整实现

以下是一个完整的 3x3 齐次坐标矩阵库,支持单位矩阵(Identity)、平移(Translate)、旋转(Rotate)、缩放(Scale)、矩阵乘法(Multiply)和点变换(Transform Point)。最后用一个测试案例展示:将一个三角形绕其中心旋转 45 度。

C++ 实现

#include <iostream>
#include <cmath>
#include <iomanip>
using namespace std;

// 3x3 homogeneous transformation matrix
struct Mat3 {
    double m[3][3];

    // Identity matrix
    static Mat3 identity() {
        Mat3 r;
        for (int i = 0; i < 3; i++)
            for (int j = 0; j < 3; j++)
                r.m[i][j] = (i == j) ? 1 : 0;
        return r;
    }

    // Translation matrix
    static Mat3 translate(double tx, double ty) {
        Mat3 r = identity();
        r.m[0][2] = tx;
        r.m[1][2] = ty;
        return r;
    }

    // Rotation matrix (angle in radians)
    static Mat3 rotate(double angle) {
        Mat3 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;
    }

    // Scale matrix
    static Mat3 scale(double sx, double sy) {
        Mat3 r = identity();
        r.m[0][0] = sx;
        r.m[1][1] = sy;
        return r;
    }

    // Matrix multiplication: this * other
    Mat3 multiply(const Mat3& other) const {
        Mat3 result;
        for (int i = 0; i < 3; i++)
            for (int j = 0; j < 3; j++) {
                result.m[i][j] = 0;
                for (int k = 0; k < 3; k++)
                    result.m[i][j] += m[i][k] * other.m[k][j];
            }
        return result;
    }

    // Transform point (x,y)
    void transform(double x, double y, double& ox, double& oy) const {
        ox = m[0][0]*x + m[0][1]*y + m[0][2];
        oy = m[1][0]*x + m[1][1]*y + m[1][2];
    }
};

int main() {
    // Triangle vertices
    double tri[3][2] = {{0, 0}, {2, 0}, {1, 2}};
    // Compute centroid
    double cx = (tri[0][0] + tri[1][0] + tri[2][0]) / 3.0;
    double cy = (tri[0][1] + tri[1][1] + tri[2][1]) / 3.0;

    cout << fixed << setprecision(4);
    cout << "Triangle vertices:" << endl;
    for (int i = 0; i < 3; i++)
        cout << "  (" << tri[i][0] << ", " << tri[i][1] << ")" << endl;
    cout << "Centroid: (" << cx << ", " << cy << ")" << endl;

    // Rotate 45 degrees around centroid
    double angle = M_PI / 4;
    Mat3 composite = Mat3::translate(cx, cy)
        .multiply(Mat3::rotate(angle))
        .multiply(Mat3::translate(-cx, -cy));

    cout << "\nRotate 45 deg around centroid:" << endl;
    for (int i = 0; i < 3; i++) {
        double ox, oy;
        composite.transform(tri[i][0], tri[i][1], ox, oy);
        cout << "  (" << tri[i][0] << ", " << tri[i][1] << ") -> ("
             << ox << ", " << oy << ")" << endl;
    }

    return 0;
}

C 实现

#include <stdio.h>
#include <math.h>

typedef double Mat3[3][3];

// Set matrix to identity
void mat3_identity(Mat3 m) {
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++)
            m[i][j] = (i == j) ? 1 : 0;
}

// Set matrix to translation
void mat3_translate(Mat3 m, double tx, double ty) {
    mat3_identity(m);
    m[0][2] = tx;
    m[1][2] = ty;
}

// Set matrix to rotation (angle in radians)
void mat3_rotate(Mat3 m, double angle) {
    mat3_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;
}

// Set matrix to scale
void mat3_scale(Mat3 m, double sx, double sy) {
    mat3_identity(m);
    m[0][0] = sx;
    m[1][1] = sy;
}

// Matrix multiplication: result = A * B
void mat3_multiply(Mat3 A, Mat3 B, Mat3 result) {
    Mat3 temp = {{0}};
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++)
            for (int k = 0; k < 3; k++)
                temp[i][j] += A[i][k] * B[k][j];
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++)
            result[i][j] = temp[i][j];
}

// Transform point (x,y) by matrix M
void mat3_transform(Mat3 M, double x, double y, double* ox, double* oy) {
    *ox = M[0][0]*x + M[0][1]*y + M[0][2];
    *oy = M[1][0]*x + M[1][1]*y + M[1][2];
}

int main() {
    double tri[3][2] = {{0, 0}, {2, 0}, {1, 2}};
    double cx = (tri[0][0] + tri[1][0] + tri[2][0]) / 3.0;
    double cy = (tri[0][1] + tri[1][1] + tri[2][1]) / 3.0;

    printf("Triangle vertices:\n");
    for (int i = 0; i < 3; i++)
        printf("  (%g, %g)\n", tri[i][0], tri[i][1]);
    printf("Centroid: (%g, %g)\n", cx, cy);

    // Rotate 45 degrees around centroid
    double angle = M_PI / 4;
    Mat3 T1, R, T2, temp, composite;
    mat3_translate(T1, cx, cy);
    mat3_rotate(R, angle);
    mat3_translate(T2, -cx, -cy);

    mat3_multiply(R, T2, temp);
    mat3_multiply(T1, temp, composite);

    printf("\nRotate 45 deg around centroid:\n");
    for (int i = 0; i < 3; i++) {
        double ox, oy;
        mat3_transform(composite, tri[i][0], tri[i][1], &ox, &oy);
        printf("  (%g, %g) -> (%.4f, %.4f)\n", tri[i][0], tri[i][1], ox, oy);
    }

    return 0;
}

Python 实现

import math

class Mat3:
    """3x3 homogeneous transformation matrix."""

    def __init__(self):
        self.m = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]

    @staticmethod
    def identity():
        return Mat3()

    @staticmethod
    def translate(tx, ty):
        r = Mat3()
        r.m[0][2] = tx
        r.m[1][2] = ty
        return r

    @staticmethod
    def rotate(angle):
        r = Mat3()
        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

    @staticmethod
    def scale(sx, sy):
        r = Mat3()
        r.m[0][0] = sx
        r.m[1][1] = sy
        return r

    def multiply(self, other):
        result = Mat3()
        for i in range(3):
            for j in range(3):
                result.m[i][j] = sum(
                    self.m[i][k] * other.m[k][j] for k in range(3)
                )
        return result

    def transform(self, x, y):
        ox = self.m[0][0]*x + self.m[0][1]*y + self.m[0][2]
        oy = self.m[1][0]*x + self.m[1][1]*y + self.m[1][2]
        return ox, oy


# Triangle vertices
tri = [(0, 0), (2, 0), (1, 2)]
cx = sum(p[0] for p in tri) / 3
cy = sum(p[1] for p in tri) / 3

print("Triangle vertices:")
for p in tri:
    print(f"  {p}")
print(f"Centroid: ({cx:.4f}, {cy:.4f})")

# Rotate 45 degrees around centroid
angle = math.pi / 4
composite = Mat3.translate(cx, cy) \
    .multiply(Mat3.rotate(angle)) \
    .multiply(Mat3.translate(-cx, -cy))

print("\nRotate 45 deg around centroid:")
for px, py in tri:
    ox, oy = composite.transform(px, py)
    print(f"  ({px}, {py}) -> ({ox:.4f}, {oy:.4f})")

Go 实现

package main

import (
	"fmt"
	"math"
)

// Mat3 represents a 3x3 homogeneous transformation matrix
type Mat3 [3][3]float64

// mat3Identity returns the identity matrix
func mat3Identity() Mat3 {
	return Mat3{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}
}

// mat3Translate returns a translation matrix
func mat3Translate(tx, ty float64) Mat3 {
	m := mat3Identity()
	m[0][2] = tx
	m[1][2] = ty
	return m
}

// mat3Rotate returns a rotation matrix (angle in radians)
func mat3Rotate(angle float64) Mat3 {
	m := mat3Identity()
	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
}

// mat3Scale returns a scale matrix
func mat3Scale(sx, sy float64) Mat3 {
	m := mat3Identity()
	m[0][0] = sx
	m[1][1] = sy
	return m
}

// multiply returns the product of two 3x3 matrices
func (a Mat3) multiply(b Mat3) Mat3 {
	var result Mat3
	for i := 0; i < 3; i++ {
		for j := 0; j < 3; j++ {
			for k := 0; k < 3; k++ {
				result[i][j] += a[i][k] * b[k][j]
			}
		}
	}
	return result
}

// transform applies the matrix to point (x,y)
func (m Mat3) transform(x, y float64) (float64, float64) {
	ox := m[0][0]*x + m[0][1]*y + m[0][2]
	oy := m[1][0]*x + m[1][1]*y + m[1][2]
	return ox, oy
}

func main() {
	// Triangle vertices
	tri := [3][2]float64{{0, 0}, {2, 0}, {1, 2}}
	cx := (tri[0][0] + tri[1][0] + tri[2][0]) / 3
	cy := (tri[0][1] + tri[1][1] + tri[2][1]) / 3

	fmt.Println("Triangle vertices:")
	for _, p := range tri {
		fmt.Printf("  (%g, %g)\n", p[0], p[1])
	}
	fmt.Printf("Centroid: (%.4f, %.4f)\n", cx, cy)

	// Rotate 45 degrees around centroid
	angle := math.Pi / 4
	composite := mat3Translate(cx, cy).
		multiply(mat3Rotate(angle)).
		multiply(mat3Translate(-cx, -cy))

	fmt.Println("\nRotate 45 deg around centroid:")
	for _, p := range tri {
		ox, oy := composite.transform(p[0], p[1])
		fmt.Printf("  (%g, %g) -> (%.4f, %.4f)\n", p[0], p[1], ox, oy)
	}
}

以上四个版本分别用 C++ 的结构体+静态方法、C 的函数+二维数组、Python 的类以及 Go 的类型别名实现了完整的 3x3 矩阵库。每个版本都提供了 identitytranslaterotatescalemultiplytransform 六个核心操作。测试案例将三角形绕其重心旋转 45 度,验证了"平移到原点-旋转-平移回去"这一经典组合变换模式。

运行该程序将输出:

Triangle vertices:
  (0, 0)
  (2, 0)
  (1, 2)
Centroid: (1.0000, 0.6667)

Rotate 45 deg around centroid:
  (0, 0) -> (0.7618, 1.1785)
  (2, 0) -> (1.7618, -0.0355)
  (1, 2) -> (0.4764, 0.8569)

二维旋转与平移矩阵的性质

3x3 齐次坐标矩阵的优势

使用 3x3 齐次坐标矩阵(Homogeneous Coordinate Matrix)进行二维变换有以下核心优势:

  • 统一表示:旋转、平移、缩放、剪切都可以用 3x3 矩阵表示,无需为不同类型的变换设计不同的数据结构
  • 矩阵组合:多个变换可以通过矩阵乘法预先合并为一个矩阵,无论包含多少步变换,对每个点的处理都只需一次矩阵乘法
  • 硬件友好:GPU 的图形管线(Graphics Pipeline)直接使用 4x4 齐次坐标处理 3D 变换,2D 的 3x3 是其自然简化
  • 逆变换简单:3x3 仿射变换矩阵的逆矩阵仍然是 3x3 仿射变换矩阵,可以高效计算

仿射变换的性质

所有由 3x3 齐次坐标矩阵表示的变换都属于仿射变换(Affine Transformation)。仿射变换具有以下保持性质:

  • 共线性(Collinearity):变换前在同一条直线上的点,变换后仍在同一条直线上
  • 平行性(Parallelism):变换前平行的直线,变换后仍然平行
  • 距离比(Ratios of Distances):同一条直线上各线段长度的比值保持不变

仿射变换不保持的性质:

  • 长度和角度一般会改变(除非只包含刚体变换——旋转和平移)
  • 面积可能改变(缩放会改变面积,剪切也会改变面积)

变换顺序的重要性

矩阵乘法不满足交换律,因此变换的顺序至关重要:

R(90) * T(1,0) != T(1,0) * R(90)

先平移再旋转:点先移到 (1,0),再绕原点旋转到 (0,1)。先旋转再平移:点先旋转(绕原点旋转无变化),再平移到 (1,0)。结果完全不同。

一般规则:从右到左应用变换。矩阵表达式 T * R * S * point 表示先缩放(S),再旋转(R),最后平移(T)。

所有二维仿射变换的统一形式

任何二维仿射变换都可以表示为一个 3x3 矩阵:

| a  b  tx |
| c  d  ty |
| 0  0   1 |

其中:

  • [a, b, c, d] 构成左上角 2x2 子矩阵,负责旋转、缩放和剪切
  • [tx, ty] 是平移分量
  • 第三行始终为 [0, 0, 1],确保仿射变换的封闭性

常见变换的矩阵参数:

变换类型 a b c d tx ty
单位矩阵 1 0 0 1 0 0
平移 1 0 0 1 tx ty
旋转 cos -sin sin cos 0 0
缩放 sx 0 0 sy 0 0
水平剪切 1 kx 0 1 0 0
垂直剪切 1 0 ky 1 0 0

通过矩阵乘法将多个变换组合后,最终得到的仍然是一个 3x3 矩阵。这意味着无论变换序列有多复杂,都可以用一次矩阵乘法完成所有变换。

posted @ 2026-04-17 08:44  游翔  阅读(4)  评论(0)    收藏  举报