[英]Confusion between C++ and OpenGL matrix order (row-major vs column-major)
我对矩阵定义感到非常困惑。 我有一个矩阵 class,它包含一个float[16]
,我认为它是行优先的,基于以下观察:
float matrixA[16] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
float matrixB[4][4] = { { 0, 1, 2, 3 }, { 4, 5, 6, 7 }, { 8, 9, 10, 11 }, { 12, 13, 14, 15 } };
matrixA
和matrixB
在 memory 中都具有相同的线性布局(即所有数字均按顺序排列)。 根据http://en.wikipedia.org/wiki/Row-major_order这表示行优先布局。
matrixA[0] == matrixB[0][0];
matrixA[3] == matrixB[0][3];
matrixA[4] == matrixB[1][0];
matrixA[7] == matrixB[1][3];
因此, matrixB[0]
= row 0, matrixB[1]
= row 1,等等。同样,这表示行优先布局。
当我创建一个看起来像这样的翻译矩阵时,我的问题/困惑就来了:
1, 0, 0, transX
0, 1, 0, transY
0, 0, 1, transZ
0, 0, 0, 1
在 memory 中布局为{ 1, 0, 0, transX, 0, 1, 0, transY, 0, 0, 1, transZ, 0, 0, 0, 1 }
。
然后,当我调用glUniformMatrix4fv时,我需要将转置标志设置为 GL_FALSE,表明它是列优先的,否则无法正确应用转换/缩放等转换:
如果 transpose 为 GL_FALSE,则假定每个矩阵都按列主要顺序提供。 如果 transpose 为 GL_TRUE,则假定每个矩阵都按行主要顺序提供。
为什么我的矩阵看起来是行优先的,需要作为列优先传递给 OpenGL?
opengl 文档中使用的矩阵表示法没有描述 OpenGL 矩阵的内存布局
如果您认为放弃/忘记整个“行/列主要”的事情会更容易。 这是因为除了行/列专业之外,程序员还可以决定他希望如何在内存中布置矩阵(相邻元素形成行还是列),除了符号,这会增加混乱。
OpenGL 矩阵与 directx 矩阵具有相同的内存布局。
x.x x.y x.z 0
y.x y.y y.z 0
z.x z.y z.z 0
p.x p.y p.z 1
或者
{ x.x x.y x.z 0 y.x y.y y.z 0 z.x z.y z.z 0 p.x p.y p.z 1 }
x、y、z 是描述矩阵坐标系(相对于全局坐标系内的局部坐标系)的 3 分量向量。
p 是描述矩阵坐标系原点的三分量向量。
这意味着翻译矩阵应该像这样在内存中布局:
{ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, transX, transY, transZ, 1 }.
把它留在那里,其余的应该很容易。
---来自旧的opengl常见问题解答--
9.005 OpenGL 矩阵是列优先还是行优先?
出于编程目的,OpenGL 矩阵是 16 值数组,其基向量在内存中连续布局。 平移分量占据 16 元素矩阵的第 13、14 和 15 个元素,其中索引从 1 到 16 编号,如 OpenGL 2.1 规范的第 2.11.2 节所述。
列优先与行优先纯粹是一种符号约定。 请注意,与列优先矩阵的后乘法产生与行优先矩阵的预乘相同的结果。 OpenGL 规范和 OpenGL 参考手册都使用列优先表示法。 您可以使用任何符号,只要清楚说明即可。
可悲的是,在规范和蓝皮书中使用列优先格式导致 OpenGL 编程社区无休止的混乱。 列优先表示法表明矩阵没有像程序员所期望的那样布置在内存中。
我将更新这个 9 岁的答案。
一个数学矩阵被定义为mxn
矩阵。 其中m
是行数, n
是列数。 为了完整起见,行是水平的,列是垂直的。 当用数学符号Mij
表示矩阵元素时,第一个元素 ( i
) 是行索引,第二个元素 ( j
) 是列索引。 当两个矩阵相乘时,即A(mxn) * B(m1 x n1)
,得到的矩阵具有第一个参数的行数( A
),第二个参数的列数( B
),以及第一个参数 ( A
) 必须匹配第二个 ( B
) 的行数。 所以n == m1
。 到目前为止清楚,是吗?
现在,关于内存布局。 您可以通过两种方式存储矩阵。 行优先和列优先。 Row-major 意味着有效地你有一个又一个线性排列的行。 所以,元素从左到右,一行接一行。 有点像英文文本。 Column-major 意味着有效地你有一个又一个线性排列的列。 所以元素从左上角开始,从上到下。
例子:
//matrix
|a11 a12 a13|
|a21 a22 a23|
|a31 a32 a33|
//row-major
[a11 a12 a13 a21 a22 a23 a31 a32 a33]
//column-major
[a11 a21 a31 a12 a22 a32 a13 a23 a33]
现在,这是有趣的部分!
有两种方法可以将 3d 变换存储在矩阵中。 正如我之前提到的,3d 中的矩阵本质上存储坐标系基向量和位置。 因此,您可以将这些向量存储在矩阵的行或列中。 当它们存储为列时,您将矩阵与列向量相乘。 像这样。
//convention #1
|vx.x vy.x vz.x pos.x| |p.x| |res.x|
|vx.y vy.y vz.y pos.y| |p.y| |res.y|
|vx.z vy.z vz.z pos.z| x |p.z| = |res.z|
| 0 0 0 1| | 1| |res.w|
但是,您也可以将这些向量存储为行,然后将行向量与矩阵相乘:
//convention #2 (uncommon)
| vx.x vx.y vx.z 0|
| vy.x vy.y vy.z 0|
|p.x p.y p.z 1| x | vz.x vz.y vz.z 0| = |res.x res.y res.z res.w|
|pos.x pos.y pos.z 1|
所以。 约定#1 经常出现在数学课本中。 约定 #2 在某个时候出现在 DirectX sdk 中。 两者都是有效的。
关于这个问题,如果您使用约定#1,那么您的矩阵是列优先的。 如果您使用约定 #2,那么它们是行专业的。 但是,两种情况下的内存布局是相同的
[vx.x vx.y vx.z 0 vy.x vy.y vy.z 0 vz.x vz.y vz.z 0 pos.x pos.y pos.z 1]
这就是为什么我说在 9 年前更容易记住哪个元素是哪个元素。
总结 SigTerm 和 dsharlet 的答案: 在 GLSL 中变换向量的常用方法是将变换矩阵与向量右乘:
mat4 T; vec4 v; vec4 v_transformed;
v_transformed = T*v;
为了使其工作,OpenGL 期望T
的内存布局为,如 SigTerm 所述,
{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, transX, transY, transZ, 1 }
这也被称为“列专业”。 但是,在您的着色器代码中(如您的评论所示),您将变换矩阵左乘以向量:
v_transformed = v*T;
只有在T
被转置时才会产生正确的结果,即具有布局
{ 1, 0, 0, transX, 0, 1, 0, transY, 0, 0, 1, transZ, 0, 0, 0, 1 }
(即“行专业”)。 由于您已经为着色器提供了正确的布局,即行专业,因此没有必要设置glUniform4v
的transpose
标志。
您正在处理两个不同的问题。
首先,您的示例正在处理内存布局。 您的 [4][4] 数组是行主要的,因为您使用了 C 多维数组建立的约定来匹配您的线性数组。
第二个问题是关于如何在程序中解释矩阵的约定问题。 glUniformMatrix4fv用于设置着色器参数。 是否为行向量或列向量转换计算转换取决于您如何在着色器代码中使用矩阵。 因为您说您需要使用列向量,所以我假设您的着色器代码使用矩阵A和列向量x来计算x' = A x 。
我认为glUniformMatrix的文档令人困惑。 转置参数的描述是一种非常迂回的方式,只是说矩阵是转置的还是不是。 OpenGL 本身只是将该数据传输到您的着色器,无论您是否想要转置它是您应该为您的程序建立的约定问题。
这个链接有一些很好的进一步讨论: http ://steve.hollasch.net/cgindex/math/matrix/column-vec.html
我认为这里现有的答案非常无用,我可以从评论中看到人们在阅读后感到困惑,所以这是看待这种情况的另一种方式。
作为程序员,如果我想在内存中存储一个数组,我不能存储一个矩形网格的数字,因为计算机内存不能这样工作,我必须以线性序列存储数字。
假设我有一个 2x2 矩阵,我在我的代码中初始化它,如下所示:
const matrix = [a, b, c, d];
只要我知道每个数组元素代表什么,我就可以在代码的其他部分成功地使用这个矩阵。
OpenGL 规范定义了每个索引位置所代表的内容,这就是构建数组并将其传递给 OpenGL 并让它执行您期望的操作所需要知道的全部内容。
只有当我想在描述我的代码的文档中编写矩阵时,行或列的主要问题才会发挥作用,因为数学家将矩阵写成矩形的数字网格。 然而,这只是一种约定,一种写下来的方式,对我编写的代码或计算机内存中数字的排列没有影响。 您可以使用其他符号轻松地重写这些数学论文,并且效果也一样。
对于上面的数组,我有两个选项可以在我的文档中将此数组写为矩形网格:
|a b| OR |a c|
|c d| |b d|
无论我选择哪种方式编写文档,这都不会影响我的代码或计算机内存中数字的顺序,它只是文档。
为了让阅读我的文档的人知道我在程序中将值存储在线性数组中的顺序,我可以指定这是矩阵的列主要或行主要表示形式。 如果它是列主要顺序,那么我应该遍历列以获得数字的线性排列。 如果这是行主要表示,那么我应该遍历行以获得数字的线性排列。
一般来说,按行主要顺序编写文档会使程序员的生活更轻松,因为如果我想翻译这个矩阵
|a b c|
|d e f|
|g h i|
成代码,我可以这样写:
const matrix = [
a, b, c
d, e, f
g, h, i
];
例如:
GLM 将矩阵值存储为 m[4][4]。 但它将矩阵视为具有列主要顺序。 即使对于 C 中的二维数组 m[ x ][ y ], x表示一行, y表示一列,这意味着该数组表示的矩阵实际上具有行主序。 诀窍是将 m[ x ][ y ] 视为x代表一列, y代表一行。 这就像你转置矩阵而不执行任何额外的操作来实现它。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.