八、OpenGL变换
OpenGL变换是本篇的重点内容,它包括计算机图形学中最基本的三维变换,即几何变换、投影变换、裁剪变换、视口变换,以及针对OpenGL的特殊变换概念理解和用法,如相机模拟、矩阵堆栈等。学好了这章,才开始真正走进三维世界。
8.1、从三维空间到二维平面
8.1.1 相机模拟
在真实世界里,所有的物体都是三维的。但是,这些三维物体在计算机世界中却必须以二维平面物体的形式表现出来。那么,这些物体是怎样从三维变换到二维的呢?下面我们采用相机( Camera )模拟的方式来讲述这个概念,如图8-1所示。
实际上,从三维空间到二维平面,就如同用相机拍照一样,通常都要经历以下几个步骤 (括号内表示的是相应的图形学概念):
第一步,将相机置于三角架上,让它对准三维景物(视点变换,Viewing Transformation)。
第二步,将三维物体放在适当的位置(模型变换,Modeling Transformation)。
第三步,选择相机镜头并调焦,使三维物体投影在二维胶片上(投影变换,Projection Transformation)。
第四步,决定二维像片的大小(视口变换,Viewport Transformation)。
这样,一个三维空间里的物体就可以用相应的二维平面物体表示了,也就能在二维的电脑屏幕上正确显示了。
8.1.2 三维图形显示流程
运用相机模拟的方式比较通俗地讲解了三维图形显示的基本过程,但在具体应用OpenGL函数库编程时,还必须了解三维图形世界中的几个特殊坐标系的概念,以及用这些概念表达的三维图形显示流程。
计算机本身只能处理数字,图形在计算机内也是以数字的形式进行加工和处理的。大家都知道,坐标建立了图形和数字之间的联系。为了使被显示的物体数字化,要在被显示的物体所在的空间中定义一个坐标系。这个坐标系的长度单位和坐标轴的方向要适合对被显示物体的描述,这个坐标系称为世界坐标系。
计算机对数字化的显示物体作了加工处理后,要在图形显示器上显示,这就要在图形显示器屏幕上定义一个二维直角坐标系,这个坐标系称为屏幕坐标系。这个坐标系坐标轴的方向通常取成平行于屏幕的边缘,坐标原点取在左下角,长度单位常取成一个象素的长度,大小可以是整型数。
为了使显示的物体能以合适的位置、大小和方向显示出来,必须要通过投影。投影的方法有两种,即正射投影和透视投影。
有时为了突出图形的一部分,只把图形的某一部分显示出来,这时可以定义一个三维视景体(Viewing Volume)。正射投影时一般是一个长方体的视景体,透视投影时一般是一个棱台似的视景体。只有视景体内的物体能被投影在显示平面上,其他部分则不能。在屏幕窗口内可以定义一个矩形,称为视口(Viewport),视景体投影后的图形就在视口内显示。
为了适应物理设备坐标和视口所在坐标的差别,还要作一适应物理坐标的变换。这个坐标系称为物理设备坐标系。根据上面所述,三维图形的显示流程应如图8-2所示。
8.1.3 基本变换简单分析 下面举一个简单的变换例子,cube.c:
例8-4 简单变换例程(cube.c)
#include "glos.h"
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
void myinit(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);
void CALLBACK display (void)
{
glClear(GL_COLOR_BUFFER_BIT);
glColor3f (1.0, 1.0, 1.0);
glLoadIdentity (); /* clear the matrix */
glTranslatef (0.0, 0.0, -5.0); /* viewing transformation */
glScalef (1.0, 2.0, 1.0); /* modeling transformation */
auxWireCube(1.0); /* draw the cube */
glFlush();
}
void myinit (void)
{
glShadeModel (GL_FLAT);
}
void CALLBACK myReshape(GLsizei w, GLsizei h)
{
glMatrixMode (GL_PROJECTION); /* prepare for and then */
glLoadIdentity (); /* define the projection */
glFrustum (-1.0, 1.0, -1.0, 1.0, 1.5, 20.0); /* transformation */
glMatrixMode (GL_MODELVIEW); /* back to modelview matrix */
glViewport (0, 0, w, h); /* define the viewport */
}
void main(void)
{
auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);
auxInitPosition (0, 0, 500, 500);
auxInitWindow ("Perspective 3-D Cube");
myinit ();
auxReshapeFunc (myReshape);
auxMainLoop(display);
}
以上程序运行结果就是绘制一个三维的正面透视立方体。其中已经用到了相机模拟中提到的四种基本变换,即视点变换、模型变换、投影变换和视口变换。
下面简单分析一下整个程序过程:
1)视点变换。视点变换是在视点坐标系中进行的。视点坐标系于一般的物体所在的世界坐标系不同,它遵循左手法则,即左手大拇指指向Z正轴,与之垂直的四个手指指向X正轴,四指弯曲90度的方向是Y正轴。而世界坐标系遵循右手法则的。如图8-4所示。当矩阵初始化glLoadIdentity()后,调用glTranslatef()作视点变换。函数参数(x, y, z)表示视点或相机在视点坐标系中移动的位置,这里z=-5.0,意思是将相机沿Z负轴移动5个单位。
通常相机位置缺省值同场景中的物体一样