hyde
路人甲
路人甲
  • 注册日期2003-09-24
  • 发帖数555
  • QQ
  • 铜币1457枚
  • 威望0点
  • 贡献值0点
  • 银元0个
阅读:1223回复:0

OpenGL系列讲座(7)

楼主#
更多 发布于:2004-03-20 10:33
第五章   OpenGL 变换
 

OpenGL变换是本篇的重点内容,它包括计算机图形学中最基本的三维变换,即几何变换、

投影变换、裁剪变换、视口变换,以及针对OpenGL的特殊变换概念理解和用法,如相机模拟、

矩阵堆栈等。学好了这章,才开始真正走进三维世界。

 

5.1  从三维空间到二维平面
 

5.1.1 相机模拟

 

在真实世界里,所有的物体都是三维的。但是,这些三维物体在计算机世界中却必须以

二维平面物体的形式表现出来。那么,这些物体是怎样从三维变换到二维的呢?下面我们采

用相机(Camera)模拟的方式来讲述这个概念,如图2-5-1所示。

 

            

设置相机                   视点变换

 

              

调整物体位置                   模型变换

 

              

调焦拍照                        投影变换

 

              

冲印相片                         视口变换

 

 

                    图2-5-1  相机模拟

 

实际上,从三维空间到二维平面,就如同用相机拍照一样,通常都要经历以下几个步骤

(括号内表示的是相应的图形学概念):

    第一步,将相机置于三角架上,让它对准三维景物(视点变换,Viewing Transformation

);

    第二步,将三维物体放在适当的位置(模型变换,Modeling Transformation );

    第三步,选择相机镜头并调焦,使三维物体投影在二维胶片上(投影变换,Projection

Transformation );

    第四步,决定二维像片的大小(视口变换,Viewport Transformation )。

这样,一个三维空间里的物体就可以用相应的二维平面物体表示了,也就能在二维的计

算机屏幕上正确显示了。

 

 

5.1.2 三维图形显示流程

 

运用相机模拟的方式比较通俗地讲解了三维图形显示的基本过程,但在具体应用OpenGL

函数库编程时,还必须了解三维图形世界中的几个特殊坐标系的概念,以及用这些概念表达

的三维图形显示流程。

计算机本身只能处理数字,图形在计算机内也是以数字的形式进行加工和处理的。大家

都知道,坐标建立了图形和数字之间的联系。为了使被显示的物体数字化,要在被显示的物

体所在的空间中定义一个坐标系。这个坐标系的长度单位和坐标轴的方向要适合对被显示物

体的描述,这个坐标系称为世界坐标系。

计算机对数字化的显示物体作了加工处理后,要在图形显示器上显示,这就要在图形显

示器屏幕上定义一个二维直角坐标系,这个坐标系称为屏幕坐标系。这个坐标系坐标轴的方

向通常取成平行于屏幕的边缘,坐标原点取在左下角,长度单位常取成一个象素的长度,大

小可以是整型数。

为了使显示的物体能以合适的位置、大小和方向显示出来,必须要通过投影。投影的方

法有两种,即正射投影和透视投影。

有时为了突出图形的一部分,只把图形的某一部分显示出来,这时可以定义一个三维视

景体(Viewing Volume)。正射投影时一般是一个长方体的视景体,透视投影时一般是一个棱

台似的视景体。只有视景体内的物体能被投影在显示平面上,其他部分则不能。在屏幕窗口

内可以定义一个矩形,称为视口(Viewport),视景体投影后的图形就在视口内显示。

为了适应物理设备坐标和视口所在坐标的差别,还要作一适应物理坐标的变换。这个坐

标系称为物理设备坐标系。

    根据上面所述,三维图形的显示流程应如图2-5-2所示。

  

 

                    图2-5-2 三维图形的显示流程

 

5.1.3 基本变换简单分析

 

    下面举一个简单的变换例子,cube.c:

 

例 2-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);

    }

 

以上程序运行结果就是绘制一个三维的正面透视立方体。其中已经用到了相机模拟中提

到的四种基本变换,即视点变换、模型变换、投影变换和视口变换。

 

  

 

下面简单分析一下整个程序过程:

一、视点变换。视点变换是在视点坐标系中进行的。视点坐标系于一般的物体所在的世

界坐标系不同,它遵循左手法则,即左手大拇指指向Z正轴,与之垂直的四个手指指向X正

轴,四指弯曲90度的方向是Y正轴。而世界坐标系遵循右手法则的。如图2-5-3所示。当矩

阵初始化glLoadIdentity()后,调用glTranslatef()作视点变换。函数参数(x,y,z)表示视

点或相机在视点坐标系中移动的位置,这里z=-5.0,意思是将相机沿Z负轴移动5个单位。

通常相机位置缺省值同场景中的物体一样,都在原点处,而且相机初始方向都指向Z负轴。

这里相机移走后,仍然对准立方体。如果相机需要指向另一方向,则调用glRotatef()可以

改变。

  

 

                      图2-5-3  视点坐标系与世界坐标系

 

二、模型变换。模型变换是在世界坐标系中进行的。在这个坐标系中,可以对物体实施

平移glTranslatef()、旋转glRotatef()和放大缩小glScalef()。例子里只对物体进行比例

变换,glScalef(sx,sy,sz)的三个参数分别是X、Y、Z轴向的比例变换因子。缺省时都为1.0,

即物体没变化。程序中物体Y轴比例为2.0,其余都为1.0,就是说将立方体变成长方体。

三、投影变换。投影变换类似于选择相机的镜头。本例中调用了一个透视投影函数

glFrustum(),在调用它之前先要用glMatrixMode()说明当前矩阵方式是投影L_PROJECTION。

这个投影函数一共有六个参数,由它们可以定义一个棱台似的视景体。即视景体内的部分可

见,视景体外的部分不可见,这也就包含了三维裁剪变换。

四、视口变换。视口变换就是将视景体内投影的物体显示在二维的视口平面上。通常,

都调用函数glViewport()来定义一个视口,这个过程类似于将照片放大或缩小。

总而言之,一旦所有必要的变换矩阵被指定后,场景中物体的每一个顶点都要按照被指

定的变换矩阵序列逐一进行变换。注意,OpenGL中的物体坐标一律采用齐次坐标,即

(x,y,z,w),故所有变换矩阵都采用4X4矩阵。一般说来,每个顶点先要经过视点变换和模型

变换,然后进行指定的投影,如果它位于视景体外,则被裁剪掉。最后,余下的已经变换过

的顶点x、y、z坐标值都用比例因子w除,即x/w、y/w、z/w,再映射到视口区域内,这样

才能显示在屏幕上。

 

 

5.2  几何变换
 

实际上,上述所说的视点变换和模型变换本质上都是一回事,即图形学中的几何变换。

只是视点变换一般只有平移和旋转,没有比例变换。当视点进行平移或旋转时,视点坐标系

中的物体就相当于在世界坐标系中作反方向的平移或旋转。因此,从某种意义上讲,二者可

以统一,只是各自出发点不一样而已。读者可以根据具体情况,选择其中一个角度去考虑,

这样便于理解。

 

 

5.2.1 两个矩阵函数解释

 

    这里先解释两个基本OpenGL矩阵操作函数,便于以后章节的讲述。函数解释如下:

 

void glLoadMatrix{fd}(const TYPE *m)

设置当前矩阵中的元素值。函数参数*m是一个指向16个元素(m0,m1,...,m15)的指针,

这16个元素就是当前矩阵 M 中的元素,其排 列方式如下:

       *                  *

       * m0  m4  m8   m12 *

    M =* m1  m5  m9   m13 *

       * m2  m6  m10  m14 *

       * m3  m7  m11  M15 *

       *                  *

 

void glMultMatrix{fd}(const TYPE *m)

   用当前矩阵去乘*m所指定的矩阵,并将结果存放于*m中。当前矩阵可以是用

glLoadMatrix()

指定的矩阵,也可以是其它矩阵变换函数的综合结果。

 

    当几何变换时,调用OpenGL的三个变换函数glTranslate*()、glRotate*()和glScale*(),

实质上相当于产生了一个近似的平移、旋转和比例矩阵,然后调用glMultMatrix()与当前矩

阵相乘。但是直接调用这三个函数程序运行得快一些,因OpenGL自动能计算矩阵。

 

 

5.2.2 平移


 

    平移变换函数如下:

void  glTranslate{fd}(TYPE x,TYPE y,TYPE z)

三个函数参数就是目标分别沿三个轴向平移的偏移量。这个函数表示用这三个偏移量生

成的矩阵乘以当前矩阵。当参数是(0.0,0.0,0.0)时,表示对函数glTranslate*()的操作是

单位矩阵,也就是对物体没有影响。

    平移示意如图2-5-5所示。

  

图2-5-5  目标平移

 

 

 

5.2.3 旋转

 

    旋转变换函数如下:

void  glRotate{fd}(TYPE angle,TYPE x,TYPE y,TYPE z)

函数中第一个参数是表示目标沿从点(x,y,z)到原点的方向逆时针旋转的角度,后三个参

数是旋转的方向点坐标。这个函数表示用这四个参数生成的矩阵乘以当前矩阵。当角度参数

是0.0时,表示对物体没有影响。

    旋转示意如图2-5-6所示。

 

  

  

图2-5-6  目标旋转

 

5.2.3 缩放和反射


    缩放和反射变换函数如下:

void  glScale{fd}(TYPE x,TYPE y,TYPE z)

三个函数参数值就是目标分别沿三个轴向缩放的比例因子。这个函数表示用这三个比例

因子生成的矩阵乘以当前矩阵。这个函数能完成沿相应的轴对目标进行拉伸、压缩和反射三

项功能。当参数是(1.0,1.0,1.0)时,表示对函数glScale*()操作是单位矩阵,也就是对物

体没有影响。当其中某个参数为负值时,表示将对目标进行相应轴的反射变换,且这个参数

不为1.0,则还要进行相应轴的缩放变换。最好不要令三个参数值都为零,这将导致目标沿

三轴都缩为零。

    缩放和反射示意如图2-5-7所示。

 

  

 

图2-5-7  目标缩放和反射

 

5.2.5 几何变换举例

 

以上介绍了三个基本几何变换函数,下面举一个简单的例子进一步说明它们的用法。程

序geomtrsf.c如下:

 

例 2-5  几何变换例程 geomtrsf.c

 

#include "glos.h"

#include <GL/gl.h>

#include <GL/glu.h>

#include <GL/glaux.h>

 

void myinit(void);

void draw_triangle(void);

void CALLBACK display(void);

void CALLBACK myReshape(GLsizei w, GLsizei h);

 

void draw_triangle(void)

{

    glBegin(GL_LINE_LOOP);

    glVertex2f(0.0, 25.0);

    glVertex2f(25.0, -25.0);

    glVertex2f(-25.0, -25.0);

    glEnd();

}

 

void CALLBACK display(void)

{

    glClearColor (0.0, 0.0, 0.0, 1.0);

    glClear (GL_COLOR_BUFFER_BIT);

 

/* draw an original triangle */

 

    glLoadIdentity ();

    glColor3f (1.0, 1.0, 1.0);         /* white */

    draw_triangle ();

 

/* translating a triangle along X_axis  */

 

    glLoadIdentity ();

    glTranslatef (-20.0, 0.0, 0.0);

    glColor3f(1.0,0.0,0.0);           /* red */

    draw_triangle ();

 

/* scaling a triangle along X_axis by 1.5 and along Y_axis by 0.5  */

 

    glLoadIdentity();

    glScalef (1.5, 0.5, 1.0);

    glColor3f(0.0,1.0,0.0);           /* green */

    draw_triangle ();

 

/* rotating a triangle in a counterclockwise direction about Z_axis */

 

    glLoadIdentity ();

    glRotatef (90.0, 0.0, 0.0, 1.0);

    glColor3f(0.0,0.0,1.0);          /* blue */

    draw_triangle ();

 

/*  scaling a triangle along Y_axis and reflecting it about Y_axis  */

 

    glLoadIdentity();

    glScalef (1.0, -0.5, 1.0);

    glColor3f(1.0,1.0,0.0);          /* yellow */

    draw_triangle ();

 

    glFlush();

}

 

void myinit (void)

{

    glShadeModel (GL_FLAT);

}

 

void CALLBACK myReshape(GLsizei w, GLsizei h)

{

    glViewport(0, 0, w, h);

    glMatrixMode(GL_PROJECTION);

    glLoadIdentity();

    if (w <= h)

    glOrtho (-50.0, 50.0, -50.0*(GLfloat)h/(GLfloat)w,

        50.0*(GLfloat)h/(GLfloat)w, -1.0, 1.0);

    else

    glOrtho (-50.0*(GLfloat)w/(GLfloat)h,

        50.0*(GLfloat)w/(GLfloat)h, -50.0, 50.0, -1.0, 1.0);

    glMatrixMode(GL_MODELVIEW);

}

 

void main(void)

{

    auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);

    auxInitPosition (0, 0, 500, 500);

    auxInitWindow ("Geometric Transformations");

    myinit ();

    auxReshapeFunc (myReshape);

    auxMainLoop(display);

}

 

   以上程序运行结果:第一个白色三角形是原始三角形,第二个红色三角形是白三角沿X

负轴平移后的三角形,第三个绿色三角形是白三角分别沿X轴和Y轴比例变换后的三角形,

第四个蓝色三角形是白三角绕Z正轴逆时针转90度后的三角形,第五个黄色三角形是白三角

沿Y轴方向缩小一倍且相对于X轴作反射后形成的三角形。

 

  

图 2-5-8 三角形的几何变换

 

喜欢0 评分0
夜落了,风静了,我喜欢一本书,一杯茶,一粒摇曳的烛光...
游客

返回顶部