3D 空间中的变换矩阵详解
在 3D 计算机图形学中,所有几何变换都可以通过 4×4 齐次变换矩阵 来表示。以下详细介绍各种变换矩阵及其原理。
核心变换矩阵
1. 单位矩阵(不变变换)
I=[1000010000100001] I = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} I= 1000010000100001
2. 平移矩阵(Translation)
将点 p=[x,y,z,1]T\mathbf{p} = [x, y, z, 1]^Tp=[x,y,z,1]T 沿向量 t=[tx,ty,tz]\mathbf{t} = [t_x, t_y, t_z]t=[tx,ty,tz] 移动:
T=[100tx010ty001tz0001] T = \begin{bmatrix} 1 & 0 & 0 & t_x \\ 0 & 1 & 0 & t_y \\ 0 & 0 & 1 & t_z \\ 0 & 0 & 0 & 1 \end{bmatrix} T= 100001000010txtytz1
变换后坐标:
p′=T⋅p=[x+tx,y+ty,z+tz,1]T p' = T \cdot p = [x+t_x, y+t_y, z+t_z, 1]^T p′=T⋅p=[x+tx,y+ty,z+tz,1]T
T逆矩阵:将 (tx,ty,tz)(t_x,t_y,t_z)(tx,ty,tz) 取负
3. 缩放矩阵(Scaling)
沿坐标轴缩放,缩放因子 (sx,sy,sz)(s_x, s_y, s_z)(sx,sy,sz):
S=[sx0000sy0000sz00001] S = \begin{bmatrix} s_x & 0 & 0 & 0 \\ 0 & s_y & 0 & 0 \\ 0 & 0 & s_z & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} S= sx0000sy0000sz00001
特殊类型:
- 均匀缩放 :sx=sy=szs_x = s_y = s_zsx=sy=sz
- 镜像 :某个分量为负(如 sx=−1s_x = -1sx=−1)
逆矩阵:使用 (1/sx,1/sy,1/sz)(1/s_x,1/s_y,1/s_z)(1/sx,1/sy,1/sz)(假设因子不为0)。
4. 旋转矩阵(Rotation)
a) 绕坐标轴旋转
旋转轴 | 矩阵 |
---|---|
X轴 | Rx(θ)=[10000cosθ−sinθ00sinθcosθ00001]R_x(\theta) = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & \cos\theta & -\sin\theta & 0 \\ 0 & \sin\theta & \cos\theta & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}Rx(θ)= 10000cosθsinθ00−sinθcosθ00001 |
Y轴 | Ry(θ)=[cosθ0sinθ00100−sinθ0cosθ00001]R_y(\theta) = \begin{bmatrix} \cos\theta & 0 & \sin\theta & 0 \\ 0 & 1 & 0 & 0 \\ -\sin\theta & 0 & \cos\theta & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}Ry(θ)= cosθ0−sinθ00100sinθ0cosθ00001 |
Z轴 | Rz(θ)=[cosθ−sinθ00sinθcosθ0000100001]R_z(\theta) = \begin{bmatrix} \cos\theta & -\sin\theta & 0 & 0 \\ \sin\theta & \cos\theta & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}Rz(θ)= cosθsinθ00−sinθcosθ0000100001 |
旋转方向:右手坐标系中,逆时针为正方向(从旋转轴正向观察)
b) 绕任意轴旋转(Rodrigues公式)
绕单位向量 u=[ux,uy,uz]\mathbf{u} = [u_x, u_y, u_z]u=[ux,uy,uz] 旋转角度 θ\thetaθ:
R(u,θ)=[cosθ+ux2(1−cosθ)uxuy(1−cosθ)−uzsinθuxuz(1−cosθ)+uysinθ0uyux(1−cosθ)+uzsinθcosθ+uy2(1−cosθ)uyuz(1−cosθ)−uxsinθ0uzux(1−cosθ)−uysinθuzuy(1−cosθ)+uxsinθcosθ+uz2(1−cosθ)00001] R(\mathbf{u}, \theta) = \begin{bmatrix} \cos\theta + u_x^2(1-\cos\theta) & u_xu_y(1-\cos\theta) - u_z\sin\theta & u_xu_z(1-\cos\theta) + u_y\sin\theta & 0 \\ u_yu_x(1-\cos\theta) + u_z\sin\theta & \cos\theta + u_y^2(1-\cos\theta) & u_yu_z(1-\cos\theta) - u_x\sin\theta & 0 \\ u_zu_x(1-\cos\theta) - u_y\sin\theta & u_zu_y(1-\cos\theta) + u_x\sin\theta & \cos\theta + u_z^2(1-\cos\theta) & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} R(u,θ)= cosθ+ux2(1−cosθ)uyux(1−cosθ)+uzsinθuzux(1−cosθ)−uysinθ0uxuy(1−cosθ)−uzsinθcosθ+uy2(1−cosθ)uzuy(1−cosθ)+uxsinθ0uxuz(1−cosθ)+uysinθuyuz(1−cosθ)−uxsinθcosθ+uz2(1−cosθ)00001
5. 组合变换矩阵
变换矩阵可以通过乘法组合。注意矩阵乘法的顺序非常重要(不满足交换律)。通常,变换按以下顺序应用:缩放 -> 旋转 -> 平移,即:
M=T⋅R⋅S M = T \cdot R \cdot S M=T⋅R⋅S
但是,如果以不同的顺序应用,结果会不同。
例如,先绕Y轴旋转90度,再沿X轴平移3个单位,与先平移后旋转的结果不同:
先旋转后平移:点会先旋转,然后沿世界坐标系的X轴平移。
先平移后旋转:点先在世界坐标系中沿X轴平移,然后旋转(此时点的位置已经改变,旋转后位置不同)。
注意顺序:
- 缩放 (S)
- 旋转 ®
- 平移 (T)
这是因为矩阵乘法是 右乘结合 :p′=T⋅(R⋅(S⋅p)) \mathbf{p}' = T \cdot (R \cdot (S \cdot \mathbf{p})) p′=T⋅(R⋅(S⋅p))
特殊变换类型
1. 剪切矩阵(Shear)
H=[1hxyhxz0hyx1hyz0hzxhzy100001] H = \begin{bmatrix} 1 & h_{xy} & h_{xz} & 0 \\ h_{yx} & 1 & h_{yz} & 0 \\ h_{zx} & h_{zy} & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} H= 1hyxhzx0hxy1hzy0hxzhyz100001
2. 正交投影矩阵
向XY平面投影:
Portho=[1000010000000001] P_{\text{ortho}} = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} Portho= 1000010000000001
变换矩阵在编程中的实现
使用GLM(OpenGL Mathematics)库示例(两种写法)
-
GLM函数叠加运算
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>// 初始化矩阵
glm::mat4 model = glm::mat4(1.0f); // 单位矩阵// 平移:沿X轴移动1.5单位
model = glm::translate(model, glm::vec3(1.5f, 0.0f, 0.0f));// 旋转:绕Z轴旋转90度
model = glm::rotate(model, glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f));// 缩放:Y轴放大2倍
model = glm::scale(model, glm::vec3(1.0f, 2.0f, 1.0f));
变换顺序很重要:矩阵乘法从右到左应用变换,所以上面代码的顺序实际上是先缩放,再旋转,最后平移。
在组合变换时,考虑使用逆序:先进行的变换在矩阵乘法中放在右边(后乘),后进行的变换放在左边(先乘)
- 计算好各个变换矩阵后再相乘
cpp
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtx/transform.hpp>
int main() {
// 单位矩阵
glm::mat4 identity = glm::mat4(1.0f);
// 平移变换:沿X轴移动2个单位
glm::mat4 translation = glm::translate(glm::mat4(1.0), glm::vec3(2.0f, 0.0f, 0.0f));
// 缩放变换:Y轴扩大3倍
glm::mat4 scaling = glm::scale(glm::mat4(1.0), glm::vec3(1.0f, 3.0f, 1.0f));
// 旋转变换:绕Z轴旋转45度
glm::mat4 rotation = glm::rotate(glm::mat4(1.0),
glm::radians(45.0f),
glm::vec3(0.0f, 0.0f, 1.0f));
// 绕任意轴旋转:绕(1,1,0)旋转30度
glm::vec3 axis = glm::normalize(glm::vec3(1.0f, 1.0f, 0.0f));
glm::mat4 customRotation = glm::rotate(glm::mat4(1.0),
glm::radians(30.0f),
axis);
// 组合变换:先缩放,再旋转,最后平移
glm::mat4 composite = translation * rotation * scaling;
// 应用变换到点
glm::vec4 point(1.0f, 1.0f, 0.0f, 1.0f);
glm::vec4 transformedPoint = composite * point;
return 0;
}
手动实现矩阵类
cpp
class Matrix4f {
public:
float m[4][4];
Matrix4f() { /* 初始化为单位矩阵 */ }
static Matrix4f translate(float tx, float ty, float tz) {
Matrix4f m;
m.m[0][3] = tx;
m.m[1][3] = ty;
m.m[2][3] = tz;
return m;
}
static Matrix4f scale(float sx, float sy, float sz) {
Matrix4f m;
m.m[0][0] = sx;
m.m[1][1] = sy;
m.m[2][2] = sz;
return m;
}
static Matrix4f rotateX(float angle) {
float rad = angle * M_PI/180.0;
float c = cos(rad);
float s = sin(rad);
Matrix4f m;
m.m[1][1] = c; m.m[1][2] = -s;
m.m[2][1] = s; m.m[2][2] = c;
return m;
}
Matrix4f operator*(const Matrix4f& right) const {
// 矩阵乘法实现
}
Vec4f operator*(const Vec4f& v) const {
// 矩阵向量乘法
}
};
变换矩阵的性质与应用
-
齐次坐标
- 3D点:[x, y, z, 1]ᵀ
- 3D向量:[x, y, z, 0]ᵀ(防止被平移)
-
逆变换
- 平移逆矩阵: T\^{-1}(t_x, t_y, t_z) = T(-t_x, -t_y, -t_z)
- 缩放逆矩阵: S\^{-1}(s_x, s_y, s_z) = S(1/s_x, 1/s_y, 1/s_z)
- 旋转逆矩阵: R\^{-1} = R\^T (正交矩阵)
-
组合变换求逆
组合变换的逆矩阵可以通过矩阵求逆得到。如果变换矩阵是正交的(纯旋转,不含缩放或错切),则转置就是逆矩阵。如果包含平移和缩放,则需要计算逆矩阵:
(T⋅R⋅S)−1=S−1⋅R−1⋅T−1 (T \cdot R \cdot S)^{-1} = S^{-1} \cdot R^{-1} \cdot T^{-1} (T⋅R⋅S)−1=S−1⋅R−1⋅T−1
其中:
T −1 :将平移向量取负。
R −1:旋转矩阵的转置(因为旋转矩阵正交)。
S −1:缩放因子的倒数(假设非零)。
-
在变换链中的位置
局部坐标 → 模型矩阵 → 世界坐标 ↓ 世界坐标 → 视图矩阵 → 观察坐标 ↓ 观察坐标 → 投影矩阵 → 裁剪坐标
可视化理解变换顺序
考虑一个点 P(0,1,0) 的变换过程:
原始位置: 0,1,0 缩放: 2倍Y轴 结果: 0,2,0 旋转: 绕Z轴90度 结果: -2,0,0 平移: X轴移动3单位 最终位置: 1,0,0 错误顺序 先平移: 3,1,0 后旋转: -1,3,0 结果不符
正确的矩阵乘法顺序:
Pfinal=[1003010000100001]⏟平移×[0−100100000100001]⏟旋转×[1000020000100001]⏟缩放×[0101] P_{\text{final}} = \underset{\text{平移}}{\underbrace{ \begin{bmatrix} 1 & 0 & 0 & 3 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} }} \times \underset{\text{旋转}}{\underbrace{ \begin{bmatrix} 0 & -1 & 0 & 0 \\ 1 & 0 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} }} \times \underset{\text{缩放}}{\underbrace{ \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 2 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} }} \times \begin{bmatrix} 0 \\ 1 \\ 0 \\ 1 \end{bmatrix} Pfinal=平移 1000010000103001 ×旋转 0100−100000100001 ×缩放 1000020000100001 × 0101
这些矩阵构成了 3D 图形学的基础,在 OpenGL、DirectX、Vulkan 等图形 API 以及 Unity、Unreal 等游戏引擎中广泛使用。掌握它们对于理解 3D 渲染管线至关重要。