有人可以解释乘法/级联中colum vs row major的(原因)吗?


11

由于我对矩阵的两个标准感到困惑,因此我试图学习如何构造视图和投影矩阵,并在实现过程中遇到困难。
我知道如何乘法矩阵,并且可以看到乘法之前的转置会完全改变结果,因此需要以不同的顺序进行乘法。

我不明白的是什么仅是“符号约定” -从这里这里的文章看来,作者断言这对矩阵的存储或转移到GPU的方式没有影响,而在第二个方面分页该矩阵显然不等同于将其布置在行大的内存中;如果我查看程序中的填充矩阵,我会看到翻译组件占据了第4、8和12个元素。

鉴于:

“与列主矩阵的后乘运算产生的结果与与行主矩阵的预乘结果相同。”

为什么在以下代码段中:

        Matrix4 r = t3 * t2 * t1;
        Matrix4 r2 = t1.Transpose() * t2.Transpose() * t3.Transpose();

r!= r2 为何为什么pos3!= pos为什么

        Vector4 pos = wvpM * new Vector4(0f, 15f, 15f, 1);
        Vector4 pos3 = wvpM.Transpose() * new Vector4(0f, 15f, 15f, 1);

乘法过程是否根据矩阵是行还是列主矩阵而改变,或者仅仅是顺序(对于等效效果)?

让事情变得更清晰的一件事是,当提供给DirectX时,我的列主WVP矩阵已成功通过HLSL调用成功用于转换顶点:mul(vector,matrix),应将向量视为row-major,那么我的数学库提供的列主矩阵如何工作?



Answers:


11

如果我查看程序中的填充矩阵,则会看到翻译组件占据了第4、8和12个元素。

在开始之前,重要的是要了解:这意味着您的矩阵是行专业的。因此,您回答以下问题:

我的列主WVP矩阵已成功通过HLSL调用成功用于转换顶点:mul(vector,matrix),这将导致向量被视为行主矩阵,那么我的数学库提供的列主矩阵如何工作?

非常简单:您的矩阵是主要行。

如此多的人使用行优先矩阵或转置矩阵,以至于他们忘记了矩阵不是自然地定向的。因此他们看到了一个翻译矩阵:

1 0 0 0
0 1 0 0
0 0 1 0
x y z 1

这是转置的转换矩阵。那不是正常的翻译矩阵的样子。翻译在第四而不是第四行中进行。有时,您甚至在教科书中看到了这一点,这简直是垃圾。

很容易知道数组中的矩阵是行还是列。如果是行优先的,则转换将存储在第3、7和11个索引中。如果列是主要列,则翻译将存储在第12、13和14个索引中。当然是零基索引。

您之所以感到困惑,是因为您实际上在使用行优先矩阵,却认为您正在使用列优先矩阵。

行与列专业是仅符号约定的说法是完全正确的。无论采用哪种约定,矩阵乘法和矩阵/矢量乘法的机制都是相同的。

变化的是结果的含义。

毕竟4x4矩阵只是4x4的数字网格。它不具有参考坐标系的变化。但是,一旦为特定矩阵分配了含义,您现在需要知道其中存储了什么以及如何使用它。

以我上面显示的翻译矩阵为准。那是一个有效的矩阵。您可以float[16]通过以下两种方式之一存储该矩阵:

float row_major_t[16] =    {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y, z, 1};
float column_major_t[16] = {1, 0, 0, x, 0, 1, 0, y, 0, 0, 1, z, 0, 0, 0, 1};

但是,我说这个翻译矩阵是错误的,因为翻译位置不正确。我专门说过,它是相对于如何构建翻译矩阵的标准约定而换位的,应该看起来像这样:

1 0 0 x
0 1 0 y
0 0 1 z
0 0 0 1

让我们看看它们是如何存储的:

float row_major[16] =    {1, 0, 0, x, 0, 1, 0, y, 0, 0, 1, z, 0, 0, 0, 1};
float column_major[16] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y, z, 1};

请注意,column_major完全一样row_major_t。因此,如果我们采用适当的转换矩阵并将其存储为以列为主,则与转置该矩阵并将其存储为以行为主相同。

那就是仅是一种符号约定。实际上有两套约定:内存存储和转置。内存存储主要是列与行,而转置是正常与转置。

如果您有按行优先顺序生成的矩阵,则可以通过转置该矩阵的按列优先等效项来获得相同的效果。反之亦然。

矩阵乘法只能以一种方式完成:给定两个矩阵,并以特定的顺序将某些值相乘并存储结果。现在,,A*B != B*A但是的实际源代码A*B与的代码相同B*A。它们都运行相同的代码来计算输出。

矩阵乘法代码不在乎矩阵是按列优先还是行优先顺序存储。

向量/矩阵乘法不能说相同。这就是为什么。

向量/矩阵相乘是个错误。这是不可能的。但是,您可以将一个矩阵乘以另一个矩阵。因此,如果您假设向量是矩阵,那么只需进行矩阵/矩阵乘法,就可以有效地进行向量/矩阵乘法。

可以将4D向量视为列向量或行向量。也就是说,可以将4D向量视为4x1矩阵(请记住:在矩阵表示法中,行数排在最前面)或1x4矩阵。

但这就是问题:给定两个矩阵A和B,A*B仅当A的列数与B的行数相同时才定义。因此,如果A是我们的4x4矩阵,则B必须是4行的矩阵在里面。因此,您无法执行A*xx为行向量的情况。同样,x*A如果x是列向量,则无法执行。

因此,大多数矩阵数学库都做出了这样的假设:如果将向量乘以矩阵,就意味着要进行实际可行的乘法运算,而不是没有意义的乘法运算。

让我们为任何4D向量x定义以下内容。C应该是的列向量矩阵形式x,并且R应该是的行向量矩阵形式x。鉴于此,对于任何4x4矩阵A,都A*C表示矩阵乘以A的列向量x。并R*A表示将行向量x乘以A的矩阵。

但是,如果我们使用严格的矩阵数学来观察这一点,就会发现它们并不等效R*A 不能与相同A*C。这是因为行向量与列向量不同。它们不是相同的矩阵,因此它们不会产生相同的结果。

但是,它们以一种方式相关。的确是这样R != C。但是,也确实是,其中T是转置运算。这两个矩阵互为换位。R = CT

这是一个有趣的事实。由于向量被视为矩阵,因此它们也存在列与行为主的存储问题。问题是它们看上去都一样。浮点数组是相同的,因此仅通过查看数据就无法分辨出R和C之间的区别。区别的唯一方法是如何使用它们。

如果您有任意两个矩阵A和B,并且A存储为行为主,B存储为列主,则将它们相乘是完全没有意义的。结果是胡说八道。好吧,不是真的。从数学上讲,您所获得的等同于做。或; 它们在数学上是相同的。AT*BA*BT

因此,矩阵乘法只有在两个矩阵以相同的主要顺序存储时才有意义(记住:矢量/矩阵乘法只是矩阵乘法)。

那么,矢量是列优先还是行优先?如前所述,它既是又不是。仅当它用作列矩阵时才是列专业,而当它用作行矩阵时才是行专业。

因此,如果您有一个以列为主的矩阵A,则x*A意味着...没有任何意义。好吧,这再次意味着,但这不是您真正想要的。同样,如果行大,则进行转置乘法。x*ATA*xA

因此,矢量/矩阵乘法的顺序改变,具体取决于数据的主要顺序(以及是否使用转置矩阵)。

为什么在以下代码段中r!= r2

因为您的代码已损坏且存在错误。数学上,。如果没有得到此结果,则说明您的相等性测试错误(浮点精度问题)或矩阵乘法代码已损坏。A * (B * C) == (CT * BT) * AT

为什么pos3!= pos

因为那没有道理。唯一的正确方法是if 。但这仅适用于对称矩阵。A * t == AT * tA == AT


@Nicol,现在一切开始单击。由于我所看到的与我应该的样子之间存在脱节,所以造成了混乱,因为我的库(取自Axiom)声明列为主要列(所有乘法顺序等都符合此列),但内存布局为行-主要(根据翻译索引和事实,HLSL使用非转置矩阵可以正常工作);我现在知道这是如何不冲突的。非常感谢你!
sebf 2011年

2
我几乎给您一个-1来表示诸如“那不是正常翻译矩阵的样子”和“那完全是垃圾”之类的东西。然后,您继续并很好地解释为什么它们完全等效,因此没有一个比另一个更“自然”。您为什么不从一开始就删除一点废话呢?您剩下的答案实际上是相当不错的。(此外,对于感兴趣的人:steve.hollasch.net/cgindex/math/matrix/column-vec.html
imre

2
@imre:因为这不是废话。约定很重要,因为有两个约定令人困惑。很久以前,数学家就决定采用矩阵惯例。“转置矩阵”(之所以命名是因为它们是从标准中转置的),违反了该约定。由于它们是等效的,因此不会给用户带来实际利益。而且由于它们不同并且可以被滥用,因此会造成混乱。换句话说,如果不存在转置矩阵,OP将永远不会问这个问题。因此,这种替代惯例会造成混乱。
Nicol Bolas

1
@Nicol:在12-13-14中转换的矩阵仍然可以是行主要的-如果我们随后将行向量与它一起使用(并乘以vM)。请参阅DirectX。或者可以将其视为主要列,与列向量(Mv,OpenGL)一起使用。真的一样 相反,如果矩阵在3-7-11中具有平移,则可以将其视为具有列向量的以行为主的矩阵,或具有行向量的以列为主的矩阵。12-13-14版本确实更常见,但是我认为1)并不是真正的标准,并且2)称其为column-major可能会产生误导,因为不一定如此。
imre

1
@imre:这是标准的。询问任何实际训练有素的数学家,译文会去哪里,他们会在第四栏中告诉您译文。数学家发明了矩阵。他们是制定惯例的人。
Nicol Bolas

3

在这里,有两种不同的约定可供选择。一种是使用行向量还是列向量,而这些约定的矩阵是彼此的转置。

另一个是您是按行优先顺序还是列优先顺序将矩阵存储在内存中。请注意,“行优先”和“列优先” 不是讨论行向量/列向量约定的正确术语……尽管许多人都这样滥用它们。行主要和列主要的内存布局也因转置而不同。

OpenGL使用列向量约定和列主存储顺序,而D3D使用行向量约定和行主存储顺序(嗯-至少D3DX,数学库是这样做的),所以这两个转置被抵消,结果是相同的内存布局适用于OpenGL和D3D。也就是说,在内存中顺序存储的16个浮点数的相同列表在两个API中的工作方式相同。

这可能就是人们所说的“对矩阵的存储或转移到GPU的方式没有影响”。

至于您的代码段,r!= r2是因为产品转置的规则是(ABC)^ T = C ^ TB ^ TA ^ T。换位以重整顺序分布在乘法上。因此,在您的情况下,您应该得到r.Transpose()== r2,而不是r == r2。

同样,pos!= pos3,因为您已转置但未反转乘法顺序。您应该获得wpvM * localPos == localPos * wvpM.Tranpose()。向量在矩阵的左侧相乘时会自动解释为行向量,在矩阵的右侧相乘时会自动解释为列向量。除此之外,乘法的方式没有改变。

最后,重新:“我的列主WVP矩阵已成功用于HLSL调用:mul(vector,matrix)来转换顶点,”我对此不确定,但是可能是混淆/错误导致矩阵从数学库已经调换。


1

在3d图形中,您可以使用矩阵来变换矢量和点。考虑到您在谈论转换矩阵这一事实,我只讲点(您不能用矩阵转换向量,或者说得更好,可以,但是您将获得相同的向量)。

矩阵乘法中,第一个矩阵的列数应等于第二个矩阵的行数(您可以将anxm矩阵乘以mxk)。

一个点(或一个向量)由3个分量(x,y,z)表示,可以被视为行或列:

列(尺寸3 X 1):

| x |

| y |

| z |

要么

行(尺寸1 X 3):

| x,y,z |

您可以选择首选约定,这只是一个约定。我们将其称为T转换矩阵。如果选择第一个约定,则为了将点p乘以矩阵,您需要使用后乘:

T * v(尺寸3x3 * 3x1)

除此以外:

v * T(尺寸1x3 * 3x3)

作者似乎断言,矩阵的存储或转移到GPU的方式没有区别

如果您始终使用相同的约定,则没有区别。这并不意味着不同约定的矩阵将具有相同的内存表示形式,而是使用2种不同约定转换一个点将获得相同的转换点:

p2 = B * A * p1; //第一个约定

p3 = p1 * A * B; //第二个约定

p2 == p3;


1

我看到翻译成分占据了第4、8和12个元素,这意味着您的矩阵是“错误的”。

转换组件始终指定为转换矩阵的条目#13,#14和#15将数组的第一个元素视为元素#1)。

行主要转换矩阵如下所示:

[ 2 2 2 1 ]   R00 R01 R02 0  
              R10 R11 R12 0 
              R20 R21 R22 0 
              t.x t.y t.z 1 

列主变换矩阵如下所示:

 R00 R01 R02 t.x   2  
 R10 R11 R12 t.y   2 
 R20 R21 R22 t.z   2 
  0   0   0   1    1 

行主要矩阵在行的下方指定

将上面的行主矩阵声明为线性数组,我会这样写:

ROW_MAJOR = { R00, R01, R02, 0,  // row 1 // very intuitive
              R10, R11, R12, 0,  // row 2
              R20, R21, R22, 0,  // row 3
              t.x, t.y, t.z, 1 } ; // row 4

看来很自然。因为要注意,英语是用“行为主”写的-矩阵在上面的文本中的出现与数学中的完全一样。

这就是混乱的地方。

在列下指定列主矩阵

这意味着要在代码中将列主变换矩阵指定为线性数组,您必须编写:

    COLUMN_MAJOR = {R00,R10,R20,0, // COLUMN#1 //非常违反直觉
                     R01,R11,R21、0,
                     R02,R12,R22、0,
                     tx,ty,tz,1};

注意,这完全违反直觉!!初始化线性数组时, 列主矩阵的条目在列下方指定,因此第一行

COLUMN_MAJOR = { R00, R10, R20, 0,

指定矩阵的第一列

 R00
 R10
 R20
  0 

而不是第一行,因为您会相信文本的简单布局。在代码中看到列主矩阵时,您必须在思维上进行转置,因为指定的前4个元素实际上描述了第一列。我想这就是为什么很多人在代码中喜欢行优先矩阵(GO DIRECT3D !!咳嗽)的原因。

因此,无论您使用行主矩阵还是列主矩阵,转换分量始终位于线性数组索引#13,#14和#15(其中第一个元素为#1)上。

您的代码发生了什么,为什么有效?

您的代码中发生的事情是,您有一个major-matrix列,但是您将翻译组件放在了错误的位置。转置矩阵时,条目#4进入条目#13,条目#8至#13,条目#12至#15。那里有。


0

简而言之,差异的原因是矩阵乘法不是可交换的。使用数字的规则乘法,如果A * B = C,则得出B * A也=C。矩阵不是这种情况。这就是为什么选择行优先或列重要的原因。

为什么没有事情是,在一个现代化的API(和我专门这里所说的着色器),你可以选择自己的惯例,增加你在正确的顺序在自己的着色器的代码,约定矩阵。该API不再对您强制执行任何一项。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.