您似乎在这里混合了两个问题,我猜这就是 scrapapixel 上的网页试图解释的内容。从您所引用的页面上的信息来看,事情似乎很清楚,但是很难将此类内容牢记在心。你有理论(你用笔和纸在数学中做什么)和你的实现(C++)。这是两个不同的问题。
数学:您可以使用两种符号,即列专业或行专业。正如 GraphicsMuncher 在此网页上提到的,对于行主向量,在纸上,您需要编写向量矩阵乘法 vM,其中 v 是行向量 (1x4),M 是您的 4x4 矩阵,为什么,因为您只能在数学上写[1x4]*[4x4],而不是相反。同样,如果您使用列,则向量需要垂直书写,或以符号 [4x1](4 行,1 列)的形式书写。因此,与矩阵的乘法只能写成:[4x4][4x1]。矩阵放在向量:Mv 前面。第一个表示法称为后乘法,第二个 (Mv) 称为前乘法(矩阵在前面)。
现在,正如 GraphicsMuncher 所提到的,如果您需要变换向量(或点),那么当您将它们写在纸上时,您需要注意乘法的顺序。如果你想用矩阵 T 平移一些东西,然后用 R 旋转,然后用 S 缩放,那么在列主要世界中,你需要写 v' = S * R * T * v。在行主要世界中你需要写成 v' = v * T * R * S。
这是为了理论。
计算机:当你决定用 C++ 实现这个时,就到了关键的时刻。这样做的好处是 C++ 不会强加给你任何事情。您可以按照您想要的方式将矩阵系数的值映射到内存中,并且可以编写代码以按照您想要的方式执行与另一个矩阵的矩阵乘法。同样,如何访问向量矩阵乘法的系数完全取决于您。
您需要清楚地区分如何在内存中映射系数,以及从数学角度来看需要使用哪些约定来表示向量。这是两个独立的问题。例如,在您的情况下,您可能将矩阵类声明为包含 16 个连续浮点数的数组。没关系。其中系数 m14、m24、m34 表示矩阵的平移部分(Tx、Ty、Tz),因此您假设您的“约定”是行优先,即使您被告知使用 OpenGL 矩阵约定(据说是列)主要的。在这里,您的困惑来自于这样一个事实:内存中系数的映射与您自己制作的“列主”矩阵的心理表示不同。你编码“行”,但据说你使用(从数学角度来看)“列”,因此你很难理解你做的事情是对还是错。
重要的是要将矩阵视为由三个轴和平移定义的坐标系的表示。将这些数据存储在内存中的位置和方式完全取决于您。假设代表坐标系三个轴的三个向量分别命名为AX(x,y,z)、AY(x,y,z)、AZ(x,y,z),平移向量记为(Tx ,Ty,Tz),那么从数学上来说,如果你使用你拥有的列向量(我猜乳胶不支持):
AXx AYx AZx Tx
M = AXy AYy AZy Ty
AXz AYz AZz Tz
0 0 0 1
坐标系的轴是垂直书写的。现在,如果您使用行优先:
AXx AXy AXz 0
M = AYx AYy AYz 0
AZx AZy AZz 0
Tx Ty Tz 1
坐标系的轴是水平书写的。因此,现在涉及计算机世界的问题是如何将这些系数存储在内存中。你也可以这样做:
float m[16] = { AXx, AXy, AXz, 0, AYx, AYy, AYz, 0, AZx, AZy, AZz, 0, Tx, Ty, Tz, 1};
它会告诉您您使用哪种约定吗?不,你还可以写:
float m[16] = { AXx, AXy, AXz, Tx, AYx, AYy, AYz, Ty, AZx, AZy, AZz, Tz, 0, 0, 0, 1};
or
float m[16] = { AXx, AYx, AZx, Tx, AXy, AYy, AZy, Ty, AXz, AYz, AZz, Tz, 0, 0, 0, 1};
再说一次,这并没有给你一个具体的指示,告诉你你使用的是哪种“数学”约定。您只是以不同的方式将 16 个系数存储在内存中,只要您知道这种方式是什么就完全没问题,以便您以后可以适当地访问它们。现在请记住,无论您使用行还是列数学符号,向量乘以矩阵都应该给出相同的向量。因此,真正重要的是,将向量的 (x,y,z) 坐标乘以矩阵中的正确系数,这需要了解“您”如何决定将矩阵系数存储在内存中:
Vector3 vecMatMult (Vector3 v,
float AXx, float AXy, float AXz, float Tx,
float AYx, float AYy, float AYz, float Ty,
float AZz, float AZy, float AZz, float Tz) {
return Vector3(
v.x * AXx + v.y * AYx + v.z * AZx + Tx,
v.x * AXy + v.y * AYy + v.z * AZy + Ty,
v.x * AXz + v.y * AYz + v.z * AZz + Tz
}
编辑:上面的代码是错误的,现在修复它。
我编写这个函数是为了强调这样一个事实:无论您使用哪种约定,向量 * 矩阵乘法的结果都只是向量的输入坐标与坐标系的轴坐标 AX、AY 和 AZ 之间的乘法和加法(无论您使用的符号,无论您如何将它们存储在内存中)。
如果您使用:
float m[16] = { AXx, AXy, AXz, 0, AYx, AYy, AYz, 0, AZx, AZy, AZz, 0, Tx, Ty, Tz, 1};
您需要致电:
vecMatMult(v, m[0], m[1], m[2], m[12], m[4], m[5], m[6], m[13], ...
如果您使用:
float m[16] = { AXx, AYx, AZx, Tx, AXy, AYy, AZy, Ty, AXz, AYz, AZz, Tz, 0, 0, 0, 1};
您需要致电:
vecMatMult(v, m[0], m[4], m[8], m[3], m[1], m[5], m[9], m[10], ...
这是否告诉您您使用哪种约定?不需要。当您进行 vec * mat 乘法时,您只需要在正确的位置调用正确的系数即可。这就是它的全部内容,尽管看起来可能令人不安。
现在,当涉及到 mat * mat 乘法时,情况略有不同。您可以假设矩阵相乘的顺序不同。所以 R * S * T 与 T * S * R 不同。顺序确实很重要。现在,如果您使用“行专业”,那么从数学上讲,您需要编写(使用您的符号):
mt11 = ml11 * mr11 + ml12 * mr21 + m13 * m31 + ml14 * mr41
其中 ml 是左手矩阵,mr 是右手矩阵:mt = ml * mr。但请注意,我没有使用方括号 [] 作为访问索引,因为我不想建议我们访问存储在此处的一维数组中的元素。我们只是在讨论矩阵的系数。如果您想用 C++ 编写此代码,那么这完全取决于您如何将系数存储在内存中,如上所述。
希望能帮助到你。