为什么android.opengl.Matrix.translateM()会产生意想不到的结果?

时间:2013-11-17 10:28:37

标签: java android matrix opengl-es-2.0 matrix-transform

来自android.opengl.Matrix.translateM(float[] m, int mOffset, float x, float y, float z)的{​​{3}}:

/**
 * Translates matrix m by x, y, and z in place.
 * @param m matrix
 * @param mOffset index into m where the matrix starts
 * @param x translation factor x
 * @param y translation factor y
 * @param z translation factor z
 */
public static void translateM(
        float[] m, int mOffset,
        float x, float y, float z) {
    for (int i=0 ; i<4 ; i++) {
        int mi = mOffset + i;
        m[12 + mi] += m[mi] * x + m[4 + mi] * y + m[8 + mi] * z;
    }
}

问题在于这一行(或整个for循环):

m[12 + mi] += m[mi] * x + m[4 + mi] * y + m[8 + mi] * z;

为什么转换参数的x,y和z分量在这里与源矩阵分量相乘?这不应该是简单的(这条线代替整个for-cycle):

m[12 + mi] += x;
m[13 + mi] += y;
m[14 + mi] += z;

问题背景

我在OpenGL ES 2.0中做了一些2D游戏。我想扩大规模并在那里移动一些物体。虽然我简单地移动它:

Matrix.setIdentityM(mModelMatrix, 0);
Matrix.translateM(mModelMatrix, 0, x, y, 0);
一切都很好。一旦我在移动之前进行缩放 - 此时移动翻译:

Matrix.setIdentityM(mModelMatrix, 0);
Matrix.scaleM(mModelMatrix, 0, xScaleFactor, yScaleFactor, 1);
Matrix.translateM(mModelMatrix, 0, x, y, 0);

实际上乘以缩放:(

2 个答案:

答案 0 :(得分:2)

OpenGL是列专业,缩放,旋转和翻译的正确顺序实际上是Translation * Rotation * Scaling。在D3D和任何行主矩阵库中,Scale * Rotate * Translate都是正确的。当你使用列主矩阵时,你必须考虑从右到左的事情。

或者,您可以在乘法之前转置每个矩阵 - 但通常只需遵循列主要矩阵乘法的规范顺序即可。请注意,这也适用于Position * Model * View * Projection(D3D / row-major)之类的内容,在GL(column-major)中,正确的顺序为Projection * View * Model * Position

答案 1 :(得分:0)

translateM 模拟m X T的矩阵乘法,其中T是转换矩阵。

翻译矩阵就像恒等矩阵(对角1),最后一列是 x,y,z,1 用于翻译。 ( 1 用于同质坐标,我将不讨论)。

假设您知道矩阵乘法(其中第一个操作数的每一行由第二个操作数的每一列成对加倍),则可以利用转换矩阵T的细节来简化操作: / p>

它实际上并不创建转换矩阵T,而是模拟乘法m x T。大部分值不会改变(因为像身份矩阵一样),因此只需要计算会改变的值(涉及T的最后一列)即可。

每个循环迭代将m的一行与转换矩阵的最后一列(即x,y,z,1)相乘。代码行将是(其中 i m的行和T的列):

m[12+i] = m[0+i]*x + m[4+i]*y + m[8+i]*z + m[12+i]*1f;

,但是它们使用+=运算符对其进行了简化;使用mi来补偿;并省略0+

不变的元素可以留在这里,因为translateM的这个版本是“就地”。

请记住,它使用 column-major 形式的矩阵,并带有索引:

0  4  8  12
1  5  9  13
2  6  10 14
3  7  11 15

在遍历数组时,向下浏览一列中的每个元素,并在其末尾转到下一个列,依此类推。因此, i 行和列中的元素 j 位于m[i + 4*j]。也就是说,下降是 +1 ,穿过是 +4 。 thr next(另一种方式是 row-major ,您可以在其中连续浏览,向下浏览到下一行等。打印起来更直观,但android.opengl.Matrix的使用却不是。)< / p>


您的scaleMtranslateM等价于S x T(缩放矩阵S,转换矩阵T)。效果是从平移到缩放的从右向左(即向后)。因此,翻译也得到了扩展。

正如公认的答案所说,先进行translateM然后scaleM(即T x S)具有缩放然后翻译的效果(因此翻译不会缩放)。

对此进行考虑的一种方法是,该矩阵最终将与列向量v(T x S) x v相乘。从S x v = v'缩放v开始,然后用T x v'转换结果,我们可以将它们合并为T x (S x v)。必须首先进行缩放,这很自然。也许令人惊讶,因为矩阵乘法是 associative ,所以它与(T x S) x v相同。如果我们忽略最后一个x v(在GPU上完成),则我们有T x S,表示“缩放,然后 then 转换”。最右边的矩阵是首先完成的。

顺便说一句,矩阵乘法的关联性就像Java字符串串联一样,其中求值顺序无关紧要,(a+b)+c = a+(b+c)在那里(尽管操作数确实很重要,例如{{ 1}}-可交换)。这意味着我们可以选择适合我们的任何一部分。


顺便说一句:这里有几层:矩阵乘法,排序的效果,齐次坐标,平移矩阵,乘法排序的效果,列主形式,上面的乘法快捷键a+b+c != c+b+a和{ {1}} mi`(用于一个数组中的许多矩阵)。

您可以理解很多编程知识,但是为此,最好依次进入每一层。