3、二维平移变换:平移矩阵推导,使用齐次坐标实现平移
好,咱们今天聊一个非常基础但又极其重要的东西——二维平移变换。
你可能会想,平移不就是把点从(x,y)挪到(x+tx, y+ty)吗?这有什么好讲的?
嗯,没错,数学上确实就这么简单。但问题在于——当你想要把平移和旋转、缩放组合在一起时,事情就没那么单纯了。我在做嵌入式GUI框架时,就因为这个“简单”的平移,踩过不少坑。
3.1 平移的向量表达
先看最直观的形式。假设平面上有一个点P(x, y),你想把它沿x方向移动tx,沿y方向移动ty,得到新点P'(x', y'):
x' = x + tx
y' = y + ty
用向量写就是:
P' = P + T
其中T = [tx, ty]ᵀ 是平移向量。
这有什么问题呢?问题在于——这是一个加法运算。而旋转、缩放都是乘法运算(矩阵乘法)。
你想想看,如果你先旋转再平移,或者先平移再旋转,运算顺序不同,结果完全不同。但如果你想把它们写成一个统一的变换矩阵,加法就变成了障碍。
核心矛盾:旋转和缩放可以用2×2矩阵乘法表示,但平移是加法。两种运算不兼容,无法合并成一个矩阵。
3.2 齐次坐标的引入
那怎么办?
我记得第一次遇到这个问题是在做一个小型2D游戏引擎。当时我想把物体的位置、旋转、缩放用一个矩阵搞定,结果发现平移死活塞不进去。查了半天资料,才明白——需要升维。
具体做法是:给二维点增加一个维度,变成三维向量。
原来:P = [x, y]ᵀ
现在:P = [x, y, 1]ᵀ
这个多出来的“1”就是齐次坐标的w分量。对于二维点,我们通常设w=1。
为什么要加这个1?
因为这样一来,平移就可以写成矩阵乘法了:
[x'] [1 0 tx] [x]
[y'] = [0 1 ty] [y]
[1 ] [0 0 1] [1]
展开看看:
x' = 1·x + 0·y + tx·1 = x + tx
y' = 0·x + 1·y + ty·1 = y + ty
1 = 0·x + 0·y + 1·1 = 1
结果完全一致。但形式上,它变成了矩阵乘法。
个人习惯:我一般把平移矩阵记作T(tx, ty)。写代码时,我会专门封装一个函数来生成这个矩阵,避免手算出错。
3.3 平移矩阵的几何意义
这个3×3矩阵的结构很有意思:
| 位置 | 含义 |
|---|---|
| 左上角2×2 | 旋转/缩放部分(这里是单位矩阵,表示不做旋转和缩放) |
| 右上角2×1 | 平移量 [tx, ty]ᵀ |
| 底部行 | [0, 0, 1],保持齐次坐标结构 |
说白了,这个矩阵就是在说:“先保持原来的形状不变(左上角单位矩阵),然后整体挪个位置(右上角平移量)”。
3.4 代码实现
在实际项目中,我习惯用C语言来实现。下面是一个简单的平移矩阵生成和应用的例子:
// 定义3x3矩阵(行主序)
typedef struct {
float m[3][3];
} Mat3;
// 生成平移矩阵
Mat3 mat3_translate(float tx, float ty) {
Mat3 result = {{
{1, 0, tx},
{0, 1, ty},
{0, 0, 1}
}};
return result;
}
// 应用平移矩阵到点
void apply_translate(Mat3* mat, float* x, float* y) {
float x_new = mat->m[0][0] * (*x) + mat->m[0][1] * (*y) + mat->m[0][2];
float y_new = mat->m[1][0] * (*x) + mat->m[1][1] * (*y) + mat->m[1][2];
*x = x_new;
*y = y_new;
}
我曾经踩过的坑:在嵌入式平台上,浮点运算比较慢。如果你只是做纯平移,直接用加法比用矩阵乘法快得多。我建议只有在需要组合多种变换时,才使用齐次坐标矩阵。否则,老老实实做加法就好。
3.5 逆平移矩阵
有正就有反。平移的逆操作很简单——往反方向移动同样的距离。
平移矩阵T(tx, ty)的逆矩阵是T(-tx, -ty):
T⁻¹ = [1 0 -tx]
[0 1 -ty]
[0 0 1 ]
验证一下:T(tx, ty) · T(-tx, -ty) = 单位矩阵。嗯,完美。
3.6 本章知识体系
下面我用一张SVG图来总结本章的核心逻辑:
这张图把整个逻辑串起来了:从“平移是加法”这个矛盾出发,引出齐次坐标这个解决方案,然后给出具体的矩阵形式,最后落到实际应用上。
3.7 小结
二维平移本身不复杂,但它是理解齐次坐标和组合变换的敲门砖。我个人觉得,齐次坐标是计算机图形学里最优雅的设计之一——用一个额外的维度,把加法和乘法统一了起来。
你想想看,如果没有齐次坐标,每次做组合变换时,你都得手动拆成加法和乘法两部分,代码写起来又臭又长。有了齐次坐标,一切都可以用矩阵乘法搞定,简洁、统一、高效。
下一节我们会讲二维旋转,到时候你就会发现,齐次坐标的真正威力在于——平移、旋转、缩放可以无缝组合成一个矩阵。嗯,到时候你就明白了。