C++与OpenGL矩阵顺序混淆(行为主与列为主)

wnavrhmk  于 2022-11-23  发布在  其他
关注(0)|答案(4)|浏览(165)

我对矩阵的定义感到非常困惑。我有一个矩阵类,它包含一个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 } };

matrixAmatrixB在内存中具有相同的线性布局(即,所有数字都按顺序排列)。根据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] =行0,matrixB[1] =行1,等等。同样,这表示以行为主的布局。
我的问题/困惑出现在我创建的转换矩阵如下所示时:

1, 0, 0, transX
0, 1, 0, transY
0, 0, 1, transZ
0, 0, 0, 1

它在内存中的布局为{ 1, 0, 0, transX, 0, 1, 0, transY, 0, 0, 1, transZ, 0, 0, 0, 1 }
然后,当我调用glUniformMatrix4fv时,我需要将转置标志设置为GL_FALSE,表明它是以列为主的,否则诸如translate / scale等转换就不能正确应用:
如果转置为GL_FALSE,则假定每个矩阵以列为主顺序提供。如果转置为GL_TRUE,则假定每个矩阵以行为主顺序提供。
为什么我的矩阵(看起来是行为主)需要作为列为主传递给OpenGL?

uqdfh47h

uqdfh47h1#

opengl文档中使用的矩阵表示法不描述OpenGL矩阵的内存中布局

如果你放弃或忘记整个“行/列为主”的东西会更容易。这是因为除了行/列为主,程序员还可以决定他想如何在内存中布局矩阵(相邻元素是形成行还是列),除了符号,这会增加混乱。
OpenGL矩阵具有same memory layout as directx matrices

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是描述矩阵坐标系原点的3分量向量。

这意味着翻译矩阵在内存中的布局如下:

{ 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年前的答案"
数学矩阵定义为m x n矩阵。其中m是 * 行数 *,n是 * 列数 *。为了完整起见,行是水平的,列是垂直的。当用数学符号Mij表示矩阵元素时,第一个元素(i)是行索引,第二个(j)是列索引。当两个矩阵相乘时,例如A(m x n) * B(m1 x n1),结果矩阵的行数从第一个参数开始(A),第二个参数的列数(B),第一个参数的列数(A)必须与第二个参数的行数(B)匹配。所以n == m1。到目前为止,清楚了吗?
现在,关于内存中的布局。你可以用两种方式存储矩阵。行主型和列主型。行主型意味着你有一行接一行的线性布局。所以,元素从左到右,一行接一行。有点像英语文本。列主型意味着你有一列接一列的线性布局。所以元素从左上角开始,从上到下。
示例:

//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年前我说记住哪个元素是哪个元素更容易。

gr8qqesn

gr8qqesn2#

总结一下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 }

也称为“column major”。但是,在着色器代码中(如注解所示),您将变换矩阵左乘向量:

v_transformed = v*T;

其仅在T被转置时才产生正确的结果,即具有

{ 1, 0, 0, transX, 0, 1, 0, transY, 0, 0, 1, transZ, 0, 0, 0, 1 }

(i.e.'row major')。由于您已经为着色器提供了正确的布局,即row major,因此没有必要设置glUniform4vtranspose标志。

daupos2t

daupos2t3#

你在处理两个不同的问题。
首先,你的例子是关于内存布局的。你的[4][4]数组是以行为主的,因为你使用了C多维数组建立的约定来匹配你的线性数组。
第二个问题是如何在程序中解释矩阵的约定。glUniformMatrix4fv用于设置着色器参数。是否为行向量列向量转换计算转换取决于如何在着色器代码中使用矩阵。因为您说需要使用列向量,我假设您的着色器代码使用矩阵A和列向量x来计算x'=Ax
我认为glUniformMatrix的文档令人困惑。转置参数的描述是一种非常迂回的方式,只是说矩阵是转置的还是不是。OpenGL本身只是将数据传输到着色器,是否要转置它是你应该为程序建立的约定问题。
这个环节有一些很好的进一步探讨:http://steve.hollasch.net/cgindex/math/matrix/column-vec.html

piah890a

piah890a4#

我认为这里现有的答案是非常没有帮助的,我可以从评论中看到,人们在阅读后感到困惑,所以这里是另一种看待这种情况的方式。
作为一个程序员,如果我想在内存中存储一个数组,我不能存储一个矩形网格的数字,因为计算机内存不是这样工作的,我必须以线性序列存储数字。
假设我有一个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
];

相关问题