在 opengl 中,为什么我们必须在 gluLookAt 之前执行 gluPerspective?

in opengl why do we have to do gluPerspective before gluLookAt?

所以在 GL_PROJECTION 我做了

    glu.gluPerspective(90,aspect,1,10);
    glu.gluLookAt(0,0,3,0,0,0,0,1,0);

这很好用,但是当我切换顺序时,我的屏幕上没有任何对象,我旋转了我的相机,但什么也没有。

我知道切换两者会改变矩阵乘法的顺序,但我想知道为什么第一种情况有效而第二种情况无效。谢谢

要在屏幕上看到一个对象,您需要它落在规范视图体积内,对于 OpenGL,在所有三个维度上都是 [−1, 1]。要变换一个对象,您大致可以做

P' = Projection × View × Model × P

其中 P' 是规范视图体积中需要的最终点,P 是模型 space 中的初始点。 P由模型矩阵转换,然后是视图,然后是投影。

我遵循的顺序是基于列向量的,其中每个进一步的变换都是 pre/left-multiplied。读取相同公式的另一种方法是从左到右读取它,而不是转换点,转换坐标系并解释转换系统中的 P 在空间上表示原始系统中的 P'系统。这只是另一种看待它的方式,两者的结果是一样的;在数字和空间上。

why do we have to do gluPerspective before gluLookAt?

较旧的固定功能管道 OpenGL post/right-multiplies 因此需要颠倒顺序才能获得相同的效果。因此,当我们首先需要 LookAt,然后需要 Perspective 时,我们进行相反的操作以获得预期的结果。

以正确的顺序给出两者会导致

P' = View × Projection × Model × P

由于矩阵乘法是反交换的,因此您不会得到正确的 P',它落在规范的视图体积内,因此黑屏。

请参阅 Chapter 3, Red Book,在 通用转换命令 部分下,其中解释了 OpenGL 遵循的顺序。摘录:

Note: All matrix multiplication with OpenGL occurs as follows: Suppose the current matrix is C and the matrix specified with glMultMatrix*() or any of the transformation commands is M. After multiplication, the final matrix is always CM. Since matrix multiplication isn't generally commutative, the order makes a difference.


I want to know why the first case works but the second doesn't.

要了解由错误顺序形成的矩阵究竟会发生什么,让我们在 2D 中做一个小练习。假设规范视图 region 在 X 和 Y 中都是 [−100, 100];这之外的任何东西都被剪掉了。这个假想的方形屏幕的原点在中心,X 向右,Y 向上。当没有应用变换时,调用 DrawImage 会在原点绘制图像。你有一张 1 × 1 的图像;它的模型矩阵按 200 缩放,因此它变成了 200 × 200 图像;一个填满整个屏幕。由于原点位于屏幕中心,要绘制图像以使其填满屏幕,我们需要一个视图矩阵将图像平移(移动)(−100, −100)。制定这个

P' = View × Model = Translate−100, −100 × Scale200, 200

[ 200,  0,  −100 ]
[  0,  200, −100 ]
[  0,   0,   1   ]

然而,

的结果

Model × View = S200, 200 × T−100, −100

[ 200,  0,  −20000 ]
[  0,  200, −20000 ]
[  0,   0,    1    ]

将前一个矩阵与点 (0, 0) 和 (1, 1) 相乘将得到预期的 (−100, −100) 和 (100, 100)。图像角将与屏幕角对齐。但是,将后一个矩阵与它们相乘将得到 (−20000, −20000) 和 (−19800, −19800);远在可视区域之外。这是因为,在几何上,后一个矩阵首先平移然后缩放,而不是缩放然后平移。转换后的比例导致完全偏离的点。

glu.gluPerspective(90,aspect,1,10);
glu.gluLookAt(0,0,3,0,0,0,0,1,0);

情况下,首先 model/world 坐标(在 R^3 中)被转换为视图坐标(也是 R^3)。然后投影将视图坐标映射到透视 space (P^4),然后通过透视划分将其减少到 NDC 坐标。这通常是它应该如何工作的。

现在看看:

glu.gluLookAt(0,0,3,0,0,0,0,1,0);
glu.gluPerspective(90,aspect,1,10);

这里直接投影世界坐标space (P^4)。由于 lookAt 矩阵是从 R^3 -> R^3 的映射,而我们已经在 P^4 中,因此这是行不通的。即使可以旋转 P^4,也必须调整 gluLookAt 的参数以适应投影 space 的范围。

注意:一般来说,永远不要将 gluLookAt 添加到 GL_PROJECTION 堆栈中。因为它描述了视图矩阵,所以它更适合 GL_MODELVIEW 堆栈。供参考看看 here.