在OpenGL中,为什么我们必须先执行gluPerspective再执行gluLookAt?

3

在GL_PROJECTION下,我执行了以下操作

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

这段代码本来是正常工作的,但当我改变了顺序后,屏幕上就没有任何物体显示出来,我旋转了相机,也没有看到任何东西。

我知道交换两个矩阵的顺序会改变矩阵相乘的顺序,但我想知道为什么第一个情况可以工作而第二个情况不行。谢谢。


2
附注:GL_PROJECTION应该只包含投影变换,模型和视图变换应该放在GL_MODELVIEW中。虽然这样做也可以工作,但遵循惯例是正确的事情。 - legends2k
3
@legends2k所说的没错。同时把视图变换放到GL_PROJECTION里会在光照进入场景后出现问题。 - datenwolf
"帮助消除GL_PROJECTION的滥用。" - genpfault
2个回答

4
要在屏幕上看到一个对象,需要它落在规范视体积内,对于OpenGL来说,在三个维度上的取值范围是[-1, 1]。要对一个对象进行变换,通常采用以下公式:
P' = 投影矩阵 × 视图矩阵 × 模型矩阵 × P
其中,P' 是最终点,需要在规范视体积内,而 P 是模型空间中的初始点。P 先经过模型矩阵变换,然后是视图矩阵和投影矩阵。
我所遵循的顺序基于列向量,每一次进一步的变换都是前置/左乘。另一种读取相同公式的方法是从左到右阅读,不是变换点,而是变换坐标系,并将 P 在变换后的坐标系中解释为原始坐标系中的 P'。这只是另一种看待问题的方式,结果在两种情况下都是相同的,无论是在数值上还是在空间上。
关于“为什么我们必须在 gluLookAt 之前执行 gluPerspective?”的问题,可能是因为 gluPerspective 将透视投影矩阵应用于场景,而 gluLookAt 定义了摄像机的位置和方向。因此, gluPerspective 必须先应用于场景,以便在透视投影矩阵下正确呈现对象,然后 gluLookAt 才能确定摄像机的位置和方向。
较旧的固定功能管道 OpenGL 后/右乘,因此需要反转顺序才能获得相同的效果。 因此,当我们需要先使用 LookAt 再使用 Perspective 时,我们进行反向操作以获得预期的结果。
按正确顺序给出这两个矩阵会导致

P' = 视图矩阵 × 投影矩阵 × 模型矩阵 × P

由于矩阵乘法是反交换的,您将无法得到正确的 P',导致其落在规范视图体积内,从而出现黑屏。
请参见《红皮书》第3章下的通用变换命令部分,其中解释了OpenGL遵循的顺序。摘录如下:

注意:OpenGL中的所有矩阵乘法都按以下方式进行:假设当前矩阵为C,使用glMultMatrix*()或任何变换命令指定的矩阵为M。乘法后,最终矩阵始终为CM。由于矩阵乘法通常不是可交换的,所以顺序很重要。


我想知道为什么第一个案例可以运行而第二个不能。
为了了解由于矩阵形成的错误顺序而实际发生的情况,让我们在2D中进行一次小型练习。假设规范视图“区域”在X和Y的[-100,100]之间;超出此范围的任何内容都将被剪切掉。这个虚拟正方形屏幕的原点位于中心,X向右,Y向上。当未应用变换时,调用DrawImage会在原点处绘制图像。您有一个1×1的图像;它的模型矩阵按比例缩放200,以便成为一个200×200的图像;一个填满整个屏幕的图像。由于原点位于屏幕中心,为了绘制图像使其填满屏幕,我们需要一个视图矩阵,将图像平移(移动)-100,-100。制定如下公式:
P' = 视图矩阵 × 模型矩阵 = 平移-100,-100 × 缩放200,200
[ 200,  0,  −100 ]
[  0,  200, −100 ]
[  0,   0,   1   ]

然而,结果为:

模型 × 视图 = 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);远远超出可视区域。这是因为,几何上讲,后一个矩阵先平移再缩放,而不是先缩放再平移。平移的比例导致了一个完全偏离的点。

谢谢您的回答,我明白改变顺序会改变矩阵乘法的顺序。能否再详细解释一下为什么它不符合规范视图呢?(我知道PVM是正确的顺序,改变顺序会得到一个错误的矩阵,但这个错误的矩阵意味着什么呢?)谢谢。 - demalegabi
请检查更新后的答案,其中包含一个示例以使其更清晰。 - legends2k

3

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

在编程中,首先将案例的第一个模型/世界坐标(在R^3中)转换为视图坐标(也是R^3)。然后,投影将视图坐标映射到透视空间(P^4),然后通过透视除法将其缩小到NDC坐标。这通常是它应该工作的方式。

现在看一下:

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

在这里,世界坐标直接投影到射影空间(P^4)中。由于lookAt矩阵是从R^3映射到R^3的,并且我们已经处于P^4中,所以这不起作用。即使可能旋转P^4,gluLookAt的参数也必须适应项目空间范围。请注意:通常不应将gluLookAt添加到GL_PROJECTION堆栈中。由于它描述了视图矩阵,因此更适合于GL_MODELVIEW堆栈。有关参考,请查看此处

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接