2

大约 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)了透视投影矩阵为屏幕中心生成的位置值,它们非常大。显式值矩阵正确地产生零。

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

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

更新克里斯蒂安·劳:

 #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)transpose(ts)我们使用的这个附加矩阵如何适合Viewport * Projection * View * Model * Point的变换链。

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

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

更新 4

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

感谢 Christian 的所有帮助,这是非常宝贵的 :) 问题已解决。

4

1 回答 1

2

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

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

因此,您必须将投影矩阵乘以一个矩阵,该矩阵首先[0,screenWidth]缩小到缩小到[0,2]然后转换为(使用for和for ):[0,screenHeight][0,2][0,2][-1,1]wscreenWidthhscreenHeight

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

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

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

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

于 2011-10-16T19:02:38.807 回答