视图变换,是指变换照相机的位置,角度。
模型变换,是指变换被照物体的位置,角度。
这两个变换,都会影响最终图形中,物体的位置,角度。而这两个变换,可以达到相同的效果。比如,你想要一个倒着的水杯图形,可以把你自己倒立,这样看到的水杯就是倒立的了。或者把水杯倒立,自己直立,也能看到倒立的水杯。
如图所示,这两种变换,可以看做达到目的的不同途径。甚至可以同时使用视图变换和模型变换,只要最终拿到了我们想要的图像就可以了。至于使用的是视图变换,还是模型变换,看我们理解问题的角度。
3.2.1 对变换进行思考
变换顺序,对最终的结果影响很大。
看下面的例子:
图中有两个操作,旋转和移动。一个是沿原点绕z轴逆时针旋转45度,另一个是沿x轴向下平移。左图中,是先旋转,再移动,物体最终在x轴上。右图中,是先移动,再旋转,物体最终在x=y轴上。变换顺序不同,导致物体最终位置不同,这就是变换顺序的影响。
变换顺序,在OpenGL中的具体实现。
在OpenGL中,所有的变换,都是通过矩阵来实现的。一个矩阵,表示一个或多个变换。模型视图变换,是通过模型视图矩阵来实现的。由于这个矩阵经常变换,需要进行管理,OpenGL中是通过矩阵堆栈来对矩阵进行管理的。
当前模型视图矩阵如果用C来表示,在当前模型视图基础上,进行一个变换,这个变换使用的矩阵为M。那么一个顶点v的变换之后的坐标为CMv。也就是说,M变换先作用于顶点v,然后再是当前模型视图矩阵C。
看下面的例子:
glMatrixMode(GL_MODELVIEW);
glLoadIndentity();
glMultMatrixf(N); //变换N
glMultMatrixf(M); //变换M glMultMatrixf(L); //变换L glBegin(GL_POINTS); glVertex3f(v); glEnd();
这段代码中,模型视图矩阵按顺序分别包含了I, N, NM,最后是NML,其中I表示单位矩阵。经过变换的顶点是NMLv。因此,顶点变换就是N(M(Lv)),也就是说,v首先与L相乘,Lv再与M相乘,MLv再与N相乘,而不是按它们指定的顺序出现的。
全局固定坐标系
图3-4中,左图中的先旋转,再平移,代码实现如下:
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glMultMatrixf(T); //平移
glMultMatrixf(R); //旋转 draw_the_object();
只要记住一点,物体的变换顺序,和矩阵的出现顺序,正好相反。
局部移动坐标系统
这个通常用于模型的关节控制。比如机器人手臂。比如画汽车轮子上的螺钉,这个螺钉的位置,相对于汽车轮子,这个轮子上建立的坐标系,叫局部移动坐标系。而轮子的位置,又是相对于汽车本身,最后,汽车本身,是在全局坐标系中指定。
3.2.2 模型变换
模型变换,主要涉及三个函数,移动、旋转、缩放。有了这三个函数的组合,我们可以进行任意变换。
void glTranslate{fd}(TYPE x, TYPE y, TYPE z);
void glRotate{fd}(TYPE angle, TYPE x, TYPE y, TYPE z); void glScale{fd}(TYPE x, TYPE y, TYPE z);
这三个函数,之前接触过。glTranslate,进行平移,平移的偏移量,由(x, y, z)指定。 glRotate,旋转,以逆时针方向绕着从原点到点(x, y, z)的直线旋转角度angle。glScale,按照一定比列进行缩放,比例在x轴,y轴,z轴方向上的量是(x, y, z)。 3.2.3 视图变换
视图变换,相关的三个函数是glTranslate,glRotate和gluLookAt函数。
这个glTranslate和glRotate在模型变换中,我们已经见过了。怎么模型变换和视图变换,使用的是同样的函数呢?因为变换是相对的。 比如,让模型和照相机距离5个单位长度,假设模型和照相机放在一起。我们可以将物体向前移动5个单位长度,也可以将照相机向后移动5个单位长度。所以,视 图变换,也是使用glTranslate和glRotate。只是这个参数的含义相反罢了。
比如
glTranslatef(0.0, 0.0, -5.0);
这个函数在场景中把物体沿z轴移动-5个单位,相当于把照相机沿z轴移动+5个单位。
使用工具函数gluLookAt()
void gluLookAt(GLdouble eyex, GLdouble eyey, GLdouble eyez,GLdouble centerx, GLdouble centery, GLdouble centerz,GLdouble upx, GLdouble upy, GLdouble upz);
定义一个视图矩阵,并把它与当前矩阵进行右乘。目标观察点eyex, eyey, eyez。centerx, centery和centerz参数指定了视线上的任意一点。upx,upy和upz参数表示哪个方向是朝上的(也就是说,在视景体中自底向上的方向)。 默认情况下,照相机位于原点,指向z轴的负方向,以y轴的正方向为朝上方向。相当于调用:
gluLookAt(0.0, 0.0, 0.0, 0.0, 0.0, -100.0, 0.0, 1.0, 0.0);
参考点的z值是-100.0, 但它也可以是任意的负值,因为它不会影响视线的方向。 下面是使用gluLookAt的一个例子。
gluLookAt(4.0, 2.0, 1.0, 2.0, 4.0, -3.0, 2.0, 2.0, -1.0);
这个函数,将摄像机移动到了(4.0, 2.0, 1.0)这个点,摄像机朝向(2.0, 4.0, -3.0)方向,摄像机向上的方向为(2.0, 2.0, -1.0)。
强烈推荐,理解这几个函数(glTranslate, glRotate, gluLookAt),使用Nate Robin的教程。网上有下的。
创建自定义的工具函数
创建自定义的函数,其实就是在自己的函数中,调用glTranslate和glRotate这两个函数。
要创建自定义的工具函数,主要是弄清楚两个东西:一个是参数是相对于哪个坐标系的,第二个就是照相机的变换顺序,与glTranslate,glRotate出现的顺序相同。
比如编写一个飞机模拟器,并且以飞机的驾驶员座位观察点显示飞机外面的景象。我们可以用一个圆点位于跑道上的坐标系统来描述整个场景,飞机相对于坐 标(x, y, z)。然后,假设飞机还有倾侧角、螺旋角和航向改变角(这些都是飞机相对于它的重心的旋转角度)。下面这个函数可以作用视图变换函数使用。
void pilotView(GLdouble planex, GLdouble planey, GLdouble planez, GLdouble roll, GLdouble pitch, GLdouble heading)
{glRotated(roll, 0.0, 0.0, 1.0); glRotated(pitch, 0.0, 1.0, 0.0); glRotated(heading, 1.0, 0.0, 0.0); glTranslated(-planex, -planey, -planez); }
这个其实很好理解,因为这个roll, pitch, heading,都是相对于飞机的。而飞机,就是我们的相机。所以,先将飞机旋转到一定角度,然后移动到点(planex, planey, planez)上面。因为这个planex, planey, planez是相对于机场跑道的坐标系,而我们移动的飞机相当于相机,所以都要取负号。