大约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)这个额外的矩阵{{我们使用的1}}适合 Viewport * Projection * View * Model * Point 的转换链。
对于(c):我理解矩阵做什么,即Christian Rau关于我们如何转换到范围[-1,1]的解释。但将这些额外的工作作为一个单独的转换矩阵包含在内是否正确,或者我们的MVP链的某些部分应该做这项工作呢?
真诚地感谢Christian Rau迄今为止所做的宝贵贡献。
更新4
关于“ts如何适应”的问题是愚蠢的不是它 - 整个点是只需要矩阵因为我选择使用屏幕坐标作为我的顶点;如果我从一开始就在世界空间中使用坐标,那么就不需要这项工作了!
感谢Christian的所有帮助,这非常宝贵:)问题解决了。
答案 0 :(得分:2)
原因是,你的第一个投影矩阵没有考虑变换的缩放和平移部分,而第二个矩阵就是这样。
因此,由于您的模型视图矩阵是标识,因此第一个投影矩阵假设模型的坐标位于[-1,1]
中的某个位置,而第二个矩阵已经包含缩放和平移部分(请查看{{1}那里的值)因此假定screenWidth/Height
中的坐标。
因此,您必须将投影矩阵右对乘一个矩阵,该矩阵首先将[0,screenWidth] x [0,screenHeight]
向下缩放到[0,screenWidth]
,将[0,2]
向下缩放到[0,screenHeight]
,然后翻译{{1 } [0,2]
{对[0,2]
使用[-1,1]
,w
使用screenWidth
:
h
将导致矩阵
screenHeight
所以你看到你的第二个矩阵对应于90度的fov,1:1的纵横比和[-1,1]的近远距离。此外,它还反转y轴,使原点位于左上角,这导致第二行被否定:
[ 2/w 0 0 -1 ]
[ 0 2/h 0 -1 ]
[ 0 0 1 0 ]
[ 0 0 0 1 ]
但作为结束评论,我建议您不要将投影矩阵配置为考虑所有这些。相反,您的投影矩阵应该看起来像第一个,您应该让模型视图矩阵管理您的世界的任何翻译或缩放。转换管道分为模型视图和投影矩阵并不是偶然的,在使用着色器时也应该保持这种分离。您当然可以在CPU上将两个矩阵相乘,并将单个MVP矩阵上传到着色器。
通常,在使用三维世界时,您并不真正使用基于屏幕的坐标系。如果要绘制2D图形(如GUI元素或HUD),您只需要这样做,在这种情况下,您将使用更简单的正交投影矩阵,无论如何,这只不过是上面提到的缩放平移矩阵而不是全部透视复杂性。
编辑:第3次更新:
(a)转置是必需的,因为我猜你的[ 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 ]
函数接受列主格式的参数,你把矩阵放在行中。
(b)我犯了一个小错误。您应该将[ 0 -2*d/h 0 d ]
矩阵中的GLKMatrix4Make
更改为screenWidth
(或者反过来,不确定)。我们实际上需要一个统一的比例,因为纵横比已经由投影矩阵处理。
(c)将此矩阵分类为通常的MVP管道并不容易。这是因为它并不常见。让我们看看两种常见的渲染情况:
3D:当你有一个三维世界时,在基于屏幕的单位中定义它的坐标并不常见,因为没有从三维场景到二维屏幕和使用坐标的映射单位相等像素的系统没有意义。在此设置中,您很可能会将其归类为模型视图矩阵的一部分,以便将世界转换为另一个单位系统。但在这种情况下,您需要真正的3D转换,而不仅仅是这种半成品的2D解决方案。
2D:渲染二维场景(如GUI或HUD或仅一些文本)时,您有时真的需要一个基于屏幕的坐标系。但在这种情况下,你很可能会使用正交投影(没有任何透视)。这样的正交矩阵实际上只不过是这个ts
矩阵(对于z,基于近远距离有一些额外的比例平移)。因此,在这种情况下,矩阵属于或实际上是投影矩阵。只要看一下好的旧glOrtho函数如何构造其矩阵,你就会看到它只是screenHeight
。