【问题标题】:OpenGL ES 2.0: Why does this perspective projection matrix not give the right result?OpenGL ES 2.0:为什么这个透视投影矩阵没有给出正确的结果?
【发布时间】:2011-10-16 15:41:22
【问题描述】:

大约 2 天前,我决定编写代码来显式计算模型-视图-投影(“MVP”)矩阵,以了解它是如何工作的。从那时起,我就遇到了麻烦,似乎是因为我正在使用的投影矩阵。

使用 iPhone 显示屏,我创建了一个由以下 4 个角顶点描述的屏幕居中正方形:

        const CGFloat cy = screenHeight/2.0f;
        const CGFloat z = -1.0f;
        const CGFloat dim = 50.0f;

        vxData[0] = cx-dim;
        vxData[1] = cy-dim;
        vxData[2] = z;
        vxData[3] = cx-dim;
        vxData[4] = cy+dim;
        vxData[5] = z;
        vxData[6] = cx+dim;
        vxData[7] = cy+dim;
        vxData[8] = z;
        vxData[9] = cx+dim;
        vxData[10] = cy-dim;
        vxData[11] = z;

因为我使用的是 OGLES 2.0,所以我将 MVP 作为统一传递给我的顶点着色器,然后简单地将变换应用到当前顶点位置:

uniform mat4 mvp;
attribute vec3 vpos;
void main()
{
  gl_Position = mvp * vec4(vpos, 1.0);
}

现在我已将我的 MVP 简化为 P 矩阵。下面显示的代码中列出了两个投影矩阵。第一个是标准透视投影矩阵,第二个是我在网上找到的显式值投影矩阵。

CGRect screenBounds = [[UIScreen mainScreen] bounds];
const CGFloat screenWidth = screenBounds.size.width;
const CGFloat screenHeight = screenBounds.size.height;

const GLfloat n = 0.01f;
const GLfloat f = 100.0f;
const GLfloat fov = 60.0f * 2.0f * M_PI / 360.0f;
const GLfloat a = screenWidth/screenHeight;
const GLfloat d = 1.0f / tanf(fov/2.0f);


// Standard perspective projection.
GLKMatrix4 projectionMx = GLKMatrix4Make(d/a, 0.0f, 0.0f, 0.0f,
                                         0.0f, d, 0.0f, 0.0f,
                                         0.0f, 0.0f, (n+f)/(n-f), -1.0f,
                                         0.0f, 0.0f, (2*n*f)/(n-f), 0.0f);
// The one I found online.
GLKMatrix4 projectionMx = GLKMatrix4Make(2.0f/screenWidth,0.0f,0.0f,0.0f,
                                         0.0f,2.0f/-screenHeight,0.0f,0.0f,
                                         0.0f,0.0f,1.0f,0.0f,
                                         -1.0f,1.0f,0.0f,1.0f);

当使用显式值矩阵时,正方形以正确的尺寸精确地呈现在屏幕中心。使用透视投影矩阵时,屏幕上不会显示任何内容。我已经打印了透视投影矩阵为屏幕中心(screenWidth/2, screenHeight/2, 0) 生成的位置值,它们非常大。显式值矩阵正确地产生零。

我认为显式值矩阵是正交投影矩阵 - 对吗?我的沮丧是我无法弄清楚为什么我的透视投影矩阵无法工作。

如果有人能帮助我解决这个问题,我将不胜感激。非常感谢。

Christian Rau 更新:

 #define Zn 0.0f
 #define Zf 100.0f
 #define PRIMITIVE_Z 1.0f

 //...

 CGRect screenBounds = [[UIScreen mainScreen] bounds];
 const CGFloat screenWidth = screenBounds.size.width;
 const CGFloat screenHeight = screenBounds.size.height;

 //...

 glUseProgram(program);

 //...

 glViewport(0.0f, 0.0f, screenBounds.size.width, screenBounds.size.height);

 //...

 const CGFloat cx = screenWidth/2.0f;
 const CGFloat cy = screenHeight/2.0f;
 const CGFloat z = PRIMITIVE_Z;
 const CGFloat dim = 50.0f;

 vxData[0] = cx-dim;
 vxData[1] = cy-dim;
 vxData[2] = z;
 vxData[3] = cx-dim;
 vxData[4] = cy+dim;
 vxData[5] = z;
 vxData[6] = cx+dim;
 vxData[7] = cy+dim;
 vxData[8] = z;
 vxData[9] = cx+dim;
 vxData[10] = cy-dim;
 vxData[11] = z;

 //...

 const GLfloat n = Zn;
 const GLfloat f = Zf;
 const GLfloat fov = 60.0f * 2.0f * M_PI / 360.0f;
 const GLfloat a = screenWidth/screenHeight;
 const GLfloat d = 1.0f / tanf(fov/2.0f);
 GLKMatrix4 projectionMx = GLKMatrix4Make(d/a, 0.0f, 0.0f, 0.0f,
                                          0.0f, d, 0.0f, 0.0f,
                                          0.0f, 0.0f, (n+f)/(n-f), -1.0f,
                                          0.0f, 0.0f, (2*n*f)/(n-f), 0.0f);

 //...

 // ** Here is the matrix you recommended, Christian:
 GLKMatrix4 ts = GLKMatrix4Make(2.0f/screenWidth, 0.0f, 0.0f, -1.0f,
                                0.0f, 2.0f/screenHeight, 0.0f, -1.0f,
                                0.0f, 0.0f, 1.0f, 0.0f,
                                0.0f, 0.0f, 0.0f, 1.0f);

 GLKMatrix4 mvp = GLKMatrix4Multiply(projectionMx, ts);

更新 2

新的 MVP 代码:

GLKMatrix4 ts = GLKMatrix4Make(2.0f/screenWidth, 0.0f, 0.0f, -1.0f,
                               0.0f, 2.0f/-screenHeight, 0.0f, 1.0f,
                               0.0f, 0.0f, 1.0f, 0.0f,
                               0.0f, 0.0f, 0.0f, 1.0f);

// Using Apple perspective, view matrix generators
// (I can solve bugs in my own implementation later..!)
GLKMatrix4 _p = GLKMatrix4MakePerspective(60.0f * 2.0f * M_PI / 360.0f,
                                          screenWidth / screenHeight,
                                          Zn, Zf);
GLKMatrix4 _mv = GLKMatrix4MakeLookAt(0.0f, 0.0f, 1.0f,
                                      0.0f, 0.0f, -1.0f,
                                      0.0f, 1.0f, 0.0f);
GLKMatrix4 _mvp = GLKMatrix4Multiply(_p, _mv);
GLKMatrix4 mvp = GLKMatrix4Multiply(_mvp, ts);

屏幕中心仍然看不到任何东西,并且屏幕中心的转换后的 x,y 坐标不为零。

更新 3

在上面的代码中使用ts 的转置代替!但是正方形不再是正方形了;它现在似乎具有纵横比screenHeight/screenWidth,即它具有平行于(短)屏幕宽度的较长尺寸,以及平行于(长)屏幕高度的较短尺寸。

我非常想知道(a)为什么需要转置以及它是否是有效的修复,(b)如何正确纠正非正方形维度,以及(c)这个附加矩阵如何@987654329我们使用的@适合Viewport * Projection * View * Model * Point的转换链。

对于 (c):我了解矩阵 的作用,即 Christian Rau 关于我们如何转换到范围 [-1, 1] 的解释。但是将这项额外的工作作为一个单独的转换矩阵包含在内是否正确,或者我们的 MVP 链的某些部分是否应该代替这项工作?

衷心感谢 Christian Rau 迄今为止所做的宝贵贡献。

更新 4

我关于“如何适应 ts”的问题很愚蠢,不是吗 - 关键是只需要矩阵,因为我选择使用屏幕坐标作为我的顶点;如果我从一开始就使用世界空间中的坐标,那么这项工作就不需要了!

感谢克里斯蒂安的所有帮助,这是非常宝贵的 :) 问题已解决。

【问题讨论】:

    标签: ios opengl-es-2.0 projection perspective orthographic


    【解决方案1】:

    这样做的原因是,您的第一个投影矩阵没有考虑转换的缩放和平移部分,而第二个矩阵则可以。

    因此,由于您的模型视图矩阵是恒等式,因此第一个投影矩阵假定模型的坐标位于 [-1,1] 中的某个位置,而第二个矩阵已经包含缩放和平移部分(查看其中的 screenWidth/Height 值),因此假定坐标位于[0,screenWidth] x [0,screenHeight]

    因此,您必须将投影矩阵右乘以首先将 [0,screenWidth] 缩放到 [0,2][0,screenHeight] 向下缩放到 [0,2] 然后将 [0,2] 转换为 [-1,1] 的矩阵(使用 @987654331 @ 代表screenWidthh 代表screenHeight):

    [ 2/w   0     0   -1 ]
    [ 0     2/h   0   -1 ]
    [ 0     0     1    0 ]
    [ 0     0     0    1 ]
    

    这将导致矩阵

    [ 2*d/h   0       0             -d/a        ]
    [ 0       2*d/h   0             -d          ]
    [ 0       0       (n+f)/(n-f)   2*n*f/(n-f) ]
    [ 0       0       -1            0           ]
    

    所以您看到您的第二个矩阵对应于 90 度的 fov、1:1 的纵横比和 [-1,1] 的远近范围。此外,它还会反转 y 轴,使原点位于左上角,这导致第二行被否定:

    [ 0   -2*d/h   0   d ]
    

    但作为结束评论,我建议您不要配置投影矩阵来解决所有这些问题。相反,您的投影矩阵应该看起来像第一个,并且您应该让模型视图矩阵管理您的世界的任何平移或缩放。转换管道被分离为模型视图和投影矩阵并非偶然,并且在使用着色器时也应该保持这种分离。当然,您仍然可以在 CPU 上将两个矩阵相乘,然后将单个 MVP 矩阵上传到着色器。

    在处理 3 维世界时,通常不会真正使用基于屏幕的坐标系。如果您正在绘制 2d 图形(如 GUI 元素或 HUD),您只想这样做,在这种情况下,您将使用更简单的正交投影矩阵,无论如何,这只不过是上面提到的没有所有的缩放转换矩阵透视复杂度。

    编辑:第三次更新:

    (a) 转置是必需的,因为我猜您的 GLKMatrix4Make 函数以列主要格式接受其参数,并且您将矩阵按行放置。

    (b) 我犯了一个小错误。您应该将ts 矩阵中的screenWidth 更改为screenHeight(或者可能反过来,不确定)。我们实际上需要一个统一的比例,因为投影矩阵已经处理了纵横比。

    (c) 将这个矩阵分类到通常的 MVP 管道中并不容易。这是因为它并不常见。我们来看看两种常见的渲染情况:

    1. 3D:当你有一个 3D 世界时,以基于屏幕的单位定义它的坐标并不常见,因为没有从 3d 场景到 2d 屏幕的映射并使用坐标单位等于像素的系统没有意义。在此设置中,您很可能会将其归类为模型视图矩阵的一部分,以将世界转换为另一个单位系统。但在这种情况下,您需要真正的 3d 转换,而不仅仅是这种半生不熟的 2d 解决方案。

    2. 2D:在渲染 2D 场景(如 GUI 或 HUD 或只是一些文本)时,有时您确实需要基于屏幕的坐标系。但在这种情况下,您很可能会使用正交投影(没有任何透视)。这样的正交矩阵实际上只不过是这个ts 矩阵(根据远近范围对z 进行了一些额外的比例转换)。所以在这种情况下,矩阵属于或实际上是投影矩阵。看看旧的glOrtho 函数是如何构造它的矩阵的,你会发现它只不过是ts

    【讨论】:

    • 非常感谢克里斯蒂安如此详细的回复。如果您不介意,我还有两个后续问题:
    • FIRST:因此,如果我 (1) 将透视投影矩阵保留为我在问题中首先列出的矩阵,(2) 包括视图变换,(3) 添加用户控件以缩放和平移视图,以及 (4) 指定 glViewport(0,0,screenWidth,screenHeight)glDepthRangef(zNear,zFar) 那么一切都会正确设置为 MVP 视图吗? (附带说明:我知道一旦我这样做了,我的“居中”方块将不会出现在屏幕中心。)
    • 第二个:其实没有第二个问题,如果你不介意的话,只是上面评论中的第一个问题。
    • @KomodoDave 不要使用glDepthRange。远近范围在您的投影矩阵中指定(如在您的代码示例中),glDepthRange 有不同的用途,您不需要它。您不一定需要用户控件来更改视图。使用上面提出的矩阵,您的居中正方形实际上应该居中(这就是翻译部分要做的)。
    • 好的,Christian,消息已理解。我会在晚饭后试一试,并在大约 30 分钟后发表另一条评论,让你知道它是怎么回事。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-04-17
    • 1970-01-01
    • 2015-04-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多