【问题标题】:Why does android.opengl.Matrix.translateM() produce unexpected result?为什么 android.opengl.Matrix.translateM() 会产生意想不到的结果?
【发布时间】:2013-11-17 10:28:37
【问题描述】:

来自android.opengl.Matrix.translateM(float[] m, int mOffset, float x, float y, float z)sources

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

为什么这里将 translate 参数的 x、y 和 z 分量与源矩阵分量相乘?不应该这么简单吗(这行替换了整个 for 循环):

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

实际上是乘以缩放:(

【问题讨论】:

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


【解决方案1】:

OpenGL 是列优先的,缩放、旋转和平移的正确顺序实际上是Translation * Rotation * Scaling。在 D3D 和任何行主矩阵库中,Scale * Rotate * Translate 是正确的。使用列优先矩阵时,您必须从右到左考虑问题。

或者,您可以在乘法之前转置每个矩阵 - 不过,通常只遵循列主矩阵乘法的规范顺序会更简单。请注意,这也适用于 Position * Model * View * Projection(D3D / 行专业)之类的东西,在 GL(列专业)中,正确的顺序是 Projection * View * Model * Position

【讨论】:

    【解决方案2】:

    translateM 模拟 m X T 的矩阵乘法,其中T 是一个平移矩阵。

    翻译矩阵就像一个单位矩阵(对角线 1),最后一列 x,y,z,1 用于翻译。 (1 是齐次坐标,我不会进入)。

    假设您知道矩阵乘法(第一个操作数的每一行与第二个操作数的每一列成对相乘),它通过利用平移矩阵的细节T 采取捷径:

    它实际上并没有创建平移矩阵T,而是模拟乘法m x T。大多数值不会改变(因为像单位矩阵这样的部分),所以它只需要计算发生变化的值(涉及T的最后一列)。

    每次循环迭代都将m 的一行乘以转换矩阵的最后一列(x,y,z,1)。代码行将是(im 的行和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 行和列中的元素jm[i + 4*j]。也就是说,往下走是+1,往上走是+4。 thr next(另一种方式是row-major,您可以在其中遍历一行,向下到下一行等。打印更直观,但不是android.opengl.Matrix 使用的。)


    你的scaleM 然后translateM 等价于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)。缩放必须是第一位的似乎很自然。也许令人惊讶,因为矩阵乘法是关联,它与(T x S) x v 相同。如果我们忽略最后一个 x v(在 GPU 上完成),我们就有 T x S,意思是“缩放,然后然后翻译”。最右边的矩阵先做。

    顺便说一句,矩阵乘法的关联性就像 java 字符串连接,其中评估的顺序无关紧要,(a+b)+c = a+(b+c)(尽管操作数的顺序确实很重要,例如a+b+c != c+b+a - 可交换)。这意味着我们可以先选择适合我们的部分。


    BTW:这里有几层:矩阵乘法,排序效果,齐次坐标,平移矩阵,乘法排序效果,列主形式,上面的乘法快捷方式,+=mOffset, mi`(对于一个数组中的多个矩阵)。

    很多编程可以边走边理解,但为此,最好依次上每一层。

    【讨论】:

      猜你喜欢
      • 2011-07-06
      • 1970-01-01
      • 1970-01-01
      • 2020-10-17
      • 1970-01-01
      • 2019-04-05
      • 1970-01-01
      • 2011-06-21
      • 1970-01-01
      相关资源
      最近更新 更多