Skip to content

OpenGL 矩阵平移的存储秘密:为什么看起来像转置?

· 4 min

在学习 OpenGL 矩阵变换时,很多开发者会遇到一个困惑:为什么代码中的平移矩阵实现看起来像是数学形式的”转置”?今天我们来揭开这个谜团。

问题的由来#

当我们在数学课本上学习平移矩阵时,标准形式是这样的:

平移矩阵 = [1 0 0 tx]
[0 1 0 ty]
[0 0 1 tz]
[0 0 0 1 ]

但在 OpenGL 代码中,你会看到这样的实现:

static Mat4 translate(float x, float y, float z) {
Mat4 result; // 初始化为单位矩阵
result.data[12] = x; // 设置 tx
result.data[13] = y; // 设置 ty
result.data[14] = z; // 设置 tz
return result;
}

等等,为什么 tx 被放在了 data[12] 而不是 data[3]?这看起来完全不对!

核心原因:列主序 vs 行主序#

答案在于矩阵的存储方式。计算机内存是一维的,但矩阵是二维的,所以我们需要选择如何将二维数据映射到一维内存中。

行主序存储(Row-major)#

按行依次存储,大多数编程语言的默认方式:

[0 1 2 3 ] 内存布局: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
[4 5 6 7 ]
[8 9 10 11]
[12 13 14 15]

列主序存储(Column-major)#

按列依次存储,OpenGL 的选择

[0 4 8 12] 内存布局: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
[1 5 9 13]
[2 6 10 14]
[3 7 11 15]

实际的内存映射#

在 OpenGL 的列主序系统中:

所以当我们设置 data[12] = x 时,实际上是在设置数学矩阵的第1行第4列,这正是平移矩阵中 tx 应该在的位置。

完整的对应关系#

// 数学形式的平移矩阵:
// [1 0 0 tx]
// [0 1 0 ty]
// [0 0 1 tz]
// [0 0 0 1 ]
// 在 OpenGL 列主序内存中的存储:
float data[16] = {
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, tx,ty,tz, 1
// |第1列| |第2列| |第3列| |第4列|
};

验证代码#

void printMatrix(const Mat4& mat) {
std::cout << "数学形式:" << std::endl;
for(int row = 0; row < 4; row++) {
std::cout << "[";
for(int col = 0; col < 4; col++) {
// 列主序访问:data[col*4 + row]
std::cout << mat.data[col*4 + row];
if(col < 3) std::cout << ", ";
}
std::cout << "]" << std::endl;
}
}
// 测试
Mat4 transform = Mat4::translate(5.0f, 3.0f, 0.0f);
printMatrix(transform);
// 输出:
// [1, 0, 0, 5] ← tx = 5 在第4列
// [0, 1, 0, 3] ← ty = 3 在第4列
// [0, 0, 1, 0] ← tz = 0 在第4列
// [0, 0, 0, 1]

关键要点#

  1. OpenGL 使用列主序存储矩阵
  2. data[12], data[13], data[14] 分别对应数学矩阵的 tx, ty, tz
  3. 这不是转置,而是不同的内存布局方式
  4. 着色器中接收到的矩阵是正确的数学形式

实用建议#

理解了这个概念,你就不会再对 OpenGL 矩阵操作感到困惑了。记住:看起来像转置,实际是列主序!