4x4矩阵预乘法与后乘法 [英] 4x4 Matrix Pre-multiplication vs Post-multiplication

查看:599
本文介绍了4x4矩阵预乘法与后乘法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下函数:

  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
results [M12] = lhs [M11] * rhs [M12] + lhs [M12] * rhs [M22] + lhs [M13] * rhs [M32] + lhs [M14] M42]; //第2栏
结果[M13] = 1hs [M11] * rhs [M13] + lhs [M12] * rhs [M23] + lhs [M13] * rhs [M33] + lhs [M14] M43]; //列3
结果[M14] = lhs [M11] * rhs [M14] + lhs [M12] * rhs [M24] + lhs [M13] * rhs [M34] + lhs [M14] 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
results [M22] = lhs [M21] * rhs [M12] + lhs [M22] * rhs [M22] + lhs [M23] * rhs [M32] + lhs [M24] M42]; // Column 2
results [M23] = lhs [M21] * rhs [M13] + lhs [M22] * rhs [M23] + lhs [M23] * rhs [M33] + lhs [M24] M43]; //列3
结果[M24] = 1hs [M21] * rhs [M14] + lhs [M22] * rhs [M24] + lhs [M23] * rhs [M34] + lhs [M24] 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] M42]; //第2栏
结果[M33] = 1hs [M31] * rhs [M13] + lhs [M32] * rhs [M23] + lhs [M33] M43]; //列3
结果[M34] = 1hs [M31] * rhs [M14] + lhs [M32] * rhs [M24] + lhs [M33] * rhs [M34] + lhs [M34] 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
results [M42] = lhs [M41] * rhs [M12] + lhs [M42] * rhs [M22] + lhs [M43] * rhs [M32] + lhs [M44] M42]; //栏2
结果[M43] = lhs [M41] * rhs [M13] + lhs [M42] * rhs [M23] + lhs [M43] * rhs [M33] + lhs [M44] M43]; //列3
结果[M44] = lhs [M41] * rhs [M14] + lhs [M42] * rhs [M24] + lhs [M43] * rhs [M34] + lhs [M44] M44]; // Column 4

我的印象是,如果你后乘乘矩阵c $ c> viewMatrix = transform * viewMatrix; )那么你的着色器代码需要以与我目前相反的顺序应用MVP。



Edit2:



http://scratchapixel.com/lessons/3d-basic-lessons/lesson-4-geometry/由于我使用后乘法与OpenGL(指示列主),但我的矩阵是布局在内存中的row-major?

您似乎在这里混合两个问题,这是我想在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),并且平移向量由,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(
vx * AXx + vy * AYx + vz * AZx + Tx ,
vx * AXy + vy * AYy + vz * AZy + Ty,
vx * AXz + vy * AYz + vz * AZz + Tz
}



编辑:上面的代码是错误的,现在修复。



无论你使用哪个约定,向量*矩阵乘法的结果只是一个乘法和向量的输入坐标和坐标系统的坐标轴AX,AY和AZ之间的加法(不管你使用的符号



如果您使用:

  float m [16] = {AXx,AXy,AXz,0,AYx,AYy,AYz,0,AZx,AZy,AZz,0,Tx,Ty,Tz, 

您需要拨打:

  vecMatMult(v,m [0],m [1],m [2],m [12],m [4],m [5] ],... 

如果使用:

  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] [10],... 

这会告诉你使用哪个约定在你做一个vec * mat乘法时,在正确的地方调用正确的系数。这是所有的东西,因为它可能是令人不安的。



可以假设乘法矩阵的顺序不相同,所以R * S * T与T * S * R不一样。顺序确实有问题现在再次如果你使用行主要然后数学你需要写(使用你的符号):



mt11 = ml11 * mr11 + ml12 * mr21 + m13 * m31 + ml14 * mr41



其中ml是左手矩阵,mr右手一个:mt = ml * mr。但是请注意,我没有使用括号[]用于访问索引,因为我不想建议我们访问存储在1D数组中的元素。我们只是谈论矩阵的系数。如果你想在C ++中写这个,那么这一切都取决于你如何存储你的系数在内存中如上所述。



希望它有帮助。


I have the following functions:

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;
}

And I'm unsure about the last lines of both functions. Should I be doing pre-multiplication, or post-multiplication (i.e. what I'm doing now). What implications does it have for the use of this class? I'm using the class with OpenGL, so any similarities to that would probably be useful.

Edit:

My shader code looks like this:

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

My matrix multiplication function looks like this:

// 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

I was under the impression that if you post-multiply your matrices (i.e. viewMatrix = transform * viewMatrix;) then your shader code needs to apply MVP in the opposite order that I currently have?

Edit2:

The summary table on http://scratchapixel.com/lessons/3d-basic-lessons/lesson-4-geometry/conventions-again-row-major-vs-column-major-vector/ is confusing me, as I'm using post-multiply with OpenGL (indicating column-major), yet my matrix is laid out in memory as row-major?

解决方案

You seem to be mixing two issues here which is I guess what the webpage on scratchapixel is trying to explain. From the information that is on the page you are referring to, things seem pretty clear but getting this type of stuff right in the mind is hard. You have the theory (what you do in mathematics with a pen and paper) and what you do with your implementation (C++). These are two different problems.

Mathematics: you can use two notations, either column or row major. As mentioned by GraphicsMuncher on this webpage as well, with row major vector, on paper, you need to write the vector-matrix multiplication vM where v is the row vector (1x4) and M your 4x4 matrix, why because you can mathematically only write [1x4]*[4x4], and not the other way around. Similarly if you use column, then the vector needs to be written down vertically, or in notation [4x1] (4 rows, 1 column). Thus, the multiplication with a matrix can only be written: [4x4][4x1]. The matrix is put in front of the vector: Mv. The first notation is called post-multiplication and the second (Mv) is called pre-multiplication (the matrix is in front). Now, as mentioned by GraphicsMuncher, if you need to transform a vector (or a point) then you need to pay attention to the order of multiplication, when you write them down ON PAPER. If you want to translate something with matrix T and then rotate with R and then scale with S, then in a column major world, you need to to write v' = S * R * T * v. In a row major world you need to write v' = v * T * R * S.

That's for the theory.

Computer: then comes the point when you decide to implement this in C++ say. The good thing about this is that C++ doesn't impose you anything about anything. You can map the values of your matrix's coefficients in memory the way you want, and you can write the code to perform a matrix multiplication by another matrix the way you want. Similarly how you access the coefficients for a vector-matrix multiplication is completely up to you.

You need to be make a clear distinction about how you map your coefficients in memory and what conventions you need to use from a mathematical point of you view to represent your vectors. These are two independent problems. For instance in you case, you probably declare your matrix class as an array of say 16 contiguous floats. That's fine. Where coefficients m14, m24, m34 represent the translation part of the matrix (Tx, Ty, Tz), so you assume your "convention" is row-major even though you are told to use OpenGL matrix convention which is said to be column-major. Here your confusion comes from the fact that the mapping of the coefficients in memory is different from the mental representation you are making yourself of a "column-major" matrix. You code "row" but you were said to use (from a mathematical point of view) "column", hence your difficulty to make sense of whether you do things right or wrong.

What's important is to see a matrix as a representation of a coordinate system defined by three axes, and a translation. Where and how you store this data in memory is completely up to you. Assuming the three vectors representing the three axes of the coordinate system are named AX(x,y,z), AY(x,y,z), AZ(x,y,z), and the translation vector is denoted by (Tx, Ty, Tz), then mathematically if you use column vector you have (latex not supported I guess):

    AXx AYx AZx Tx 
M = AXy AYy AZy Ty
    AXz AYz AZz Tz
     0   0   0  1 

The axes of the coordinates system are written vertically. Now if you use row-major:

    AXx AXy AXz 0 
M = AYx AYy AYz 0
    AZx AZy AZz 0
     Tx  Ty  Tz 1

The axes of the coordinate system are written horizontally. So the problem now when it comes to the computer world, is how you store these coefficients in memory. You can as well do:

float m[16] = { AXx, AXy, AXz, 0, AYx, AYy, AYz, 0, AZx, AZy, AZz, 0, Tx, Ty, Tz, 1}; 

Does it tell you though which convention you use? No. You can also write:

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};

again, that doesn't give you a particular indication of which "mathematical" convention you use. You are just storing 16 coefficients in memory in different ways and that's perfectly fine as long as you know what that way is, so that you can access them appropriately later on. Now keep in mind that a vector multiplied by a matrix should give you the same vector whether you use a row- or column- mathematical notation. Thus what's important really is that you multiply the (x,y,z) coordinates of your vector by the right coefficients from the matrix, which requires the knowledge of how "you" have decided to store the matrix coefficient in memory:

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
}

EDIT: above code was wrong, fixed it now.

I wrote this function to underline the fact that no matter which convention you use, the resulting of the vector * matrix multiplication is just a multiplication and an addition between the vector's input coordinates and the coordinate system's axis coordinates AX, AY and AZ (regardless of the notation you use, and regardless of the way you store them in memory).

If you use:

float m[16] = { AXx, AXy, AXz, 0, AYx, AYy, AYz, 0, AZx, AZy, AZz, 0, Tx, Ty, Tz, 1};

You need to call:

vecMatMult(v, m[0], m[1], m[2], m[12], m[4], m[5], m[6], m[13], ...

If you use:

float m[16] = { AXx, AYx, AZx, Tx, AXy, AYy, AZy, Ty, AXz, AYz, AZz, Tz, 0, 0, 0, 1};

You need to call:

vecMatMult(v, m[0], m[4], m[8], m[3], m[1], m[5], m[9], m[10], ...

Does that tell you which convention you use? No. You just need to call the right coefficients in the right places when you do a vec * mat multiplication. And that's all there is to it, as disconcerting as it may seem.

Now things are slightly different when it comes to mat * mat multiplication. You can assume that the order in which you multiply the matrices is not the same. So R * S * T is not the same as T * S * R. The order indeed matters. Now again if you use "row major" then mathematically you need to write (using your notation):

mt11 = ml11 * mr11 + ml12 * mr21 + m13 * m31 + ml14 * mr41

where ml is the left hand matrix and mr the right hand one: mt = ml * mr. However note that i haven't been using brackets [] for the access indices because I don't want to suggest we are accessing elements stored in a 1D array here. We are just talking about the coefficients of matrices. If you want to write this in C++, then it all depends of how you have stored your coefficients in memory as suggested above.

Hope it helps.

这篇关于4x4矩阵预乘法与后乘法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆