在阅读这篇文章之前,建议先了解什么是仿射变换矩阵
序. 仿射变换矩阵
1、核心概念:什么是仿射变换?
仿射变换是一类 "线性变换 + 平移" 的组合变换,具体包括:
- 线性变换:旋转(Rotation)、缩放(Scaling)、剪切(Shearing);
- 平移变换:将图形沿某个方向移动(Translation)。
例如:把一张图片旋转 30° 后再向右移动 100 像素,就是一次仿射变换。
2、仿射变换矩阵的结构(以 3D 为例,Cesium 中最常用)
在 3D 空间中,仿射变换矩阵是一个4×4 的矩阵(齐次坐标矩阵),结构如下(列优先存储,Cesium 默认格式):
arduino
[
a, d, g, j, // 第1列:X轴线性变换参数
b, e, h, k, // 第2列:Y轴线性变换参数
c, f, i, l, // 第3列:Z轴线性变换参数
0, 0, 0, 1 // 第4列:齐次坐标固定项(确保矩阵乘法合法性)
]
-
前 3×3 子矩阵(a-i) :负责线性变换(旋转、缩放、剪切);
这里详细分解一下旋转和缩放:
缩放变换:由前 3×3 子矩阵的 "对角线元素" 控制
arduino
Sx, 0, 0, // X轴缩放因子(m00)
0, Sy, 0, // Y轴缩放因子(m05)
0, 0, Sz // Z轴缩放因子(m10)
旋转变换:由前 3×3 子矩阵的 "所有元素共同控制"(正交矩阵)
arduino
// 绕 Z 轴旋转(航向角 Heading,对应 yaw),旋转角度为`θ`,矩阵元素:
[
cosθ, -sinθ, 0, // 第0列:m00=cosθ, m01=-sinθ, m02=0
sinθ, cosθ, 0, // 第1列:m04=sinθ, m05=cosθ, m06=0
0, 0, 1 // 第2列:m08=0, m09=0, m10=1
]
// 绕 X 轴旋转(翻滚角 Roll),旋转角度为`φ`,矩阵元素:
[
1, 0, 0, // 第0列:m00=1, m01=0, m02=0
0, cosφ, -sinφ, // 第1列:m04=0, m05=cosφ, m06=-sinφ
0, sinφ, cosφ // 第2列:m08=0, m09=sinφ, m10=cosφ
]
// 绕 Y 轴旋转(俯仰角 Pitch),旋转角度为`ψ`,矩阵元素:
[
cosψ, 0, sinψ, // 第0列:m00=cosψ, m01=0, m02=sinψ
0, 1, 0, // 第1列:m04=0, m05=1, m06=0
-sinψ,0, cosψ // 第2列:m08=-sinψ, m09=0, m10=cosψ
]
-
第 4 列前 3 个元素(j, k, l) :负责平移变换(X、Y、Z 方向的平移量);
-
最后一行(0,0,0,1) :齐次坐标的 "标识行",确保矩阵乘法能正确融合线性变换和平移。
3、为什么用 4×4 矩阵?(齐次坐标的作用)
线性变换(如旋转、缩放)可以用 3×3 矩阵表示,但平移无法用 3×3 矩阵单独实现(因为线性变换的原点映射后仍是原点,而平移会改变原点位置)。
通过引入 "齐次坐标"(3D 点用 4 个分量(x, y, z, 1)表示),4×4 矩阵可以统一处理线性变换和平移:
- 对一个点
(x, y, z, 1)应用仿射变换矩阵后,结果为:x' = a*x + d*y + g*z + j``y' = b*x + e*y + h*z + k``z' = c*x + f*y + i*z + l(同时满足线性变换和平移的组合效果)
4、Cesium 中的仿射变换矩阵实例
Cesium 中几乎所有模型 / 物体的位置和姿态调整,都依赖 4×4 仿射变换矩阵(如modelMatrix):
a. 纯平移矩阵:
go
```
// 沿X轴平移10,Y轴平移20,Z轴平移30
const translateMatrix = Cesium.Matrix4.fromTranslation(new Cesium.Cartesian3(10, 20, 30));
```
矩阵结构为:
```
[1,0,0,10,
0,1,0,20,
0,0,1,30,
0,0,0,1]
```
b. 旋转 + 平移矩阵:
go
```
// 以center为原点,应用HPR旋转,生成包含旋转和平移的仿射矩阵
const hprMatrix = Cesium.Transforms.headingPitchRollToFixedFrame(center, hpr);
```
其中前 3×3 子矩阵是旋转(线性变换),第 4 列是 center 的坐标(平移)。
5、核心作用:统一组合多个变换
仿射变换矩阵的最大价值是支持矩阵乘法组合多个变换。例如:先缩放→再旋转→最后平移,只需将三个变换矩阵按顺序相乘(注意顺序:从右到左应用):
arduino
// Cesium中矩阵的乘法为右乘!即
//matA * matB,写作
Cesium.Matrix4.multiply(matB, matA, new Cesium.Matrix4())
// 缩放矩阵 → 旋转矩阵 → 平移矩阵
const temp = Cesium.Matrix4.multiply(rotateMatrix, scaleMatrix)
const finalMatrix = Cesium.Matrix4.multiply(translateMatrix, temp);
一.平移
1. 世界坐标平移(原点目标点皆为世界坐标)
思路,获取向量差,将向量差转为平移矩阵
ini
// origin 为原点坐标,target为目标坐标
const sub = Cesium.Cartesian3.subtract(target, origin, new Cesium.Cartesian3())
primitive.modelMatrix = Cesium.Matrix4.fromTranslation(sub)
2. 局部坐标平移(如模型向南平移200米)
思路,计算出目标点世界坐标,获取向量差,将向量差转为平移矩阵
ini
// origin 为原点坐标,local_translation为平移向量
const originMatrix4 = Cesium.Transforms.eastNorthUpToFixedFrame(origin);
// 平移向量的构建为:new Cesium.Cartesian3(东,北,上)
const local_translation = new Cesium.Cartesian3(300, 200, 100);
const result = new Cesium.Cartesian3(0,0,0);
// 转换矩阵左乘局部平移向量,结果存储在 result 中,结果是世界坐标下的平移终点向量,即目标点的世界坐标
Cesium.Matrix4.multiplyByPoint(frompoint_to_world_matrix, local_translation, result);
// 获取世界坐标平移向量
const sub = Cesium.Cartesian3.subtract(result, origin, new Cesium.Cartesian3())
// 应用平移
primitive.modelMatrix = Cesium.Matrix4.fromTranslation(sub)
二.旋转
思路,因为Cesium的Primitive旋转中心为坐标系中心, 所以必须先来到坐标系中心,再做操作,最后返回原点
arduino
// 获取从坐标系中心到原点的位移矩阵
const backMat = Cesium.Matrix4.fromTranslation(origin)
// 将向量取反,用于获取相反的位移矩阵,用于前往坐标系中心
const toVec = new Cesium.Cartesian3(-origin.x, -origin.y, -origin.z)
const toMat = Cesium.Matrix4.fromTranslation(toVec)
// 获取旋转参数
const hpr = new Cesium.HeadingPitchRoll(heading,pitch,roll)
// 因为目前模型在坐标系中心,第一个参数要写Cesium.Cartesian3.ZERO
const rotateMatrix = Cesium.Transforms.headingPitchRollToFixedFrame(Cesium.Cartesian3.ZERO, hpr, Cesium.Ellipsoid.WGS84)
// 获取完整矩阵,根据顺序应当 原点→中心→变换→原点,所以应当是 toMat * rotateMatrix * backMat
// 注意Cesium矩阵相乘是右乘!!!!!!
const temp = Cesium.Matrix4.multiply(rotateMatrix, toMat, new Cesium.Matrix4())
const res = Cesium.Matrix4.multiply(backMat, temp, new Cesium.Matrix4())
Primitive.modelMatrix = res
三.缩放
思路,依旧必须先来到坐标系中心,再做缩放,最后返回原点
arduino
// 省略来回位移的代码,rotateMatrix替换成scaleMat
// 获取缩放参数
const scaleVec = new Cesium.Cartesian3(0.5, 0.5, 0.5)
const scaleMat = Cesium.Matrix4.fromScale(scaleVec, new Cesium.Matrix4())