Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1110684
  • 博文数量: 165
  • 博客积分: 3900
  • 博客等级: 中校
  • 技术积分: 1887
  • 用 户 组: 普通用户
  • 注册时间: 2007-04-06 15:15
文章分类

全部博文(165)

文章存档

2020年(3)

2019年(8)

2017年(2)

2016年(8)

2015年(14)

2013年(15)

2012年(32)

2011年(11)

2010年(14)

2009年(7)

2008年(20)

2007年(31)

分类: LINUX

2010-08-27 10:11:52

第四章 矩阵变换

    通过前三章的学习,我们知道了如何使用OpenGL在3D空间中绘制基本图元,并把使用图元组成模型。然而,在我们绘制完一个物体或一个场景之后,我们总希望从多个角度观察这个物体,或者在场景中走动。这时,我们需要OpenGL的另一个功能:变换。

    OpenGL为我们提供了许多方面和类型的变换。你可以对投影方式进行变换,也可以对物体/模型 进行变换。你可以改变自己的位置和方向,也可以改变物体的大小和角度。学习本章内容,你将了解:

    变换(Transform),可以使3D空间中的物体投影到2D平面上。使用变换,你可以移动、旋转、缩放甚至弯曲一个物体。然而变换并没有直接修改顶点 数据,取而代之,变换修改了坐标系。如果旋转一个坐标系,然后再在旋转后的坐标系里绘图,绘制后的图形就好像被旋转了。在基本OpenGL渲染流程中,将 进行以下变换:

    视图变换 :用于指定观察者的位置和方向;
    模型视图变换:移动和变换场景中的模型;
    投影变换 :对视见空间进行裁剪和扭曲;
    视见区变换:对最终输出进行缩放。

4.1.1  视图变换

    在一个场景中,我们希望改变观察者的位置和观察角度。用于改变观察者方位和角度的变换,就是视图变换。默认情况下(没有执行任何变换时),观察者位于点(0,0,0),且视线朝着-Z方向。也就是说,只有在z<0的地方绘图,才有可能被观察到。

4.1.2  模型视图变换

    此变换用于移动和旋转场景中的物体。使用模型视图变换完全可以代替视图变换。道理是很简单的:比如你想使用视图变换将观察者向-Z轴移动10个单位,此时 场景中所有的物体都向+Z轴移动了10个单位。这跟你直接使用模型视图变换将场景中所有物体向+Z方向移动10个单位的效果是完全一样的。 

4.1.3  投影变换

    要把3D场景投影到2D平面上,就必须执行投影变换。投影变换有两种形式,即平行投影变换和透视投影变换。关于平行投影和透视投影,在第3中已进行了具体 的介绍,这里不在复述。现在要强调的是,投影也是一种变换,实现投影,本质上是对场景中所有物体进行特殊的变换,使得它们能够被画在一个平面上。比如透视 投影变换会将场景中所有物体按照远近不同进行缩放和扭曲,使它们看起来具有立体感。

4.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都有自己的函数用来生成这些变换的矩阵并应用它们。下面将一一介绍。

4.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;

4.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)上绘制一个球体了。

4.3.1.2 旋转

    与平移类似,OpenGL也为我们提供了一个高级函数用于旋转物体:

     glRotatef(Angle,x,y,z:Single);

    这个函数将生成并应用一个将坐标系以向量(x,y,z)为轴,旋转angle个角度的矩阵。如果我们想将一个球体以Y轴自转50度,就可以调用:

     glRotatef(50,0,1,0);
     DrawSphere(5);

4.3.1.3 缩放

    缩放变换其实是将坐标系的x、y、z轴按不同的缩放因子展宽,从而实现缩放效果。函数

    glScalef(x,y,z:Single);

    把坐标系的X、Y、Z轴分别缩放x、y、z倍。例如:

      glScalef(2,2,2);
      DrawSphere(5);

    将绘制一个半径为10的球体。

4.3.1.4 变换的叠加性质

    使用变换时,我们应该注意的是,变换是叠加在上次变换的基础上的。也就是说,变换的效果会累积。每次调用变换函数时,会生成一个新的函数来乘以当前的模型 视图矩阵,随后,新的矩阵将成为当前的模型变换矩阵,在下次执行变换时,会被新的矩阵相乘,因此作用效果将不断累积。举个例子就能很明白地说明这一点。

    例如,你想在(0,10,0)上绘制一个球体,完后在(10,0,0)上绘制另一个,得到如图4.3-1所示的图形:

图4.3-1

    你可能会写出如下代码:

      //沿Y轴向上平移10个单位
      glTranslatef(0,10,0);
      //画第一个球体
      DrawSphere(5);
      //沿X轴向左平移10个单位
      glTranslatef(10,0,0);
      //画第二个球体
      DrawSphere(5);

    然而,你不应该忘记,变换的作用效果是累积的。在绘制第二个球体时,由于此时坐标系已经向Y轴移动了10个单位,再向X方向移动10个单位之后,新的坐标系的原点应是绝对坐标系中的点(10,10)。因此,上述程序将绘制出如图4.3-2所示的图形。

图4.3-2

    你可能会在绘制第二个球体之前调用glTranslatef(0,-10,0);把坐标系往回移动10个单位。但这样会降低代码的可读性,还会给CPU增加额外的运算。这个时候,我们可以使用单位矩阵。

    我们可以调用glLoadIdentity();函数将当前模型视图变换矩阵重置到初始状态,再进行新的绘制:

    procedure RenderScene();
    begin
      glMatrixMode(GL_MODELVIEW);
      //沿Y轴向上平移10个单位
      glTranslatef(0,10,0);
      //画第一个球体
      DrawSphere(5);
      //加载单位矩阵
      glLoadIdentity;
      //沿X轴向上平移10个单位
      glTranslatef(10,0,0);
      //画第二个球体
      DrawSphere(5);
    end;

    请看第一行代码。这里调用了glMatrixMode函数。这个函数的作用是通知OpenGL我们将对模型视图变换矩阵进行操作。也就是要进行模型视图变换。glMatrixMode可用参数如下:

     GL_PROJECTION  :用于修改投影矩阵
     GL_MODELVIEW   :用于修改模型视图变换矩阵

4.3.1.5 矩阵堆栈

    如果每次变换前都把当前矩阵恢复到单位矩阵,也比较麻烦。更多时候,我们希望保存当前矩阵,执行一些变换之后,把当前矩阵恢复到上次保存时的状态。

    OpenGL为我们提供了一个“矩阵堆栈”满足我们的这种要求。我们可以把当前矩阵压入堆栈中,然后执行一些变换,再弹出刚才压入的矩阵,从而把当前矩阵恢复到上次变换之前的状态。我们调用

    glPushMatrix();

    把当前矩阵压入矩阵堆栈,调用

    glPopMatrix();

    弹出矩阵。我们还可以分别调用

    glGet(GL_MAX_MODELVIEW_STACK_DEPTH);
    glGet(GL_MAX_PROJECTION_STACK_DEPTH);

    来获取模型视图矩阵堆栈和投影矩阵堆栈的最大堆栈深度。一般情况下(在Windows平台上),模型视图的最大堆栈深度是32,而投影堆栈的最大深度是2。

    使用矩阵堆栈,4.3.1.4节中的程序可以改写为:

    procedure RenderScene();
    begin
      glMatrixMode(GL_MODELVIEW);
     //推入矩阵堆栈
      glPushMatrix;
      //沿Y轴向上平移10个单位
      glTranslatef(0,10,0);
      //画第一个球体
      DrawSphere(5);
      //恢复到上次保存时的状态
      glPopMatrix;
      //沿X轴向左平移10个单位
      glTranslatef(10,0,0);
      //画第二个球体
      DrawSphere(5);
    end;

4.3.2 投影矩阵

    设置投影矩阵往往在OpenGL绘图和模型视图变换之前。一般情况下,我们调用

    glMatrixMode(GL_PROJECTION);

    将当前矩阵设置为投影矩阵。再调用

    glOrtho 或 gluPerspective 来创建平行或透视投影。创建完后,再调用

    glMatrixMode(GL_MODELVIEW);

    将当前变换矩阵设置为模型视图变换矩阵。

    至此,你应该能够理解前面章节的示例程序中的 SetView 过程的意义了吧。请再看一次SetView过程:

procedure TfrmMain.SetView;

begin

  glClearColor(0,0,0,0);//设置背景颜色为黑色

  glViewPort(0,0,ClientWidth,ClientHeight);//指定OpenGL在此区域内绘图。

  glMatrixMode(GL_PROJECTION);//设置视图投影变换矩阵

    glLoadIdentity;//加载单位矩阵。

    glOrtho(0,ClientWidth,ClientHeight,0,1,-1);//创建平行投影。

  glMatrixMode(GL_MODELVIEW);//将矩阵变换对象切换为模型视图变换。

end;

    除了使用OpenGL为我们提供的几个高级变换函数之外,我们还可以自己创建一个矩阵,并使用当前矩阵乘以该矩阵来进行特殊的变换。你可以创建一个4×4的二维数组用于描述一个矩阵。如:

    M:array[1..4] of array[1..4] of Single;

    其中M[j,i]表示矩阵M的第j行,第i列的数据。

    你也可以创建一个一维数组:

    M:array[1..16]of Single;

    无论是2维数组还是一维数组,都是按照列优先的顺序保存的。如图4.4-1所示。

图4.4-1

   矩阵定义完后,调用

   glLoadMatrix(M);

   可以用矩阵M替换当前矩阵,调用

   glMultMatrix(M);

   用当前矩阵乘以矩阵M。

   要说明的是,使用glLoadMatrix或glMultMatrix的速度没有OpenGL的高级变换函数快。所以如果不是高级变换函数完成不了的变换,就不要使用glLoadMatrix或者glMultMatrix。

4.5 示例程序

    这是一个经典的示例程序。它演示了太阳系中地月系与太阳之间的运动关系:月球饶地球转,整个地月系饶太阳转,所有的星球都自转。这个例子很好地展示了矩阵 变换的性质和矩阵堆栈的作用。为了增加视觉效果,本程序中加入了光照渲染。同时,我们也加入了纹理贴图,这是为了能看星球自转的景象。有关光照和纹理贴图 的详细内容,我们都将会在今后的章节中具体讲解。

    以下是渲染过程的代码:

    procedure TfrmMain.RenderScene;
    begin
      glEnable(GL_CULL_FACE);
      glClear(GL_COLOR_BUFFER_BIT OR GL_DEPTH_BUFFER_BIT);
      glLoadIdentity;
      glTranslatef(0,0,-110);
      glRotatef(yDeg,0,1,0);
      glRotatef(xDeg,1,0,1);

      RenderLights;//光照处理

      //绘制太阳
      glColor3ub(255,100,64);
      glPushMatrix;
        glRotate(SunSelfAng,0,1,0);
        DrawSphere(10);
      glPopMatrix;
      glPushMatrix;//推入当前矩阵
      //绘制地月系
        glRotatef(EarthCommonAng,0,1,0);
        glTranslatef(50,0,0);
        glPushMatrix;//绘制地球
          glRotatef(EarthSelfAng,0,1,0);
          glColor3ub(20,50,255);
          DrawSphere(5);
        glPopMatrix;
        glPushMatrix;//绘制月球
          glColor3ub(200,200,200);
          glRotatef(MoonAng,0,1,0);
          glTranslatef(10,0,0);
          glRotatef(MoonSelfAng,0,1,0);
          DrawSphere(2);
        glPopMatrix;
      glPopMatrix;//弹出矩阵
      SwapBuffers(wglGetCurrentDC);

    end;

阅读(1468) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

chinaunix网友2010-08-29 08:20:16

Download More than 1000 free IT eBooks: http://free-ebooks.appspot.com