这篇文章是对《D19.1.0 对gluLookAt,gluPerspective和glOrtho的理解》的部分更正和完善。
01 gluLookAt
假定现在手上有一台单反,你可以拿着单反拍正前方,可以仰起头拍天空,还可以拿相机拍地面,相机如何拍摄对应的是gluLookAt函数。
gluLookAt功能演示:
从正前方看 斜向下45°看 从正上方看
理解gluLookAt之前,先概述OpenGL中坐标的变化,其坐标系变化次序如下:
局部坐标--世界坐标--观察空间坐标--裁剪坐标--屏幕坐标
局部坐标(空间):是指物体所在的坐标空间。假设我桌面上有手机、铅笔盒水杯,这三个物体相对自身都可以有个坐标系,并且都可以有自己的坐标原点。
世界坐标(空间):可以看成是无数个局部坐标的集合。此时世界坐标正如其名字一样就是整个世界,这个世界里有很多物体存放其中,在这个世界坐标中手机、铅笔和水杯都有一个位置。
观察坐标(空间):通常被称为摄像机空间,其实就是怎么看的问题。我的桌面上有手机、铅笔和水杯,我可以在桌子的正前方看过去,我可以在桌子正上方看过去,我甚至还可以在桌子底下看……
裁剪坐标(空间):我的桌面上有手机、铅笔和水杯,但是我的相片中只想出现手机,那就把铅笔和水杯裁剪掉吧。OpengGL期望所有的坐标都能落在一个特定的范围内,且任何在这个范围之外的点都应该被裁剪掉,被裁剪掉的坐标就会被忽略,剩下的坐标就将变为屏幕上可见的片段。
屏幕坐标(空间):使用一个叫做视口变换(Viewport Transform)的过程,将裁剪坐标变换为屏幕坐标。视口变换将位于-1.0到1.0范围的坐标变换到由glViewport函数所定义的坐标范围内。
关于裁剪空间更具体详细的解释,参照https://learnopengl-cn.github.io/01%20Getting%20started/08%20Coordinate%20Systems/
在一个顶点着色器运行的最后,OpenGL期望所有的坐标都能落在一个特定的范围内,且任何在这个范围之外的点都应该被裁剪掉(Clipped)。被裁剪掉的坐标就会被忽略,所以剩下的坐标就将变为屏幕上可见的片段。这也就是裁剪空间(Clip Space)名字的由来。
因为将所有可见的坐标都指定在-1.0到1.0的范围内不是很直观,所以我们会指定自己的坐标集(Coordinate Set)并将它变换回标准化设备坐标系,就像OpenGL期望的那样。
为了将顶点坐标从观察变换到裁剪空间,我们需要定义一个投影矩阵(Projection Matrix),它指定了一个范围的坐标,比如在每个维度上的-1000到1000。投影矩阵接着会将在这个指定的范围内的坐标变换为标准化设备坐标的范围(-1.0, 1.0)。所有在范围外的坐标不会被映射到在-1.0到1.0的范围之间,所以会被裁剪掉。在上面这个投影矩阵所指定的范围内,坐标(1250, 500, 750)将是不可见的,这是由于它的x坐标超出了范围,它被转化为一个大于1.0的标准化设备坐标,所以被裁剪掉了。
由投影矩阵创建的观察箱(Viewing Box)被称为平截头体(Frustum),每个出现在平截头体范围内的坐标都会最终出现在用户的屏幕上。将特定范围内的坐标转化到标准化设备坐标系的过程(而且它很容易被映射到2D观察空间坐标)被称之为投影(Projection),因为使用投影矩阵能将3D坐标投影(Project)到很容易映射到2D的标准化设备坐标系中。
一旦所有顶点被变换到裁剪空间,最终的操作——透视除法(Perspective Division)将会执行,在这个过程中我们将位置向量的x,y,z分量分别除以向量的齐次w分量;透视除法是将4D裁剪空间坐标变换为3D标准化设备坐标的过程。这一步会在每一个顶点着色器运行的最后被自动执行。
使用gluLookAt函数就是为了将世界坐标中的物体变换到观察坐标中。
图中x、y、z轴就在世界坐标中,上图4摄像机上也出现了三根轴,这三根轴就代表了观察坐标。而我们只要定义一个摄像机位置,一个目标位置和一个表示世界空间中的上向量的向量,接着OpenGL就会创建一个LookAt矩阵,我们可以把它当作我们的观察矩阵,就能够把世界坐标中的物体变换到摄像机空间(观察空间)。
对照上图,假设现在摄像机的位置为(0,0,3),目标位置为世界坐标系的原点(0,0,0),那此时世界空间中的上向量就是y轴,可以写成(0,1,0)。
如果摄像机的位置为(0,-3,0),目标位置为世界坐标系的原点(0,0,0),那此时世界空间中的上向量可以是x轴,也可以是y轴。
总之,gluLookAt表现的是你怎么看。
02 gluPerspective和glOrtho
显示中的三维物体变换到屏幕里面的二维图像用到了投影。我们看到的屏幕就是二维的,不管上面的图片3D效果多么的好,那还是二维图像。投影有两种:正交投影和透视投影。
gluPerspective:先说什么是透视投影?我们人眼看到的物体在近处要比在远处看到的物体要大些,并且物体放得越远越小。就像晚上看月亮一样,看过去只有一丁点大,但是实际的大小我们都清楚。这就是透视投影的效果。
glOrtho:知道了什么叫透视投影,就比较好理解什么叫正交投影?通俗地讲,正交投影变换忽略物体远近时的大小缩放变化,将物体以原比例投影到截面(如显示屏幕)上。再通俗点,离我30cm的酒杯体积是20个单位,那把它放在离我100cm的地方,它的体积看过去并没有变小还是20个单位。注意这里是视觉效果,毕竟实际的大小不管放多远多是20个单位。
相机拍出的照片是什么样的效果由gluPerspective和glOrtho反映。通常情况下,相机拍出来的照片都是近处的物体大,远处的物体小(近大远小),要实现这种效果就调用gluPerspective。如果远处物体与近处物体原本大小一样,此时要实现在屏幕上显示的大小都一样,就调用函数glOrtho。
gluPerspective和glOrtho效果对比图:
注:上面两图片都是摘自一个很好的OpenGL学习网站。