5

我有以下功能:

void Matrix::Scale(const float xScale, const float yScale, const float zScale)
{
    Matrix scaleMatrix;
    scaleMatrix.m_data[M11] = xScale;
    scaleMatrix.m_data[M22] = yScale;
    scaleMatrix.m_data[M33] = zScale;
    *this *= scaleMatrix;
}

void Matrix::Translate(const float xTranslation, const float yTranslation, const float zTranslation)
{
    Matrix translationMatrix;
    translationMatrix.m_data[M14] = xTranslation;
    translationMatrix.m_data[M24] = yTranslation;
    translationMatrix.m_data[M34] = zTranslation;
    *this *= translationMatrix;
}

而且我不确定这两个函数的最后几行。我应该做预乘法还是后乘法(即我现在正在做的事情)。它对这个类的使用有什么影响?我将这个类与 OpenGL 一起使用,所以与它的任何相似之处都可能有用。

编辑:

我的着色器代码如下所示:

void main()
{
    gl_Position = vec4(v_xy, 0.0, 1.0) * v_ModelMatrix * v_ViewMatrix * v_ProjectionMatrix;
    f_uv = v_uv;
}

我的矩阵乘法函数如下所示:

// Row 1
result[M11] = lhs[M11] * rhs[M11]   +   lhs[M12] * rhs[M21]   +   lhs[M13] * rhs[M31]   +   lhs[M14] * rhs[M41];    // Column 1
result[M12] = lhs[M11] * rhs[M12]   +   lhs[M12] * rhs[M22]   +   lhs[M13] * rhs[M32]   +   lhs[M14] * rhs[M42];    // Column 2
result[M13] = lhs[M11] * rhs[M13]   +   lhs[M12] * rhs[M23]   +   lhs[M13] * rhs[M33]   +   lhs[M14] * rhs[M43];    // Column 3
result[M14] = lhs[M11] * rhs[M14]   +   lhs[M12] * rhs[M24]   +   lhs[M13] * rhs[M34]   +   lhs[M14] * rhs[M44];    // Column 4

// Row 2
result[M21] = lhs[M21] * rhs[M11]   +   lhs[M22] * rhs[M21]   +   lhs[M23] * rhs[M31]   +   lhs[M24] * rhs[M41];    // Column 1
result[M22] = lhs[M21] * rhs[M12]   +   lhs[M22] * rhs[M22]   +   lhs[M23] * rhs[M32]   +   lhs[M24] * rhs[M42];    // Column 2
result[M23] = lhs[M21] * rhs[M13]   +   lhs[M22] * rhs[M23]   +   lhs[M23] * rhs[M33]   +   lhs[M24] * rhs[M43];    // Column 3
result[M24] = lhs[M21] * rhs[M14]   +   lhs[M22] * rhs[M24]   +   lhs[M23] * rhs[M34]   +   lhs[M24] * rhs[M44];    // Column 4

// Row 3
result[M31] = lhs[M31] * rhs[M11]   +   lhs[M32] * rhs[M21]   +   lhs[M33] * rhs[M31]   +   lhs[M34] * rhs[M41];    // Column 1
result[M32] = lhs[M31] * rhs[M12]   +   lhs[M32] * rhs[M22]   +   lhs[M33] * rhs[M32]   +   lhs[M34] * rhs[M42];    // Column 2
result[M33] = lhs[M31] * rhs[M13]   +   lhs[M32] * rhs[M23]   +   lhs[M33] * rhs[M33]   +   lhs[M34] * rhs[M43];    // Column 3
result[M34] = lhs[M31] * rhs[M14]   +   lhs[M32] * rhs[M24]   +   lhs[M33] * rhs[M34]   +   lhs[M34] * rhs[M44];    // Column 4

// Row 4
result[M41] = lhs[M41] * rhs[M11]   +   lhs[M42] * rhs[M21]   +   lhs[M43] * rhs[M31]   +   lhs[M44] * rhs[M41];    // Column 1
result[M42] = lhs[M41] * rhs[M12]   +   lhs[M42] * rhs[M22]   +   lhs[M43] * rhs[M32]   +   lhs[M44] * rhs[M42];    // Column 2
result[M43] = lhs[M41] * rhs[M13]   +   lhs[M42] * rhs[M23]   +   lhs[M43] * rhs[M33]   +   lhs[M44] * rhs[M43];    // Column 3
result[M44] = lhs[M41] * rhs[M14]   +   lhs[M42] * rhs[M24]   +   lhs[M43] * rhs[M34]   +   lhs[M44] * rhs[M44];    // Column 4

我的印象是,如果您对矩阵进行后乘(即viewMatrix = transform * viewMatrix;),那么您的着色器代码需要以与我目前相反的顺序应用 MVP?

编辑2:

http://scratchapixel.com/lessons/3d-basic-lessons/lesson-4-geometry/conventions-again-row-major-vs-column-major-vector/上的汇总表让我很困惑,因为我使用 OpenGL 的后乘(表示列优先),但我的矩阵在内存中布局为行优先?

4

3 回答 3

21

您似乎在这里混合了两个问题,我猜scratchapixel上的网页试图解释什么。从您所指的页面上的信息来看,事情似乎很清楚,但是很难将这类东西牢记在心。你有理论(你用笔和纸在数学上做什么)和你的实现做什么(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}; 

或者

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。但是请注意,我没有使用方括号 [] 作为访问索引,因为我不想建议我们在这里访问存储在 1D 数组中的元素。我们只是在谈论矩阵的系数。如果您想用 C++ 编写它,那么这完全取决于您如何将系数存储在内存中,如上所述。

希望能帮助到你。

于 2013-07-27T11:54:35.640 回答
2

矩阵是关联的,这意味着

ABC = (AB)C = A(BC)

(AB vs BC)因此,只要您保持顺序相同,您实际上首先将哪些矩阵相乘并不重要。前后乘法是一个ABvs的问题BA

话虽如此,这个问题解释了如果您希望您的转换以您想要的方式出现,您应该乘以缩放/旋转/平移的顺序。如果您使用行矩阵而不是列矩阵,则会颠倒顺序并转置每个元素。这是一个提供更好解释的幻灯片(跳转到幻灯片 19)。

严格按照矩阵,列矩阵的顺序是T * R * S或 Translate * Rotate * Scale。所以如果你从身份I开始,后乘法(你在做什么)是正确的。如果您更改为预乘,则XL的转换最终将成为L * X,因此为了获得T * R * S您将翻转执行调用的顺序。

于 2013-07-22T14:04:59.913 回答
0

创建一个类 matrix 来保存 4x1 大小的实数矩阵。将数字保存在数组中。主要创建两个对象,A 和 B 由用户填充。创建另一个对象 C。C 实际上是 A 和 B 的总和。您应该有一个函数 add,它接收两个矩阵对象并将它们的总和作为单独的矩阵返回。矩阵 C=add(A,B); 以适当的格式在屏幕上显示三个对象.in c++

于 2014-04-06T17:24:34.057 回答