关于cesium的primitive的modelMatrix的应用

在阅读这篇文章之前,建议先了解什么是仿射变换矩阵

序. 仿射变换矩阵

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())
相关推荐
vipbic8 分钟前
用 Turborepo 打造 Strapi 插件开发的极速全栈体验
前端·javascript
天涯学馆8 分钟前
为什么 JavaScript 可以单线程却能处理异步?
前端·javascript
Henry_Lau61724 分钟前
主流IDE常用快捷键对照
前端·css·ide
陶甜也28 分钟前
使用Blender进行现代建筑3D建模:前端开发者的跨界探索
前端·3d·blender
我命由我123451 小时前
VSCode - Prettier 配置格式化的单行长度
开发语言·前端·ide·vscode·前端框架·编辑器·学习方法
HashTang1 小时前
【AI 编程实战】第 4 篇:一次完美 vs 五轮对话 - UnoCSS 配置的正确姿势
前端·uni-app·ai编程
JIngJaneIL1 小时前
基于java + vue校园快递物流管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js
asdfg12589632 小时前
JS中的闭包应用
开发语言·前端·javascript
kirk_wang2 小时前
Flutter 导航锁踩坑实录:从断言失败到类型转换异常
前端·javascript·flutter