DirectX 12 中的变换体系与 DirectXMath 库实现指南
三维图形编程中的变换是将物体从一个坐标系统转换到另一个坐标系统的过程。在 DirectX 12 中,这些变换操作通过 DirectXMath 库提供的矩阵计算来实现。本章将详细介绍各种变换类型及其在 DirectX 12 中的实现。
3.1 线性变换
3.1.1 定义
线性变换是保持向量加法和标量乘法的变换,满足以下两个条件:
- 加法性质:T(u + v) = T(u) + T(v)
- 标量乘法性质:T(cv) = cT(v)
线性变换可以用矩阵来表示,对于三维空间中的向量,线性变换通常使用 3×3 矩阵:
T(v) = Mv
其中 M 是变换矩阵,v 是被变换的向量。
在 DirectXMath 中,线性变换可以这样实现:
cpp
// 定义线性变换矩阵
XMMATRIX linearTransform = XMMatrixSet(
2.0f, 0.0f, 0.0f, 0.0f,
0.0f, 2.0f, 0.0f, 0.0f,
0.0f, 0.0f, 2.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f
);
// 应用线性变换到向量
XMVECTOR vector = XMVectorSet(1.0f, 2.0f, 3.0f, 0.0f);
XMVECTOR transformedVector = XMVector3Transform(vector, linearTransform);
常见的线性变换包括缩放和旋转,但不包括平移,因为平移不满足线性变换的定义。
3.1.2 矩阵表示法
线性变换的矩阵表示法是用一个矩阵来表示该变换的效果。对于三维空间,线性变换可以用 3×3 矩阵 M 表示,其中矩阵的列向量表示基向量的变换结果:
M = [T(e₁) T(e₂) T(e₃)]
其中 e₁, e₂, e₃ 是标准基向量 (1,0,0), (0,1,0), (0,0,1)。
在 DirectXMath 中,虽然实际使用的是 4×4 矩阵,但对于线性变换,只使用左上角的 3×3 部分:
cpp
// 创建线性变换矩阵
XMMATRIX linearMatrix = XMMatrixIdentity();
// 修改左上角 3×3 部分
linearMatrix.r[0] = XMVectorSet(a, b, c, 0.0f);
linearMatrix.r[1] = XMVectorSet(d, e, f, 0.0f);
linearMatrix.r[2] = XMVectorSet(g, h, i, 0.0f);
3.1.3 缩放
缩放是一种改变物体尺寸的线性变换。缩放可以是均匀的(各方向等比例缩放)或非均匀的(各方向不同比例缩放)。
缩放变换的矩阵形式为:
S = [sx 0 0 ]
[0 sy 0 ]
[0 0 sz]
其中 sx, sy, sz 分别是 x, y, z 轴方向的缩放因子。
在 DirectXMath 中,可以使用 XMMatrixScaling
函数创建缩放矩阵:
cpp
// 创建缩放矩阵
XMMATRIX scalingMatrix = XMMatrixScaling(2.0f, 3.0f, 1.0f);
// 应用缩放变换
XMVECTOR originalVector = XMVectorSet(1.0f, 1.0f, 1.0f, 1.0f);
XMVECTOR scaledVector = XMVector3Transform(originalVector, scalingMatrix);
// 结果: scaledVector = (2.0f, 3.0f, 1.0f, 1.0f)
缩放变换的几何意义是将坐标轴拉伸或压缩,从而改变物体在各方向上的尺寸。
特殊的缩放变换:
- 均匀缩放:sx = sy = sz
- 镜像变换:某个缩放因子为负值,例如 sx = -1 表示关于 yz 平面的镜像
cpp
// 创建镜像矩阵(关于 yz 平面镜像)
XMMATRIX mirrorXMatrix = XMMatrixScaling(-1.0f, 1.0f, 1.0f);
// 创建均匀缩放矩阵(放大 2 倍)
XMMATRIX uniformScaleMatrix = XMMatrixScaling(2.0f, 2.0f, 2.0f);
3.1.4 旋转
旋转是一种保持原点不变并保持距离的线性变换。在三维空间中,旋转可以绕任意轴进行。
最基本的旋转是绕坐标轴的旋转,其矩阵形式为:
- 绕 X 轴旋转 θ 角度:
Rx(θ) = [1 0 0 ]
[0 cos(θ) sin(θ)]
[0 -sin(θ) cos(θ)]
- 绕 Y 轴旋转 θ 角度:
scheme
Ry(θ) = [cos(θ) 0 -sin(θ)]
[0 1 0 ]
[sin(θ) 0 cos(θ)]
- 绕 Z 轴旋转 θ 角度:
Rz(θ) = [cos(θ) sin(θ) 0]
[-sin(θ) cos(θ) 0]
[0 0 1]
在 DirectXMath 中,可以使用以下函数创建旋转矩阵:
cpp
// 绕 X 轴旋转 45 度
XMMATRIX rotationX = XMMatrixRotationX(XM_PIDIV4); // π/4 = 45度
// 绕 Y 轴旋转 90 度
XMMATRIX rotationY = XMMatrixRotationY(XM_PIDIV2); // π/2 = 90度
// 绕 Z 轴旋转 30 度
XMMATRIX rotationZ = XMMatrixRotationZ(XM_PI / 6); // π/6 = 30度
// 应用旋转变换
XMVECTOR vector = XMVectorSet(1.0f, 0.0f, 0.0f, 1.0f);
XMVECTOR rotatedVector = XMVector3Transform(vector, rotationZ);
对于绕任意轴的旋转,可以使用罗德里格斯旋转公式或四元数。DirectXMath 提供 XMMatrixRotationAxis
函数:
cpp
// 定义旋转轴(需要归一化)
XMVECTOR axis = XMVectorSet(1.0f, 1.0f, 1.0f, 0.0f);
axis = XMVector3Normalize(axis);
// 绕该轴旋转 60 度
XMMATRIX rotationMatrix = XMMatrixRotationAxis(axis, XM_PI / 3);
旋转矩阵的特性:
- 正交矩阵:R⁻¹ = Rᵀ(逆等于转置)
- 行列式为 1:det(R) = 1
- 保持向量长度不变
- 保持向量间的角度不变
复合旋转:当需要连续执行多个旋转时,可以将各个旋转矩阵相乘:
cpp
// 先绕 X 轴旋转,再绕 Y 轴旋转
XMMATRIX combinedRotation = XMMatrixMultiply(rotationX, rotationY);
// 或简写为
XMMATRIX combinedRotation = rotationX * rotationY;
注意:矩阵乘法不满足交换律,因此旋转顺序很重要。
欧拉角:使用三个角度(通常称为俯仰角 pitch、偏航角 yaw 和翻滚角 roll)来表示三维旋转:
cpp
// 使用欧拉角创建旋转矩阵
float pitch = XM_PIDIV4; // 绕 X 轴旋转 45 度
float yaw = XM_PIDIV2; // 绕 Y 轴旋转 90 度
float roll = XM_PI / 6; // 绕 Z 轴旋转 30 度
XMMATRIX rotationMatrix = XMMatrixRotationRollPitchYaw(pitch, yaw, roll);
欧拉角的优点是直观,但存在万向节锁(Gimbal Lock)问题,在某些角度组合下会丧失一个自由度。
四元数:另一种表示旋转的方法,可以避免万向节锁问题:
cpp
// 从欧拉角创建四元数
XMVECTOR quaternion = XMQuaternionRotationRollPitchYaw(pitch, yaw, roll);
// 从四元数创建旋转矩阵
XMMATRIX rotationMatrix = XMMatrixRotationQuaternion(quaternion);
// 从旋转矩阵创建四元数
XMVECTOR quaternionFromMatrix = XMQuaternionRotationMatrix(someRotationMatrix);
3.2 仿射变换
3.2.1 齐次坐标
为了在同一个框架下表示线性变换和平移变换,引入了齐次坐标。在齐次坐标中,三维空间中的点 (x, y, z) 表示为 (x, y, z, 1),而向量表示为 (x, y, z, 0)。
齐次坐标的主要优点:
- 可以用矩阵乘法统一表示所有变换(包括平移)
- 提供了表示无穷远点的方式
- 使投影变换变得简单
在 DirectXMath 中,向量通常使用 XMVECTOR 类型,具有 4 个分量:
cpp
// 创建点的齐次坐标(w = 1)
XMVECTOR point = XMVectorSet(x, y, z, 1.0f);
// 创建向量的齐次坐标(w = 0)
XMVECTOR vector = XMVectorSet(x, y, z, 0.0f);
3.2.2 仿射变换的定义及其矩阵表示
仿射变换是线性变换与平移的组合。在齐次坐标下,仿射变换可以用 4×4 矩阵表示:
A = [M t]
[0ᵀ 1]
其中 M 是 3×3 线性变换矩阵,t 是 3×1 平移向量,0ᵀ 是 1×3 的零向量。
仿射变换保持点之间的相对关系,例如共线的点在变换后仍然共线,平行线变换后仍然平行。
在 DirectXMath 中,仿射变换矩阵可以这样创建:
cpp
// 创建仿射变换矩阵
XMMATRIX affineMatrix = XMMatrixSet(
m11, m12, m13, 0.0f,
m21, m22, m23, 0.0f,
m31, m32, m33, 0.0f,
tx, ty, tz, 1.0f
);
// 或者用线性部分和平移部分组合
XMMATRIX linearPart = XMMatrixRotationZ(angle);
XMVECTOR translation = XMVectorSet(tx, ty, tz, 0.0f);
XMMATRIX affineMatrix = XMMatrixAffineTransformation(
XMVectorSet(1.0f, 1.0f, 1.0f, 0.0f), // 缩放(这里是单位缩放)
XMVectorZero(), // 旋转中心
quaternion, // 旋转(四元数)
translation // 平移
);
3.2.3 平移
平移是将物体在空间中移动,不改变其形状、大小和方向的变换。
平移矩阵的形式为:
T(tx, ty, tz) = [1 0 0 0]
[0 1 0 0]
[0 0 1 0]
[tx ty tz 1]
在 DirectXMath 中,可以使用 XMMatrixTranslation
函数创建平移矩阵:
cpp
// 创建平移矩阵
XMMATRIX translationMatrix = XMMatrixTranslation(5.0f, -3.0f, 2.0f);
// 应用平移变换
XMVECTOR point = XMVectorSet(1.0f, 1.0f, 1.0f, 1.0f);
XMVECTOR translatedPoint = XMVector4Transform(point, translationMatrix);
// 结果: translatedPoint = (6.0f, -2.0f, 3.0f, 1.0f)
平移变换只影响点(w = 1),不影响向量(w = 0):
cpp
// 向量不受平移影响
XMVECTOR vector = XMVectorSet(1.0f, 1.0f, 1.0f, 0.0f);
XMVECTOR translatedVector = XMVector4Transform(vector, translationMatrix);
// 结果: translatedVector = (1.0f, 1.0f, 1.0f, 0.0f)
为了方便,DirectXMath 提供了专门的函数来变换点和向量:
cpp
// 变换点(包括平移)
XMVECTOR transformedPoint = XMVector3Transform(point, transformMatrix);
// 变换向量(不包括平移)
XMVECTOR transformedVector = XMVector3TransformNormal(vector, transformMatrix);
平移变换的组合是将各个平移向量相加:
cpp
// 组合两个平移
XMMATRIX translation1 = XMMatrixTranslation(1.0f, 2.0f, 3.0f);
XMMATRIX translation2 = XMMatrixTranslation(4.0f, 5.0f, 6.0f);
XMMATRIX combinedTranslation = translation1 * translation2;
// 结果等同于
XMMATRIX combinedTranslation = XMMatrixTranslation(5.0f, 7.0f, 9.0f);
3.2.4 缩放和旋转的仿射矩阵
在仿射变换框架下,缩放和旋转矩阵可以与平移矩阵组合,形成更复杂的变换。
缩放的仿射矩阵:
S = [sx 0 0 0]
[0 sy 0 0]
[0 0 sz 0]
[0 0 0 1]
旋转的仿射矩阵(以绕 Z 轴旋转为例):
scheme
Rz(θ) = [cos(θ) sin(θ) 0 0]
[-sin(θ) cos(θ) 0 0]
[0 0 1 0]
[0 0 0 1]
在 DirectXMath 中,这些矩阵自动以仿射矩阵形式创建:
cpp
// 创建缩放的仿射矩阵
XMMATRIX scaleMatrix = XMMatrixScaling(2.0f, 2.0f, 2.0f);
// 创建旋转的仿射矩阵
XMMATRIX rotationMatrix = XMMatrixRotationY(XM_PIDIV2);
组合变换:多个仿射变换可以通过矩阵乘法组合:
cpp
// 创建变换矩阵:先缩放,再旋转,最后平移
XMMATRIX scaleMatrix = XMMatrixScaling(2.0f, 2.0f, 2.0f);
XMMATRIX rotationMatrix = XMMatrixRotationY(XM_PIDIV2);
XMMATRIX translationMatrix = XMMatrixTranslation(5.0f, 0.0f, 0.0f);
XMMATRIX transformMatrix = scaleMatrix * rotationMatrix * translationMatrix;
注意:变换的顺序很重要,矩阵乘法不满足交换律。
3.2.5 仿射变换矩阵的几何意义
仿射变换矩阵的几何意义可以从其组成部分理解:
-
线性部分(左上 3×3 矩阵):
- 控制旋转、缩放和剪切
- 决定物体的形状和朝向
-
平移部分(右上 3×1 向量):
- 控制物体在空间中的位置
- 不影响物体的形状和朝向
典型的仿射变换应用:
-
模型变换:将物体从局部坐标系变换到世界坐标系
cpp
XMMATRIX modelMatrix = XMMatrixScaling(scale, scale, scale) * XMMatrixRotationRollPitchYaw(pitch, yaw, roll) * XMMatrixTranslation(x, y, z);
-
视图变换:将世界坐标系变换到摄像机坐标系
cpp
XMMATRIX viewMatrix = XMMatrixLookAtLH(eyePosition, targetPosition, upDirection);
-
变换层次结构:构建复杂物体的骨架
cpp
// 父对象的变换 XMMATRIX parentTransform = CalculateParentTransform(); // 相对于父对象的局部变换 XMMATRIX localTransform = XMMatrixRotationZ(angle) * XMMatrixTranslation(offsetX, offsetY, offsetZ); // 综合变换 XMMATRIX worldTransform = localTransform * parentTransform;
3.3 变换的复合
变换的复合是将多个变换连续应用的过程,在数学上表示为矩阵乘法:
C = T₂ ∘ T₁ = T₂ · T₁
其中 T₁ 和 T₂ 是变换矩阵,∘ 表示复合操作,· 表示矩阵乘法。这意味着先应用 T₁,再应用 T₂。
在 DirectXMath 中,变换的复合是通过矩阵乘法实现的:
cpp
// 复合变换矩阵
XMMATRIX transform1 = XMMatrixRotationX(angle1);
XMMATRIX transform2 = XMMatrixTranslation(x, y, z);
XMMATRIX compositeTransform = transform2 * transform1;
// 应用复合变换
XMVECTOR result = XMVector3Transform(point, compositeTransform);
复合变换的性质:
-
不满足交换律:一般情况下,T₁ · T₂ ≠ T₂ · T₁
cpp
// 先旋转再平移 XMMATRIX transform1 = rotationMatrix * translationMatrix; // 先平移再旋转 XMMATRIX transform2 = translationMatrix * rotationMatrix; // transform1 和 transform2 通常不相等
-
满足结合律:(T₁ · T₂) · T₃ = T₁ · (T₂ · T₃)
cpp
// 这两种计算方式结果相同 XMMATRIX result1 = (transform1 * transform2) * transform3; XMMATRIX result2 = transform1 * (transform2 * transform3);
-
复合变换的逆是各变换逆的反序复合:(T₁ · T₂)⁻¹ = T₂⁻¹ · T₁⁻¹
cpp
// 计算复合变换的逆 XMVECTOR determinant; XMMATRIX inverseComposite = XMMatrixInverse(&determinant, compositeTransform); // 等价于各变换逆的反序复合 XMMATRIX inverse1 = XMMatrixInverse(nullptr, transform1); XMMATRIX inverse2 = XMMatrixInverse(nullptr, transform2); XMMATRIX inverseComposite2 = inverse2 * inverse1;
变换复合的典型应用是构建变换管线,例如从模型空间到屏幕空间的变换序列:
cpp
// 模型-视图-投影变换管线
XMMATRIX modelMatrix = CalculateModelMatrix(); // 模型空间 -> 世界空间
XMMATRIX viewMatrix = CalculateViewMatrix(); // 世界空间 -> 视图空间
XMMATRIX projMatrix = CalculateProjectionMatrix(); // 视图空间 -> 裁剪空间
// 复合变换矩阵
XMMATRIX model