代码改变世界

OpenGL 位图与图像 (转载)

2010-12-01 16:12  bingcaihuang  阅读(808)  评论(0编辑  收藏  举报

OpenGL基础图形编程(二)


十一、位图与图像

11.1、位图

  11.1.1 位图(Bitmap)与字符(Font)
  位图是以元素值为0或1的矩阵形式存储的,通常用于对窗口中相应区域的绘图屏蔽。比如说,当前颜色设置为红色,则在矩阵元素值为1的地方象素用红色来取代,反之,在为0的地方,对应的象素不受影响。位图普遍用于字符显示,请看下面例子:

  例11-1 位图字符例程(font.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);

  GLubyte rasters[12] = {
    0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xfc,
    0xfc, 0xc0, 0xc0, 0xc0, 0xff, 0xff};

  void myinit(void)
  {
    glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
    glClearColor (0.0, 0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT);
  }

  void CALLBACK display(void)
  {
    glColor3f (1.0, 0.0, 1.0);
    glRasterPos2i (100, 200);
    glBitmap (8, 12, 0.0, 0.0, 20.0, 20.0, rasters);
    glBitmap (8, 12, 0.0, 0.0, 0.0, 0.0, rasters);

    glColor3f (1.0, 1.0, 0.0);
    glRasterPos2i (150, 200);
    glBitmap (8, 12, 0.0, 0.0, 0.0, 0.0, rasters);

    glFlush();
  }

  void CALLBACK myReshape(GLsizei w, GLsizei h)
  {
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho (0, w, 0, h, -1.0, 1.0);
    glMatrixMode(GL_MODELVIEW);
  }

  void main(void)
  {
    auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Bitmap");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
  }

  以上程序运行结果是显示三个相同的字符F。OpenGL函数库只提供了最底层操作,即用glRasterPos*()和glBitmap()在屏幕上定位和画一个位图,图11-1显示了F的位图和相应的位图数据。

图11-1 字符F位图显示


图11-2 字符F位图及其相应数据


  在图中,字符大小为12*8的方阵,每一行数据用8位16进制表示。注意:位图数据总是按块存储,每块的位数总是8的倍数,但实际位图的宽并不一定使8的倍数。组成位图的位从位图的左下角开始画:首先画最底下的一行,然后是这行的上一行,依此类推。这个程序中的几个重要函数的解释将在下面几个小节,其中函数glPixelstorei()描述了位图数据在计算机内存中存储的方式。

  11.1.2 当前光栅位置
  当前光栅位置函数就是:

  void glRasterPos{234}{SIFD}[V](TYPE x,TYPE y,TYPE z,TYPE w);

  设置当前所画位图或图像的原点。其中参数x、y、z、w给出了光栅位置坐标。在变换到屏幕坐标时(即用模型变换和透视变换),光栅位置坐标与 glVertex*()提供的坐标同样对待。也就是说,变换后要么确定一个有效点,要么认为位于视口以外的点的当前光栅位置无效。
  在上一例 中,颜色设置的位置与当前光栅位置函数调用的位置有关,glColor*()必须放在glRasterPos*()前,则紧跟其后的位图就都继承当前的颜色,例前两个紫色的F;若要改变当前位图颜色,则需重新调用glColor*()和 glRasterPos*(),如第三个黄色字符F的显示。

  11.1.3 位图显示
  当设置了光栅位置后,就可以调用glBitmap()函数来显示位图数据了。这个函数形式为:

  void glBitmap( GLsizei width,GLsizei height,GLfloat xbo,GLfloat ybo,
          GLfloat xbi,GLfloat ybi,const GLubyte *bitmap);

  显示由bitmap指定的位图,bitmap是一个指向位图的指针。位图的原点放在最近定义的当前光栅位置上。若当前光栅位置是无效的,则不显示此位图或其一部分,而且当前光栅位置仍然无效。参数width和height一象素为单位说明位图的宽行高。宽度不一定是8的倍数。参数xbo和ybo定义位图的原点(正值时,原点向上移动;负值时,原点向下移动)。参数xbi和ybi之处在位图光栅化后光栅位置的增量。在上一例中:

  glColor3f (1.0, 0.0, 1.0);
  glRasterPos2i (100, 200);
  glBitmap (8, 12, 0.0, 0.0, 20.0, 20.0, rasters);
  glBitmap (8, 12, 0.0, 0.0, 0.0, 0.0, rasters);

  第一个字符F与第二个字符F的间距是由glBitmap()的两个增量参数决定的,即第二个字符F在第一个字符F的基础上分别向X正轴和Y负轴移动20个象素单位。

11.2 图像
  一般来说,OpenGL图像(image)操作包括象素读写、象素拷贝和图像缩放,下面分别来介绍。

  11.2.1 象素读写
  OpenGL提供了最基本的象素读和写函数,它们分别是:

  读取象素数据:

  void glReadPixels(GLint x,GLint y,GLsizesi width,GLsizei height,
           GLenum format,GLenum type,GLvoid *pixel);

   函数参数(x, y)定义图像区域左下角点的坐标,width和height分别是图像的高度和宽度,*pixel是一个指针,指向存储图像数据的数组。参数format 指出所读象素数据元素的格式(索引值或R、G、B、A值,如表11-1所示),而参数type指出每个元素的数据类型(见表11-2)。
  写入象素数据:

  void glDrawPixels(GLsizesi width,GLsizei height,GLenum format,
           GLenum type,GLvoid *pixel);

  函数参数format和type与glReadPixels()有相同的意义,pixel指向的数组包含所要画的象素数据。注意,调用这个函数前必须先设置当前光栅位置,若当前光栅位置无效,则给出该函数时不画任何图形,并且当前光栅位置仍然保持无效。
名称 象素数据类型
GL_INDEX 单个颜色索引
GL_RGB 先是红色分量,再是绿色分量,然后是蓝色分量
GL_RED 单个红色分量
GL_GREEN 单个绿色分量
GL_BLUE 单个蓝色分量
GL_ALPHA  单个Alpha值
GL_LUMINANCE_ALPHA 先是亮度分量,然后是Alpha值
GL_STENCIL_INDEX 单个的模板索引
GL_DEPTH_COMPONENT 单个深度分量
表11-1 函数glReadPixels()及glDrawPixels()的象素格式

名称 数据类型
GL_UNSIGNED_BYTE  无符号的8位整数
GL_BYTE 8位整数
GL_BITMAP 无符号的8位整数数组中的单个数位
GL_UNSIGNED_SHORT 无符号的16位整数
GL_SHORT 16位整数
GL_UNSIGNED_INT 无符号的32位整数
GL_INT 32位整数
GL_FLOAT  单精度浮点数
表11-2 函数glReadPixels()及glDrawPixels()的象素数据类型


  图像的每个元素按表11-2给出的数据类型存储。若元素表示连续的值,如红、绿、蓝或亮度分量,每个值都按比例放缩使之适合于可用的位数。例如,红色分量是0.0到1.0之间的浮点值。若它需要放到无符号单字节整数中,也仅有8位精度保存下来,其他无符号整数类型同理。对于有符号的数据类型还要少一位,例如颜色索引存到有符号的8位整数中,它的第一位被0xfe屏蔽掉了(即这个掩码包含7个1)。若类型是GL_FLOAT,索引值简单地转化成单精度浮点值,例如索引17转化成17.0,同理。

  11.2.2 象素拷贝
  象素拷贝函数是:

  void glCopyPixels(GLint x,GLint y,GLsizesi width,GLsizei height, GLenum type);

  这个函数使用起来有点类似于先调用glReadPixels()函数后再调用glDrawPixels()一样,但它不需要将数据写到内存中去,因它只将数据写到framebuffer里。函数功能就是拷贝framebuffer中左下角点在(x, y)尺寸为width、height的矩形区域象素数据。数据拷贝到一个新的位置,其左下角点在当前光栅的位置,参数type可以是GL_COLOR、 GL_STENCIL、GL_DEPTH。在拷贝过程中,参数type要按如下方式转换成format:
  1)若type为GL_DEPTH或GL_STENCIL,那么format应分别是GL_DEPTH_COMPONENT或GL_STENCIL_INDEX;
  2)若type为GL_COLOR,format则用GL_RGB或GL_COLOR_INDEX,这要依赖于图形系统是处于RGBA方式还是处于颜色表方式。

  11.2.3 图像缩放
  一般情况下,图像的一个象素写到屏幕上时也是一个象素,但是有时也需要将图像放大或缩小,OpenGL提供了这个函数:

  void glPixelZoom(GLfloat zoomx,GLfloat zoomy);

  设置象素写操作沿X和Y方向的放大或缩小因子。缺省情况下,zoomx、zoomy都是1.0。如果它们都是2.0,则每个图像象素被画到4个屏幕象素上面。注意:小数形式的缩放因子和负数因子都是可以的。

  11.2.4 图像例程
  下面举出一个图像应用的例子:

  例11-2 图像应用例程(image.c)

  #include "glos.h"
  #include <GL/gl.h>
  #include <GL/glu.h>
  #include <GL/glaux.h>

  void myinit(void);
  void triangle(void);
  void SourceImage(void);
  void CALLBACK display(void);
  void CALLBACK myReshape(GLsizei w, GLsizei h);

  void myinit (void)
  {
    glClear (GL_COLOR_BUFFER_BIT);
  }

  void triangle(void)
  {
    glBegin (GL_TRIANGLES);
      glColor3f (0.0, 1.0, 0.0);
       glVertex2f (2.0, 3.0);
      glColor3f(0.0,0.0,1.0);
       glVertex2f (12.0, 3.0);
      glColor3f(1.0,0.0,0.0);
       glVertex2f (7.0, 12.0);
      glEnd ();
  }

  void SourceImage(void)
  {
    glPushMatrix();
      glLoadIdentity();
      glTranslatef(4.0,8.0,0.0);
      glScalef(0.5,0.5,0.5);
      triangle ();
    glPopMatrix();
  }

  void CALLBACK display(void)
  {
    int i;

    
    SourceImage();

    
    for(i=0;i<5;i++)
    {
      glRasterPos2i( 1+i*2,i);
      glCopyPixels(160,310,170,160,GL_COLOR);
    }

    glFlush ();
  }

  void CALLBACK myReshape(GLsizei w, GLsizei h)
  {
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
      gluOrtho2D (0.0, 15.0, 0.0, 15.0 * (GLfloat) h/(GLfloat) w);
    else
      gluOrtho2D (0.0, 15.0 * (GLfloat) w/(GLfloat) h, 0.0, 15.0);
    glMatrixMode(GL_MODELVIEW);
  }

  void main(void)
  {
    auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Pixel Processing");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
  }

  以上程序运行的结果是在屏幕正上方显示一个最初的五彩三角形,然后在下半部显示一串拷贝的三角形。当然,读者自己可以再加上图像放大缩小等,试试看,会发生怎样的情形?

图11-3 图象拷贝

 


十二、纹理映射

12.1 基本步骤
  纹理映射是一个相当复杂的过程,这节只简单地叙述一下最基本的执行纹理映射所需的步骤。基本步骤如下:
  1)定义纹理、2)控制滤波、3)说明映射方式、4)绘制场景,给出顶点的纹理坐标和几何坐标。
  注意:纹理映射只能在RGBA方式下执行,不能运用于颜色表方式。下面举出一个最简单的纹理映射应用例子:

  例12-1 简单纹理映射应用例程(texsmpl.c)

  #include "glos.h"
  #include <GL/gl.h>
  #include <GL/glu.h>
  #include <GL/glaux.h>

  void myinit(void);
  void makeImage(void);
  void CALLBACK myReshape(GLsizei w, GLsizei h);
  void CALLBACK display(void);

  
  #define ImageWidth 64
  #define ImageHeight 64
  GLubyte Image[ImageWidth][ImageHeight][3];

  void makeImage(void)
  {
    int i, j, r,g,b;

    for (i = 0; i < ImageWidth; i++)
    {
      for (j = 0; j < ImageHeight; j++)
      {
        r=(i*j)%255;
        g=(4*i)%255;
        b=(4*j)%255;
        Image[i][j][0] = (GLubyte) r;
        Image[i][j][1] = (GLubyte) g;
        Image[i][j][2] = (GLubyte) b;
      }
    }
  }

  void myinit(void)
  {
    glClearColor (0.0, 0.0, 0.0, 0.0);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);

    makeImage();
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    
    glTexImage2D(GL_TEXTURE_2D, 0, 3, ImageWidth,
      ImageHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, &Image[0][0][0]);

    
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

    
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);

    
    glEnable(GL_TEXTURE_2D);

    glShadeModel(GL_FLAT);
  }

  void CALLBACK display(void)
  {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    
    glBegin(GL_QUADS);
      glTexCoord2f(0.0, 0.0); glVertex3f(-2.0, -1.0, 0.0);
      glTexCoord2f(0.0, 1.0); glVertex3f(-2.0, 1.0, 0.0);
      glTexCoord2f(1.0, 1.0); glVertex3f(0.0, 1.0, 0.0);
      glTexCoord2f(1.0, 0.0); glVertex3f(0.0, -1.0, 0.0);

      glTexCoord2f(0.0, 0.0); glVertex3f(1.0, -1.0, 0.0);
      glTexCoord2f(0.0, 1.0); glVertex3f(1.0, 1.0, 0.0);
      glTexCoord2f(1.0, 1.0); glVertex3f(2.41421, 1.0, -1.41421);
      glTexCoord2f(1.0, 0.0); glVertex3f(2.41421, -1.0, -1.41421);
    glEnd();

    glFlush();
  }

  void CALLBACK myReshape(GLsizei w, GLsizei h)
  {
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60.0, 1.0*(GLfloat)w/(GLfloat)h, 1.0, 30.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0.0, 0.0, -3.6);
  }

  void main(void)
  {
    auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow ("Simple Texture Map");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
  }

图12-1 简单纹理映射


  以上程序运行结果是将一块纹理映射到两个正方形上去。这两个正方形都是按透视投影方式绘制,其中一个正对观察者,另一个向后倾斜45度角。图形的纹理是由函数makeImage()产生的,并且所有纹理映射的初始化工作都在程序myinit()中进行。由glTexImage2d()说明了一个全分辨率的图像,其参数指出了图像的尺寸、图像类型、图像位置和图像的其它特性;下面连续调用函数glTexParameter*()说明纹理怎样缠绕物体和当象素与纹理数组中的单个元素(texel,暂称纹素)不能精确匹配时如何过滤颜色;接着用函数glTexEnv*()设置画图方式为GL_DECAL,即多边形完全用纹理图像中的颜色来画,不考虑多边形在未被纹理映射前本身的颜色;最后,调用函数glEnable()启动纹理映射。子程序display()画了两个正方形,其中纹理坐标与几何坐标一起说明,glTexCoord*() 函数类似于glNormal*()函数,不过它是设置纹理坐标,之后任何顶点都把这个纹理坐标与顶点坐标联系起来,直到再次调用 glTexCoord*()改变当前纹理坐标。

12.2、纹理定义

  12.2.1 二维纹理定义的函数

  void glTexImage2D(GLenum target,GLint level,GLint components,
           GLsizei width, glsizei height,GLint border,
           GLenum format,GLenum type, const GLvoid *pixels);

  定义一个二维纹理映射。其中参数target是常数GL_TEXTURE_2D。参数level表示多级分辨率的纹理图像的级数,若只有一种分辨率,则level设为0。
  参数components是一个从1到4的整数,指出选择了R、G、B、A中的哪些分量用于调整和混合,1表示选择了R分量,2表示选择了R和A两个分量,3表示选择了R、G、B三个分量,4表示选择了R、G、B、A四个分量。
  参数width和height给出了纹理图像的长度和宽度,参数border为纹理边界宽度,它通常为0,width和height必须是2m+2b,这里m是整数,长和宽可以有不同的值,b是border的值。纹理映射的最大尺寸依赖于OpenGL,但它至少必须是使用64x64(若带边界为 66x66),若width和height设置为0,则纹理映射有效地关闭。
  参数format和type描述了纹理映射的格式和数据类型,它们在这里的意义与在函数glDrawPixels()中的意义相同,事实上,纹理数据与glDrawPixels()所用的数据有同样的格式。参数 format可以是GL_COLOR_INDEX、GL_RGB、GL_RGBA、GL_RED、GL_GREEN、GL_BLUE、GL_ALPHA、 GL_LUMINANCE或GL_LUMINANCE_ALPHA(注意:不能用GL_STENCIL_INDEX和 GL_DEPTH_COMPONENT)。类似地,参数type是GL_BYPE、GL_UNSIGNED_BYTE、GL_SHORT、 GL_UNSIGNED_SHORT、GL_INT、GL_UNSIGNED_INT、GL_FLOAT或GL_BITMAP。
  参数pixels包含了纹理图像数据,这个数据描述了纹理图像本身和它的边界。

  12.2 一维纹理定义的函数

  void glTexImage1D(GLenum target,GLint level,GLint components,GLsizei width,
           GLint border,GLenum format,GLenum type,const GLvoid *pixels);

  定义一个一维纹理映射。除了第一个参数target应设置为GL_TEXTURE_1D外,其余所有的参数与函数TexImage2D()的一致,不过纹理图像是一维纹素数组,其宽度值必须是2的幂,若有边界则为2m+2。

12.3、纹理控制
  OpenGL中的纹理控制函数是

  void glTexParameter{if}[v](GLenum target,GLenum pname,TYPE param);

  控制纹素映射到片元(fragment)时怎样对待纹理。第一个参数target可以是GL_TEXTURE_1D或GL_TEXTURE_2D,它指出是为一维或二维纹理说明参数;后两个参数的可能值见表12-1所示。
参数 值
GL_TEXTURE_WRAP_S GL_CLAMP
GL_REPEAT
GL_TEXTURE_WRAP_T GL_CLAMP
GL_REPEAT
GL_TEXTURE_MAG_FILTER  GL_NEAREST
GL_LINEAR
GL_TEXTURE_MIN_FILTER  GL_NEAREST
GL_LINEAR
GL_NEAREST_MIPMAP_NEAREST
GL_NEAREST_MIPMAP_LINEAR
GL_LINEAR_MIPMAP_NEAREST
GL_LINEAR_MIPMAP_LINEAR
表12-1 放大和缩小滤波方式


  12.3.1 滤波
  一般来说,纹理图像为正方形或长方形。但当它映射到一个多边形或曲面上并变换到屏幕坐标时,纹理的单个纹素很少对应于屏幕图像上的象素。根据所用变换和所用纹理映射,屏幕上单个象素可以对应于一个纹素的一小部分(即放大)或一大批纹素(即缩小)。下面用函数glTexParameter*()说明放大和缩小的方法:

  glTexParameter*(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
  glTexParameter*(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);

  实际上,第一个参数可以是GL_TEXTURE_1D或GL_TEXTURE_2D,即表明所用的纹理是一维的还是二维的;第二个参数指定滤波方法,其中参数值GL_TEXTURE_MAG_FILTER指定为放大滤波方法,GL_TEXTURE_MIN_FILTER指定为缩小滤波方法;第三个参数说明滤波方式,其值见表12-1所示。
  若选择GL_NEAREST则采用坐标最靠近象素中心的纹素,这有可能使图像走样;若选择 GL_LINEAR则采用最靠近象素中心的四个象素的加权平均值。GL_NEAREST所需计算比GL_LINEAR要少,因而执行得更快,但 GL_LINEAR提供了比较光滑的效果。

  12.3.2 重复与约简
  纹理坐标可以超出(0, 1)范围,并且在纹理映射过程中可以重复映射或约简映射。在重复映射的情况下,纹理可以在s,t方向上重复,即:

  glTexParameterfv(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
  glTexParameterfv(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);

  若将参数GL_REPEAT改为GL_CLAMP,则所有大于1的纹素值都置为1,所有小于0的值都置为0。参数设置参见表12-1。

12.4、映射方式
  在本章的第一个例程中,纹理图像是直接作为画到多边形上的颜色。实际上,可以用纹理中的值来调整多边形(曲面)原来的颜色,或用纹理图像中的颜色与多边形(曲面)原来的颜色进行混合。因此,OpenGL提供了三种纹理映射的方式,这个函数是:

  void glTexEnv{if}[v](GLenum target,GLenum pname,TYPE param);

  设置纹理映射方式。参数target必须是GL_TEXTURE_ENV;若参数pname是GL_TEXTURE_ENV_MODE,则参数 param可以是GL_DECAL、GL_MODULATE或GL_BLEND,以说明纹理值怎样与原来表面颜色的处理方式;若参数pname是 GL_TEXTURE_ENV_COLOR,则参数param是包含四个浮点数(分别是R、G、B、A分量)的数组,这些值只在采用GL_BLEND纹理函数时才有用。

12.5、纹理坐标

  12.5.1 坐标定义
  在绘制纹理映射场景时,不仅要给每个顶点定义几何坐标,而且也要定义纹理坐标。经过多种变换后,几何坐标决定顶点在屏幕上绘制的位置,而纹理坐标决定纹理图像中的哪一个纹素赋予该顶点。并且顶点之间的纹理坐标插值与基础篇中所讲的平滑着色插值方法相同。
   纹理图像是方形数组,纹理坐标通常可定义成一、二、三或四维形式,称为s,t,r和q坐标,以区别于物体坐标(x, y, z, w)和其他坐标。一维纹理常用s坐标表示,二维纹理常用(s, t)坐标表示,目前忽略r坐标,q坐标象w一样,一半值为1,主要用于建立齐次坐标。OpenGL坐标定义的函数是:

  void gltexCoord{1234}{sifd}[v](TYPE coords);

  设置当前纹理坐标,此后调用glVertex*()所产生的顶点都赋予当前的纹理坐标。对于gltexCoord1*(),s坐标被设置成给定值,t和 r设置为0,q设置为1;用gltexCoord2*()可以设置s和t坐标值,r设置为0,q设置为1;对于gltexCoord3*(),q设置为 1,其它坐标按给定值设置;用gltexCoord4*()可以给定所有的坐标。使用适当的后缀(s,i,f或d)和TYPE的相应值(GLshort、 GLint、glfloat或GLdouble)来说明坐标的类型。注意:整型纹理坐标可以直接应用,而不是象普通坐标那样被映射到[-1, 1]之间。

  12.5.2 坐标自动产生
  在某些场合(环境映射等)下,为获得特殊效果需要自动产生纹理坐标,并不要求为用函数gltexCoord*()为每个物体顶点赋予纹理坐标值。OpenGL提供了自动产生纹理坐标的函数,其如下:

  void glTexGen{if}[v](GLenum coord,GLenum pname,TYPE param);

  自动产生纹理坐标。第一个参数必须是GL_S、GL_T、GL_R或GL_Q,它指出纹理坐标s,t,r,q中的哪一个要自动产生;第二个参数值为 GL_TEXTURE_GEN_MODE、GL_OBJECT_PLANE或 GL_EYE_PLANE;第三个参数param是一个定义纹理产生参数的指针,其值取决于第二个参数pname的设置,当pname为 GL_TEXTURE_GEN_MODE时,param是一个常量,即GL_OBJECT_LINEAR、GL_EYE_LINEAR或 GL_SPHERE_MAP,它们决定用哪一个函数来产生纹理坐标。对于pname的其它可能值,param是一个指向参数数组的指针。下面是一个运用自动产生纹理坐标函数的实例:

  例12-1 纹理坐标自动产生例程(texpot.c)

  #include "glos.h"
  #include <GL/gl.h>
  #include <GL/glu.h>
  #include <GL/glaux.h>

  void myinit(void);
  void makeStripeImage(void);
  void CALLBACK display(void);
  void CALLBACK myReshape(GLsizei w, GLsizei h);

  #define stripeImageWidth 64
  GLubyte stripeImage[3*stripeImageWidth];

  void makeStripeImage(void)
  {
    int j;

    for (j = 0; j < stripeImageWidth; j++)
    {
      stripeImage[3*j] = 255;
      stripeImage[3*j+1] =255-2*j;
      stripeImage[3*j+2] =255;
    }
  }

 

  GLfloat sgenparams[] = {1.0, 1.0, 1.0, 0.0};

  void myinit(void)
  {
    glClearColor (0.0, 0.0, 0.0, 0.0);

    makeStripeImage();
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    glTexParameterf(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameterf(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexImage1D(GL_TEXTURE_1D, 0, 3, stripeImageWidth, 0, GL_RGB, GL_UNSIGNED_BYTE, stripeImage);

    glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
    glTexGenfv(GL_S, GL_OBJECT_PLANE, sgenparams);

    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
    glEnable(GL_TEXTURE_GEN_S);
    glEnable(GL_TEXTURE_1D);
    glEnable(GL_CULL_FACE);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_AUTO_NORMAL);
    glEnable(GL_NORMALIZE);
    glFrontFace(GL_CW);
    glCullFace(GL_BACK);
    glMaterialf (GL_FRONT, GL_SHININESS, 64.0);
  }

  void CALLBACK display(void)
  {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glPushMatrix ();
    glRotatef(25.0, 1.0, 0.0, 0.0);
    auxSolidTeapot(1.5);
    glPopMatrix ();
    glFlush();
  }

  void CALLBACK myReshape(GLsizei w, GLsizei h)
  {
    float a=3.5;

    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
      glOrtho (-a, a, -a*(GLfloat)h/(GLfloat)w, a*(GLfloat)h/(GLfloat)w, -a, a);
    else
      glOrtho (-a*(GLfloat)w/(GLfloat)h, a*(GLfloat)w/(GLfloat)h, -a, a, -a, a);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
  }

  void main(void)
  {
    auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);
    auxInitPosition (0, 0, 500, 500);
    auxInitWindow (" Teapot TextureMapping");
    myinit();
    auxReshapeFunc (myReshape);
    auxMainLoop(display);
  }

  以上程序运行结果是在屏幕上显示一个带条状纹理的茶壶。其中用到了前面所讲的一维纹理映射定义,以及本节的纹理坐标自动产生。

图12-2 贴纹理的茶壶

 


十三、复杂物体建模

13.1 图元扩展

  13.1.1 点和线
  下面分别介绍点和线的扩展形式及用法。
  1)点。OpenGL中定义的点可以有不同的尺寸,其函数形式为:

  void glPointSize(GLfloat size);

  设置点的宽度(以象素为单位)。参数size必须大于0.0,缺省时为1.0。

  2)线。OpenGL能指定线的各种宽度和绘制不同的虚点线,如点线、虚线等。相应的函数形式如下:

  void glLineWidth(GLfloat width);

  设置线宽(以象素为单位)。参数width必须大于0.0,缺省时为1.0。

  void glLineStipple(GLint factor,GLushort pattern);

  设置线为当前的虚点模式。参数pattern是一系列的16位数(0或1),它重复地赋给所指定的线。其中每一位代表一个象素,且从低位开始,1表示用当前颜色绘制一个象素(或比例因子指定的个数),0表示当前不绘制,只移动一个象素位(或比例因子指定的个数)。参数factor是个比例因子,它用来拉伸pattern中的元素,即重复绘制1或移动0,比如,factor为2,则碰到1时就连续绘制2次,碰到0时连续移动2个单元。factor的大小范围限制在1到255之间。在绘制虚点线之前必须先启动一下,即调用函数glEnable(GL_LINE_STIPPLE);若不用,则调用 glDisable(GL_LINE_STIPPLE)关闭。下面举出一个点线扩展应用实例:

  例13-1 点线扩展应用例程(expntlin.c)

  #include "glos.h"
  #include <GL/gl.h>
  #include <GL/glu.h>
  #include <GL/glaux.h>

  void myinit(void);
  void line2i(GLint x1,GLint y1,GLint x2,GLint y2);
  void CALLBACK display(void); void myinit (void)
  {
    glClearColor (0 , 0.0, 0.0, 0.0);
    glShadeModel (GL_FLAT);
  }

  void line2i(GLint x1,GLint y1,GLint x2,GLint y2)
  {
    glBegin(GL_LINES);
    glVertex2f(x1,y1);
    glVertex2f(x2,y2);
    glEnd();
  }

  void CALLBACK display(void)
  {
    int i;

    glClear (GL_COLOR_BUFFER_BIT);

    
    glColor3f(0.8,0.6,0.4);
    for (i = 1; i <= 10; i++)
    {
      glPointSize(i*2);
      glBegin (GL_POINTS);
        glVertex2f (30.0 + ((GLfloat) i * 50.0), 330.0);
      glEnd ();
    }

 

    glEnable (GL_LINE_STIPPLE);

    glLineStipple (1, 0x0101); 
    glColor3f(1.0 ,0.0,0.0);
    line2i (50, 250, 200, 250);

    glLineStipple (1, 0x00FF); 
    glColor3f(1.0,1.0,0.0);
    line2i (250 , 250 , 400, 250 );

    glLineStipple (1, 0x1C47); 
    glColor3f(0.0,1.0,0.0);
    line2i (450 , 250 , 600 , 250 );

 

    glLineWidth (5.0);
    glLineStipple (1, 0x0101);
    glColor3f(1.0 ,0.0,0.0);
    line2i (50 , 200 , 200 , 200 );

    glLineWidth (3.0);
    glLineStipple (1, 0x00FF);
    glColor3f(1.0 ,1.0,0.0);
    line2i (250 , 200 , 400 , 200 );

    glLineWidth (2.0);
    glLineStipple (1, 0x1C47);
    glColor3f(0.0 ,1.0,0.0);
    line2i (450 , 200 , 600 , 200 );

    
    glLineWidth(1);

 

    glLineStipple (1, 0xff0c);
    glBegin (GL_LINE_STRIP);
      glColor3f(0.0 ,1.0,1.0);
      for (i = 0; i < 12; i++)
        glVertex2f (50.0 + ((GLfloat) i * 50.0), 150.0);
      glEnd ();

 

    glColor3f(0.4 ,0.3,0.8);
    for (i = 0; i < 10; i++)
    {
      line2i (50 + ( i * 50), 70, 75 + ((i+1) * 50), 100);
    }

 

    glLineStipple (5, 0x1C47);
    glColor3f(1.0 ,0.0,1.0);
    line2i (50 , 25 , 600 , 25 );

    glFlush ();
  }

  void main(void)
  {
    auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);
    auxInitPosition (0, 0, 650, 450);
    auxInitWindow ("External Points and Lines");
    myinit ();
    auxMainLoop(display);
  }

  以上程序运行结果是显示不同尺寸的点及不同线型和宽度的线的绘制方式。

图13-1 扩展点线


  13.1.2 多边形
  多边形的绘制模式包含有好几种:全填充式、轮廓点式、轮廓线式以及图案填充式。下面分别介绍相应的OpenGL函数形式。
  1)多边形模式设置。其函数为:

  void glPolygonMode(GLenum face,GLenum mode);

  控制多边形指定面的绘制模式。参数face为GL_FRONT、GL_BACK或GL_FRONT_AND_BACK;参数mode为 GL_POINT、GL_LINE或GL_FILL,分别表示绘制轮廓点式多边形、轮廓线式多边形或全填充式多边形。缺省时,绘制的是正反面全填充式多边形。

  2)设置图案填充式多边形。其函数为:

  void glPolygonStipple(const GLubyte *mask);

  为当前多边形定义填充图案模式。参数mask是一个指向32x32位图的指针。与虚点线绘制的道理一样,某位为1时绘制,为0时什么也不绘。注意,在调用这个函数前,必须先启动一下,即用glEnable(GL_POLYGON_STIPPLE);不用时用 glDisable(GL_POLYGON_STIPPLE)关闭。下面举出一个多边形扩展绘制实例:

  例 13-2 多边形图案填充例程(polystpl.c)

  #include "glos.h"
  #include <GL/gl.h>
  #include <GL/glu.h>
  #include <GL/glaux.h>

  void myinit(void);
  void CALLBACK display(void);

  void myinit (void)
  {
    glClearColor (0.0, 0.0, 0.0, 0.0);
    glShadeModel (GL_FLAT);
  }

  void CALLBACK display(void)
  {

    
    GLubyte pattern[]= {
      0x00, 0x01, 0x80, 0x00,
      0x00, 0x03, 0xc0, 0x00,
      0x00, 0x07, 0xe0, 0x00,
      0x00, 0x0f, 0xf0, 0x00,
      0x00, 0x1f, 0xf8, 0x00,
      0x00, 0x3f, 0xfc, 0x00,
      0x00, 0x7f, 0xfe, 0x00,
      0x00, 0xff, 0xff, 0x00,
      0x01, 0xff, 0xff, 0x80,
      0x03, 0xff, 0xff, 0xc0,
      0x07, 0xff, 0xff, 0xe0,
      0x0f, 0xff, 0xff, 0xf0,
      0x1f, 0xff, 0xff, 0xf8,
      0x3f, 0xff, 0xff, 0xfc,
      0x7f, 0xff, 0xff, 0xfe,
      0xff, 0xff, 0xff, 0xff,
      0xff, 0xff, 0xff, 0xff,
      0x7f, 0xff, 0xff, 0xfe,
      0x3f, 0xff, 0xff, 0xfc,
      0x1f, 0xff, 0xff, 0xf8,
      0x0f, 0xff, 0xff, 0xf0,
      0x07, 0xff, 0xff, 0xe0,
      0x03, 0xff, 0xff, 0xc0,
      0x01, 0xff, 0xff, 0x80,
      0x00, 0xff, 0xff, 0x00,
      0x00, 0x7f, 0xfe, 0x00,
      0x00, 0x3f, 0xfc, 0x00,
      0x00, 0x1f, 0xf8, 0x00,
      0x00, 0x0f, 0xf0, 0x00,
      0x00, 0x07, 0xe0, 0x00,
      0x00, 0x03, 0xc0, 0x00,
      0x00, 0x01, 0x80, 0x00
    };

    glClear (GL_COLOR_BUFFER_BIT);

 

    glColor3f(0.1,0.8,0.7);
    glEnable (GL_POLYGON_STIPPLE);
    glPolygonStipple (pattern);
    glRectf (48.0, 80.0, 210.0, 305.0);

    
    glColor3f(0.9,0.86,0.4);
    glPolygonStipple (pattern);
    glBegin(GL_TRIANGLES);
      glVertex2i(310,310);
      glVertex2i(220,80);
      glVertex2i(405,80);
    glEnd();

    glDisable (GL_POLYGON_STIPPLE);

    glFlush ();
  }

  void main(void)
  {
    auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);
    auxInitPosition (0, 0, 500, 400);
    auxInitWindow ("Polygon Stippling");
    myinit ();
    auxMainLoop(display);
  }

图13-2 图案填充多边形


13.2、法向计算
  法向,又称法向量(Mormal Vector)。对于一个平面,其上各点的法向的一样,统一为这个平面的法向,所以称为平面法向。对于一个曲面,虽然它在计算机图形中是由许多片小的平面多边形逼近,但是每个顶点的法向都不一样,因此曲面上每个点的法向计算就可以根据不同的应用有不同的算法,则最后效果也不相同。OpenGL有很大的灵活性,它只提供赋予当前顶点法向的函数,并不在内部具体计算其法向量,这个值由编程者自己根据需要计算。下面介绍一下法向基本计算方法和OpenGL法向定义。

  13.2.1 法向基本计算方法
  首先,讲述平面法向的计算方法。在一个平面内,有两条相交的线段,假设其中一条为矢量W,另一条为矢量V,且平面法向为N,如图13-3所示,则平面法向就等于两个矢量的叉积(遵循右手定则),即N=WxV。

图13-3 平面法向计算


  比如计算一个三角形平面的法向,就可以用它的三个顶点来计算,如图13-4所示。

图13-4 三角形平面法向计算


  设三个顶点分别为P0、P1、P2,相应两个向量为W、V,则三角平面法向的计算方式见下列一段代码:

  
  void getNormal(GLfloat gx[3],GLfloat gy[3],GLfloat gz[3],GLfloat *ddnv)
  {
    GLfloat w0,w1,w2,v0,v1,v2,nr,nx,ny,nz;

    w0=gx[0]-gx[1]; w1=gy[0]-gy[1]; w2=gz[0]-gz[1];
    v0=gx[2]-gx[1]; v1=gy[2]-gy[1]; v2=gz[2]-gz[1];
    nx=(w1*v2-w2*v1);
    ny=(w2*v0-w0*v2);
    nz=(w0*v1-w1*v0);
    nr=sqrt(nx*nx+ny*ny+nz*nz);
    ddnv[0]=nx/nr; ddnv[1]=ny/nr; ddnv[2]=nz/nr;
  }

  以上函数的输出参数为指针ddnv,它指向法向的三个分量,并且程序中已经将法向单位化(或归一化)了。
  此外,对于曲面各顶点的法向计算有很多种,最常用的是平均平面法向法,如图15-5 所示。在图中,曲面顶点P的法向就等于其相邻的四个平面的法向平均值,即:
Np = (N1+N2+N3+N4)/4


 原文地址 http://blog.csdn.net/StFairy/category/276434.aspx?PageNumber=1

http://blog.chinaunix.net/u2/74194/showart_1108619.html