视图变换 :用于指定观察者的位置和方向;
模型视图变换:移动和变换场景中的模型;
投影变换 :对视见空间进行裁剪和扭曲;
视见区变换:对最终输出进行缩放。
1.1 视图变换
在一个场景中,我们希望改变观察者的位置和观察角度。用于改变观察者方位和角度的变换,就是视图变换。默认情况下(没有执行任何变换时),观察者位于点(0,0,0),且视线朝着-Z方向。也就是说,只有在z<0的地方绘图,才有可能被观察到。
1.2 模型视图变换
此变换用于移动和旋转场景中的物体。使用模型视图变换完全可以代替视图变换。道理是很简单的:比如你想使用视图变换将观察者向-Z轴移动10个单位,此时场景中所有的物体都向+Z轴移动了10个单位。这跟你直接使用模型视图变换将场景中所有物体向+Z方向移动10个单位的效果是完全一样的。
1.3 投影变换
要把3D场景投影到2D平面上,就必须执行投影变换。投影变换有两种形式,即平行投影变换和透视投影变换。关于平行投影和透视投影,在第3中已进行了具体的介绍,这里不在复述。现在要强调的是,投影也是一种变换,实现投影,本质上是对场景中所有物体进行特殊的变换,使得它们能够被画在一个平面上。比如透视投影变换会将场景中所有物体按照远近不同进行缩放和扭曲,使它们看起来具有立体感。
1.4 视见区变换
这里又回到了第二章中的主题。视见区变换就是对投影后的2D图象进行缩放和剪裁,使它能够被正确地显示在窗口上。你可以回到第二章了解视见区的具体概念。
矩阵(Matrix)是那样的强大以至于几乎所有的变换都可以由矩阵来表达。矩阵是又n行m列的数组成的一个阵列(m、n≥1)。通过矩阵的乘法运算就可以运用各种变换。在OpenGL中,统一使用大小为4×4的矩阵。
由于矩阵的运算法则和具体数学内容,和OpenGL这一主题并没有太大关系(使用OpenGL并不需要了解矩阵是怎样运算的,因为OpenGL会帮你完成一切),所以这里不再介绍。但这并不代表这些数学知识是不重要的,灵活地运用矩阵,可以自己创造出许多OpenGL没有提供的变换,并提高运算速度。你可以参看《线性代数》了解更多内容。
在OpenGL进行变换操作时,会首先把顶点转换为1×4的矩阵(第1-3行分别存放顶点的x、y、z坐标,第4行存放w坐标,即缩放因子,一般总为1.0),然后将这个点依次乘以模型视图变换矩阵、投影矩阵、视见区变换矩阵……最后得到因出现在屏幕上的2D屏幕坐标,完成变换。幸运的是,你不需要任何数学基础,哪怕你对矩阵一无所知,也能顺利地完成这一流程。因为OpenGL已经封装了高级函数,这使得你不用自己动手写矩阵,就能完成所有的基本变换。稍后就将介绍这些基本函数。
对于每一种变换,OpenGL都有自己的函数用来生成这些变换的矩阵并应用它们。下面将一一介绍。
3.1 模型变换矩阵
这是本章最重要的内容。使用模型变换,你就可以完成物体的旋转和移动,并产生移动观察者的效果。这正是本章的主题。为了能够完成我们的示例,我们定义以下函数在原点绘制一个球体。以下函数涉及到二次曲面的内容,这是OpenGL的另一个高级主题,我们将在今后的章节中具体讲解,现在我们只用它来绘制球体:
procedure DrawSphere(R:Single); //R代表球体的半径
var SpObj:GLUQuadricObj;
begin
spObj:=gluNewQuadric;
gluQuadricNormals(SpObj,GLU_SMOOTH);
gluQuadricOrientation(SpObj,GLU_OUTSIDE);
gluSphere(SpObj,R,50,50);
gluDeleteQuadric(spObj);
end;
3.1.1 平移
当我们调用DrawSphere时,会在原点绘制一个球体。现在我们想在点(0,10,0)上绘制这个球体,就必须在绘制之前将坐标系沿+Y方向平移10个单位。于是我们会写出这样的代码:
//建立一个将坐标系沿+Y方向平移10个单位的矩阵:
....
//用当前模型视图矩阵乘以这个矩阵:
...
DrawSphere(5);//绘制一个半径为5的球体
但事实上,我们不需要这么麻烦。OpenGL为我们提供了这样一个函数:
glTranslatef(x,y,z:Single);
其中,x,y,z分别表示在X、Y、Z轴上平移的量。调用这个函数之后,OpenGL会自动生成一个平移矩阵,然后应用这个矩阵。因此,我们可以这样写代码:
glTranslatef(0,10,0);
DrawSphere(5);
这样就能在(0,10,0)上绘制一个球体了。
3.1.2 旋转
与平移类似,OpenGL也为我们提供了一个高级函数用于旋转物体:
glRotatef(Angle,x,y,z:Single);
这个函数将生成并应用一个将坐标系以向量(x,y,z)为轴,旋转angle个角度的矩阵。如果我们想将一个球体以Y轴自转50度,就可以调用:
glRotatef(50,0,1,0);
DrawSphere(5);
3.1.3 缩放
缩放变换其实是将坐标系的x、y、z轴按不同的缩放因子展宽,从而实现缩放效果。函数
glScalef(x,y,z:Single);
把坐标系的X、Y、Z轴分别缩放x、y、z倍。例如:
glScalef(2,2,2);
DrawSphere(5);
将绘制一个半径为10的球体。
3.1.5 矩阵堆栈
如果每次变换前都把当前矩阵恢复到单位矩阵,也比较麻烦。更多时候,我们希望保存当前矩阵,执行一些变换之后,把当前矩阵恢复到上次保存时的状态。
OpenGL为我们提供了一个“矩阵堆栈”满足我们的这种要求。我们可以把当前矩阵压入堆栈中,然后执行一些变换,再弹出刚才压入的矩阵,从而把当前矩阵恢复到上次变换之前的状态。我们调用
glPushMatrix();
把当前矩阵压入矩阵堆栈,调用
glPopMatrix();
弹出矩阵。我们还可以分别调用
glGet(GL_MAX_MODELVIEW_STACK_DEPTH);
glGet(GL_MAX_PROJECTION_STACK_DEPTH);
来获取模型视图矩阵堆栈和投影矩阵堆栈的最大堆栈深度。一般情况下(在Windows平台上),模型视图的最大堆栈深度是32,而投影堆栈的最大深度是2。