这些天刚刚接触opengl编程,感觉在坐标转换的地方理解起来有点难度,下午从网上找了一下红宝书中介绍的nate robin示例程序,对其中的transformation.c研究了一下,通过不断改变参数的设置,结合显示的结果,终于对各种坐标变换的关系屡清了一点思路,现在结合transformation.c介绍一下我对opengl坐标变换的理解,希望能对有同样疑惑的朋友有所帮助。
首先介绍一下opengl四种坐标变换的含义。模型变换:用于调整物体的大小、位置,模型边换之后得到的物体坐标是相对于全局坐标系统的。视图变换:用于设置相机的位置、相机轴线的方向以及拍摄的方向。模型变换和视图变换统称为模型视图变换(modelview),经过模型视图变换之后的坐标是视觉系统内的坐标,其实是模型相对于相机的坐标,因为这里是模型和视图的一种相对关系,因此模型变换和视图变换具有一定的等价性和可转换性。接着是投影变换,投影变换的效果是建立一个视景体,使物体位于其中,对视景体外的物体部分进行截断,投影变换之后的坐标反应的是物体在视景体内的位置属性,比如同样大小的面(在全局坐标系统中),靠近视景体前端的占其相对应的截面的比例更大,因此在变换后的面积也更大。最后是视口变换,其作用是将确定最终图像的大小,并将帧缓冲区域内的数据转变为能显示在屏幕上的像素。由于变换是通过左乘矩阵完成的,因此实际坐标变换的顺序和代码中出现的顺序是相反的。一般先设置模型视图变换,再设置投影变换,最后是视口变换,并且由于改变窗口只对投影和视口变换有影响,因此常将这两种变换放在reshape函数里。
transformation.c是将汽车模型的立体图显示在窗口中,而且做成了交互式的界面 。程序包括了4个主要部分:窗口(window)部分、world-space部分、screen-space部分和command-space部分。world子窗口用于显示汽车模型和相机、视景体的位置关系,screen子窗口显示汽车模型,command子窗口显示参数,便于交互式更改以观效果。当window的大小或位置发生改变时,main-reshape函数被调用,main-reshape再改变每个子窗口的reshape参数,使其被回调。window、screen和command的display和reshape函数比较简单,就是对种变换按顺序进行设置,在这不多冗述,主要是解释一下world-space的reshape和display。
void world_reshape(int width, int height)
{
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60.0, (GLfloat)width/height, 0.01, 256.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0.0, 0.0, -5.0); //world-space的四种变换
glRotatef(-45.0, 0.0, 1.0, 0.0);
glClearColor(0.0, 0.0, 0.0, 0.0);
glEnable(GL_DEPTH_TEST);
glEnable(GL_LIGHT0);
}
void world_display(void)
{
.......
invert(modelview, inverse); //求screen-space模型视图变换的逆
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
if (world_draw) {
glEnable(GL_LIGHTING);
glPushMatrix();
glMultMatrixd(inverse);
glLightfv(GL_LIGHT0, GL_POSITION, pos);
glPopMatrix();
drawmodel();
glDisable(GL_LIGHTING);
}
glPushMatrix();
glMultMatrixd(inverse);
glLightfv(GL_LIGHT0, GL_POSITION, pos);
glPushMatrix();
.............
/*draw the axis and eye vector */
.......................
glPopMatrix();
invert(projection, inverse); //求screen-space投影变换的逆
glMultMatrixd(inverse);
..................
/* draw the viewing frustum */
............................
glPopMatrix();
glutSwapBuffers();
}
world-space画的是screen-space中汽车模型与相机、视景体的位置关系,因此,汽车模型、相机(用三维坐标系表示)和视景体就相当于world-space中的“模型”。在world-reshape函数中设置的是world空间中的四种变换,这相当于最外层的变换,我们可以先不管,等将模型“画”好了再说。
world-display函数就是用来画模型的,及汽车、三维坐标系和视景体。invert函数是用来求逆变换的,逆变换矩阵保存在inverse数组中。modelview矩阵式screen-space中的模型视图变换矩阵。对它求逆得到inverse,并用inverse来变换三维坐标系模型的坐标,其实是确定了三维坐标系模型和汽车模型的相对关系:screen-space中的modelview一改变便会通过invert函数传递到world-space中,三维坐标系模型便会相对汽车模型做相反的变化。projection矩阵是screen-space中投影变换矩阵,对它求逆并作用到world-space视景体模型上,产生视景体的立体效果(其实可以这样理解:在screen-space中同样大小的两个面,靠近相机的面显得更大,因此在world-space中逆变换之后,靠近相机模型的视景体的面显得更小)。在projection逆矩阵基础上再左乘modelview的逆矩阵,就将视景体模型和三维坐标模型绑定在了一起。当screen-space中modelview变化时,在world-space中三维坐标模型和视景体模型就会相对汽车模型做相反的运动。
模型“画”完之后,再对world-space进行四种变换即可,这里将模型相对y轴旋转45度是为了更直观。
再次提醒一下,模型坐标变换的顺序和代码的顺序是相反的。红宝书中使用全局坐标系统和局部坐标系统的概念,局部坐标系统是固定在模型中心的,随模型的变换而变化,根据局部坐标系统和 glPopMatrix(), glPushMatrix()函数,以及模型之间的相对位置关系,可以使程序变得简洁很多。