31

在帮助另一位用户解决有关响应触摸事件Android 教程的问题后,我下载了源代码,并且对我所看到的内容感到非常困惑。该教程似乎无法决定是要使用行向量还是列向量,而且在我看来一切都混在一起了。

在 Android Matrix 页面上,他们声称他们的约定是列向量/列优先,这是 OpenGL 的典型。

我是对的,还是我遗漏了什么?以下是它的相关部分:

首先通过乘以 mProjMatrix * mVMatrix 创建一个 MVPMatrix。到目前为止,一切都很好。

    // Set the camera position (View matrix)
    Matrix.setLookAtM(mVMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);

    // Calculate the projection and view transformation
    Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mVMatrix, 0)

接下来他们要在 MVPMatrix 的左侧附加一个旋转?这似乎有点奇怪。

    // Create a rotation for the triangle
    Matrix.setRotateM(mRotationMatrix, 0, mAngle, 0, 0, -1.0f);

    // Combine the rotation matrix with the projection and camera view
    Matrix.multiplyMM(mMVPMatrix, 0, mRotationMatrix, 0, mMVPMatrix, 0)

以非转置顺序上传。

    GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);

最后在他们的着色器中,向量*矩阵乘法?

    // the matrix must be included as a modifier of gl_Position
    "  gl_Position = vPosition * uMVPMatrix;" 

将这些加在一起,我们得到:

gl_Position = vPosition * mRotation * mProjection * mView;

根据我的任何想象,这是不正确的。关于这里发生的事情,有什么我没有看到的解释吗?

4

5 回答 5

26

作为编写 OpenGL 教程的人,我可以确认示例代码不正确。具体来说,着色器代码中因子的顺序应该颠倒:

"  gl_Position = uMVPMatrix * vPosition;"

对于旋转矩阵的应用,也应将因子的顺序颠倒,使旋转为最后一个因子。经验法则是矩阵按从右到左的顺序应用,首先应用旋转(它是“MVP”的“M”部分),所以它需要是最右边的操作数。此外,您应该按照 Ian Ni-Lewis 的建议使用临时矩阵进行此计算(请参阅下面的更完整答案):

float[] scratch = new float[16];
// Combine the rotation matrix with the projection and camera view
Matrix.multiplyMM(scratch, 0, mMVPMatrix, 0, mRotationMatrix, 0);

感谢您关注此问题。我会尽快修复培训课程和示例代码。

编辑:这个问题现在已在可下载的示例代码和 OpenGL ES 培训课程中得到纠正,包括对因素正确顺序的评论。感谢您的反馈,伙计们!

于 2012-09-06T23:15:48.937 回答
10

该教程是不正确的,但许多错误要么相互抵消,要么在这个非常有限的上下文中不明显(固定相机以 (0,0) 为中心,仅围绕 Z 旋转)。旋转是向后的,但除此之外它看起来是正确的。(要了解错误的原因,请尝试使用不那么简单的相机:例如,将 eye 和 lookAt 设置为 y=1。)

使调试变得非常困难的一件事是 Matrix 方法不会对其输入进行任何别名检测。教程代码使您看起来可以调用 Matrix.multiplyMM 并使用相同的矩阵作为输入和结果。这不是真的。但是因为实现一次乘以一列,所以如果重用右侧(如在当前代码中,其中 mMVPMatrix 是 rhs 和结果),则与重用左侧相比,出现问题的可能性要小得多. 在写入结果中的相应列之前读取左侧的每一列,因此即使 LHS 被覆盖,输出也将是正确的。但如果右侧与结果相同,则其第一列将在读取完成之前被覆盖。

所以教程代码处于某种局部最大值:它似乎可以工作,如果你改变任何一个东西,它就会非常糟糕。这导致人们相信它看起来是错误的,它可能只是正确的。;-)

无论如何,这里有一些替换代码可以得到我认为的预期结果。

Java代码:

@Override
public void onDrawFrame(GL10 unused) {
    float[] scratch = new float[16];

    // Draw background color
    GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

    // Set the camera position (View matrix)
    Matrix.setLookAtM(mVMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);

    // Calculate the projection and view transformation
    Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mVMatrix, 0);

    // Draw square
    mSquare.draw(mMVPMatrix);

    // Create a rotation for the triangle
    Matrix.setRotateM(mRotationMatrix, 0, mAngle, 0, 0, 1.0f);

    // Combine the rotation matrix with the projection and camera view
    Matrix.multiplyMM(scratch, 0, mMVPMatrix, 0, mRotationMatrix, 0);

    // Draw triangle
    mTriangle.draw(scratch);
}

着色器代码:

gl_Position =  uMVPMatrix * vPosition;

注意:这些修复使投影正确,但它们也反转了旋转方向。那是因为原始代码以错误的顺序应用了转换。可以这样想:不是顺时针旋转物体,而是逆时针旋转相机。当您固定操作顺序以便将旋转应用于对象而不是相机时,对象开始逆时针移动。不是矩阵错了;这是用于创建矩阵的角度。

因此,要获得“正确”的结果,您还需要翻转 mAngle 的符号。

于 2012-09-07T02:36:15.553 回答
2

我解决了这个问题如下:

@Override
public void onDrawFrame(GL10 unused) {      
    GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

    Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -1f, 0f, 0f, 0f, 0f, 1.0f, 0.0f);        

    Matrix.setRotateM(mModelMatrix, 0, mAngle, 0, 0, 1.0f);
    Matrix.translateM(mModelMatrix, 0, 0.4f, 0.0f, 0);

    mSquare.draw(mProjMatrix,mViewMatrix,mModelMatrix);
}

@Override
public void onSurfaceChanged(GL10 unused, int width, int height) {
    ...  
    Matrix.frustumM(mProjMatrix, 0, -ratio, ratio, -1, 1, 1, 99);

}

class Square {

    private final String vertexShaderCode =
        "uniform mat4 uPMatrix; \n" +
        "uniform mat4 uVMatrix; \n" +
        "uniform mat4 uMMatrix; \n" +

        "attribute vec4 vPosition; \n" +
        "void main() { \n" +
        "  gl_Position = uPMatrix * uVMatrix * uMMatrix * vPosition; \n" +
        "} \n";

    ...

    public void draw(float[] mpMatrix,float[] mvMatrix,float[]mmMatrix) {

        ...

        mPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uPMatrix");
        mVMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uVMatrix");
        mMMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMMatrix");

        GLES20.glUniformMatrix4fv(mPMatrixHandle, 1, false, mpMatrix, 0);
        GLES20.glUniformMatrix4fv(mVMatrixHandle, 1, false, mvMatrix, 0);
        GLES20.glUniformMatrix4fv(mMMatrixHandle, 1, false, mmMatrix, 0);

        ...
    }
}
于 2013-05-04T08:36:35.193 回答
1

我正在解决同样的问题,这就是我发现的:

我相信乔的样本是正确的,
包括着色器代码中因素的顺序:

gl_Position = vPosition * uMVPMatrix;

为了验证它,只需尝试以相反的因子顺序旋转三角形,它会将三角形拉伸到 90 度的消失点。

真正的问题似乎出在setLookAtM函数中。
在 Joe 的示例中,参数为:

Matrix.setLookAtM(mVMatrix, 0,
     0f, 0f,-3f,   0f, 0f, 0f,   0f, 1f, 0f );

这也是完全合乎逻辑的。
但是,生成的视图矩阵对我来说看起来很奇怪:

-1  0  0  0
 0  1  0  0
 0  0 -1  0
 0  0 -3  1

正如我们所见,这个矩阵将反转 X 坐标,因为第一个成员是 –1,
这将导致屏幕上的左/右翻转。
它也会反转 Z 顺序,但让我们在这里关注 X 坐标。

我认为setLookAtM函数也可以正常工作。
但是,由于 Matrix 类不是 OpenGL 的一部分,它可以使用其他一些坐标系统,
例如 - Y 轴指向下方的常规屏幕坐标。
这只是一个猜测,我并没有真正验证这一点。

可能的解决方案:
我们可以手动构建想要的视图矩阵,
代码是:

Matrix.setIdentityM(mVMatrix,0);
mVMatrix[14] = -3f;

或者
我们可以尝试通过给setLookAtM函数提供反转的相机坐标来欺骗它:0、0、+3(而不是 –3)。

Matrix.setLookAtM(mVMatrix, 0,
     0f, 0f, 3f,   0f, 0f, 0f,   0f, 1f, 0f );

生成的视图矩阵将是:

1  0  0  0
0  1  0  0
0  0  1  0
0  0 -3  1

这正是我们所需要的。
现在相机的行为与预期一样,
并且示例可以正常工作。

于 2012-10-04T06:19:26.927 回答
0

使用当前更新的 Android 示例代码时,没有其他建议对我有用,除了以下尝试移动三角形时的建议。

以下链接包含答案。花了一天多的时间才找到它。在这里发帖是为了帮助别人,因为我多次看到这篇文章。OpenGL ES Android 矩阵变换

于 2013-11-20T22:32:55.657 回答