OpenGL(六) gluLookAt和gluPerspective函数解析

时间:2022-09-10 21:23:17


在调用gluLookAt和gluPerspective函数之前一般要先调用一下glLoadIdentity函数,先说一下这个函数是做什么的。


glLoadIdentity


glLoadIdentity是一个无参的无值函数,其功能是用一个4*4的单位矩阵来替换当前矩阵,实际上就是对当前矩阵进行初始化为单位矩阵。也就是说,无论以前进行了多少次矩阵变换,在该命令执行后,当前矩阵均恢复成一个单位矩阵,即相当于没有进行任何矩阵变换状态。之后对矩阵的变换都相当于是针对模型是位于世界坐标原点位置处进行的。


gluLockAt


在openGL中,视图变换是指保持模型坐标不变的情况下,从不同的方位观察模型,常用的设置观察者属性的函数为glLookAt 。


glLookAt 会定义一个视图矩阵,并与当前模型的模型矩阵相乘,获得模型的模型视图矩阵,再该模型视图矩阵左乘模型,就会获得在观察位置上模型的状态,就是我们在屏幕上最终看到的模型的状态。


gluLookAt用来定义观察者(相机)的状态,包括观察者在世界坐标系中所处的位置、看向世界坐标系中的方向(可以理解为眼睛所看向的方向)、观察者头部的朝向(可以在一个平面上360°旋转)。


void gluLookAt (
GLdouble eyex, GLdouble eyey, GLdouble eyez,
GLdouble centerx, GLdouble centery, GLdouble centerz,
GLdouble upx, GLdouble upy, GLdouble upz);

  • 第一二三个参数定义相机在世界坐标系中的位置坐标
  • 第四五六个参数定义相机正对着的世界坐标系中的点的位置坐标,成像后这一点会位于画板的中心位置
  • 第七八九参数,定义相机本身的朝向。这三个坐标是在世界坐标系中的坐标点,可以理解为人站立在相机处头的朝向。这三个坐标是世界坐标系中的坐标点,不是相机坐标系的,只是用来定义方向,注意这个不是视线(镜头)的朝向,而是摆放时相机本身的朝向,跟视线朝向方向是垂直的。


如果没有调用glLookAt设置视图矩阵,默认情况下,相机会被设置为位置在世界坐标系原点,指向z轴负方向,朝上向量为(0,1,0)

以观察者的角度去看这几个参数就很容易理解了。第一组参数是定义人站在距离物体有多远处,第二组参数是定义人眼看向世界坐标系中的哪个方向,有时候屏幕上黑黑的什么也看不到,可能就是这组参数设置的方向不对,有可能物体就在你身后不远处,,第三组参数是人的朝向,也表示一个方向,这个朝向跟视线是垂直的。


始终需要明确的一点是openGL中世界坐标系是右手坐标系,在二维屏幕上,屏幕水平方向是x 轴方向,向右为正,屏幕竖起方向是Y轴方向,向上为正,垂直于屏幕的方向是Z轴方向,从屏幕里往外为正。


一图胜万言。以下从实际例子上分析一下这三组参数。


gluLookAt中前三个参数定义观察者的坐标,我们假定从Y轴正上方看一个位于世界坐标系原点的模型,坐标点为(0,5,0),此时看到的场景:

OpenGL(六) gluLookAt和gluPerspective函数解析


如果观察者沿着Y轴正方向移动一段距离,比如从5到8,则距离模型的距离远了,看起来的模型也应该小一些,事实确实如此:

OpenGL(六) gluLookAt和gluPerspective函数解析



gluLookAt中中间三个参数定义观察者视线正对着的世界坐标系中的位置。以上默认是(0,0,0),即看向原点,如果设置为(2,0,0),则点(2,0,0)应该成为视野的中心,相应的物体会沿着X轴的负方向移动2:

OpenGL(六) gluLookAt和gluPerspective函数解析


注意,这个茶壶模型的初始位置是在原点,壶口所对的方向是X轴正方向,壶把所对的方向是X轴负方向,由于在第一步中把视点从Z轴上移动到了Y轴上,所以上图中X轴变为了竖直方向,向上为正。


gluLookAt中最后三个参数定义观察者或者相机本身的朝向,这个朝向是跟视线方向相互垂直的,就像相机拍摄的时候可以旋转一定角度,上下颠倒等,但是视线(即镜头对准的方向)并没有变,对观察者来说,观察者的头部可以旋转一定角度,这三个参数就是定义相机或者观察者头部的转动方向的。


观察者头部朝向的方向为(1,0,0)时,模型为:

OpenGL(六) gluLookAt和gluPerspective函数解析


头部顺时针旋转45°,方向向量为(1,0,1),此时相当于模型逆时针旋转45°:

OpenGL(六) gluLookAt和gluPerspective函数解析


观察者头部颠倒一下方向,方向向量为(-1,0,0),此时模型方向也颠倒过来:

OpenGL(六) gluLookAt和gluPerspective函数解析



gluPerspective


如果说gluLookAt定义的是相机外在参数如相机所处位置、旋转角度等,那么gluPerspective定义的就是相机的内在镜头参数了。


gluPerspective (
GLdouble fovy,
GLdouble aspect,
GLdouble zNear,
GLdouble zFar);

  • 第一个参数flvy,定义可视角的大小,flvy值小,表示从相机(人眼)出发的光线的角度小,此时同等距离下,可观察到的视野范围较小,反之则大。从物体显示在画板的大小来反映,如果flvy值较大,则物体在画板中所占比例就较少,看起来比较小,反之则显示比较大。
  • 第二个参数aspect,定义物体显示在画板上的x和y方向上的比例。aspect小于1,则物体显示出来比实际更高,大于1,显示出来比实际看起来更宽,设为1,会按实际反应长宽比。
  • 第三个参数zNear,定义距离相机(人眼)最近处物体截面相距的距离。这个值越大,表示观测点距离物体距离越远,看起来物体就比较小,反之则比较大。如果物体运动到距离观测点的距离小于了设定的zNear,则物体不会被绘制在画板上。
  • 第四个参数zFar,定义可观测到的物体的最远处截面相距相机的距离。如果物体运动到距离观测点的距离大于了设定的zFar,则物体不会被绘制的画板上。

各个参数的意义可以用下图表示:
OpenGL(六) gluLookAt和gluPerspective函数解析


第一参数表示视场角的大小,符合人眼的条件一般设置为40°~70°之间,第二个参数定义模型显示的长宽比,这两个参数比较容易理解。第三和第四个参数定义了观察者在深度上的可视范围,可以理解为镜头的景深,所不同的是镜头 在景深外成像时模糊的,而在这里“景深”之外的影响是直接被屏蔽掉的。

还要注意一点的是zNear和zFar都是从观察者或者相机的视点出发计算的,而不是从世界坐标系的原点出发的。

仍以上图为例,视点离原点的距离为5,我们设置zNear为4,zFar为10,可以看到完整的模型:

OpenGL(六) gluLookAt和gluPerspective函数解析


当进一步推进zNear为4.5时,已经有部分被截取掉,此时已经相当于视线深入到茶壶内部了:

OpenGL(六) gluLookAt和gluPerspective函数解析


完整代码如下,通过调整gluLookAt和gluPerspective的各个参数可以查看模型不同的显示效果:

#include <GL/glut.h>  
#include <stdlib.h>

void display(void)
{
glClearColor(1,1,1,0);
glClear (GL_COLOR_BUFFER_BIT);
glColor3f (0, 0, 1.0); //画笔蓝色
glLoadIdentity(); //加载单位矩阵
gluLookAt(0.0,5.0,0.0, 0,0,0, 1.0,0.0,0);
glutWireTeapot(1.3);
glutSwapBuffers();
}

void reshape (int w, int h)
{
glViewport (0, 0, (GLsizei) w, (GLsizei) h);
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
gluPerspective(60.0, (GLfloat) w/(GLfloat) h, 4, 10.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt (0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
}

int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB);
glutInitWindowSize (500, 500);
glutInitWindowPosition (100, 100);
glutCreateWindow ("gluPerspective ( X, X, 4,10 )");
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutMainLoop();
return 0;
}