OpenGL颜色

来源:互联网 发布:pdf.js 禁止下载 编辑:程序博客网 时间:2024/04/30 08:21

几乎所有OpenGL应用目的都是在屏幕窗口内绘制彩色图形,所以颜色在OpenGL编程中占有很重要的地位。这里的颜色与绘画中的颜色概念不一样,它属于RGB颜色空间,只在监视器屏幕上显示。另外,屏幕窗口坐标是以象素为单位,因此组成图形的每个象素都有自己 的颜色,而这种颜色值是通过对一系列OpenGL函数命令的处理最终计算出来的。本章将讲述计算机颜色的概念以及OpenGL的颜色模式、颜色定义和两种模式应用场合等内容,若掌握好颜色的应用,你就能走进缤纷绚丽的色彩世界,从中享受无穷的乐趣。

9.1、计算机颜色

  9.1.1 颜色生成原理
  计算机颜色不同于绘画或印刷中的颜色,显示于计算机屏幕上每一个点的颜色都是由监视器内部的电子枪激发的三束不同颜色的光(红、绿、蓝)混合而成,因此,计算机颜色通 常用R(Red)、G(Green)、B(Blue)三个值来表示,这三个值又称为颜色分量。颜色生成原理 示意图见图9-1所示。

 
图9-1 计算机颜色生成原理

  9.1.2 RGB色立体(RGB Color Cube)
  所有监视器屏幕的颜色都属于RGB颜色空间,如果用一个立方体形象地表示RGB颜色组成关系,那么就称这个立方体为RGB色立体,如图9-2所示。

 
图9-2 RGB色立体

  在图中,R、G、B三值的范围都是从0.0到1.0。如果某颜色分量越大,则表示对应的颜色分量越亮,也就是它在此点所贡献的颜色成分越多;反之,则越暗或越少。当R、G、B三个值都为0.0时,此点颜色为黑色(Black);当三者都为1.0时,此点颜色为白色(White);当三个颜色分量值相等时,表示三者贡献一样,因此呈现灰色(Grey),在图中表现为从黑色顶点到白色顶点的那条对角线;当R=1.0、G=1.0、B=0.0时,此点颜色为黄色(Yellow);同理,R=1.0、G=0.0、B=1.0时为洋红色,也叫品色(Magenta);R=0.0、G=1.0、B=1.0时为青色(Cyan)。

9.2、颜色模式
  OpenGL颜色模式一共有两个:RGB(RGBA)模式和颜色表模式。在RGB模式下,所有的颜色定义全用R、G、B三个值来表示,有时也加上Alpha值(与透明度有关),即RGBA模式。在颜色表模式下,每一个象素的颜色是用颜色表中的某个颜色索引值表示,而这个索引值指向了相应的R、G、B值。这样的一个表成为颜色映射(Color Map)。

  9.2.1 RGBA模式(RGBA Mode)
  在RGBA模式下,可以用glColor*()来定义当前颜色。其函数形式为:

  void glColor3{b s i f d ub us ui}(TYPE r,TYPE g,TYPE b);
  void glColor4{b s i f d ub us ui}(TYPE r,TYPE g,TYPE b,TYPE a);
  void glColor3{b s i f d ub us ui}v(TYPE *v);
  void glColor4{b s i f d ub us ui}v(TYPE *v);

  设置当前R、G、B和A值。这个函数有3和4两种方式,在前一种方式下,a值缺省为1.0,后一种Alpha值由用户自己设定,范围从0.0到1.0。同样,它也可用指针传递参数。另外,函数的第二个后缀的不同使用,其相应的参数值及范围不同,见下表9-1所示。虽然这些参数值不同,但实际上OpenGL已自动将它们映射在0.0到1.0或-1.0或范围之内。因此,灵活使用这些后缀,会给你编程带来很大的方便。

后缀 数据类型 最小值 最小值映射 最大值 最大值映射 
b 1字节整型数 -128 -1.0 127 1.0 
s 2字节整型数 -32,768 -1.0 32,767 1.0 
i 4字节整型数 -2,147,483,648 -1.0 2,147,483,647 1.0 
ub 1字节无符号整型数 0 0.0 255 1.0 
us 2字节无符号整型数 0 0.0 65,535 1.0 
ui 4字节无符号整型数 0 0.0 4,294,967,295 1.0 
表9-1 整型颜色值到浮点数的转换

  9.2.2 颜色表模式(Color_Index Mode)
  在颜色表方式下,可以调用glIndex*()函数从颜色表中选取当前颜色。其函数形式为:

  void glIndex{sifd}(TYPE c);
  void glIndex{sifd}v(TYPE *c);

  设置当前颜色索引值,即调色板号。若值大于颜色位面数时则取模。

  9.2.3 两种模式应用场合
  在大多情况下,采用RGBA模式比颜色表模式的要多,尤其许多效果处理,如阴影、光照、雾、反走样、混合等,采用RGBA模式效果会更好些;另外,纹理映射只能在RGBA模式下进行。下面提供几种运用颜色表模式的情况(仅供参考):
  1)若原来应用程序采用的是颜色表模式则转到OpenGL上来时最好仍保持这种模式,便于移植。
  2)若所用颜色不在缺省提供的颜色许可范围之内,则采用颜色表模式。
  3)在其它许多特殊处理,如颜色动画,采用这种模式会出现奇异的效果。

9.3、颜色应用举例
  颜色是一个极具吸引力的应用,在前面几章中已经逐步介绍了RGBA模式的应用方式,这里就不再多述。下面着重说一下颜色表模式的应用方法,请看例程:

  例9-1 颜色表应用例程(cindex.c)

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

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

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

  void InitPalette(void)
  {
    GLint j;
    static GLfloat rgb[][3]={
      {1.0,0.0,0.0},{1.0,0.0,0.5},{1.0,0.0,1.0},{0.0,0.0,1.0},
      {0.0,1.0,1.0},{0.0,1.0,0.0},{1.0,1.0,0.0},{1.0,0.5,0.0}};

    for(j=0;j<8;j++)
      auxSetOneColor(j+1,rgb[j][0],rgb[j][1],rgb[j][2]);
  }

  void CALLBACK myReshape(GLsizei w,GLsizei h)
  {
    glViewport(0,0,w,h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if(w<=h)
      glOrtho(-12.0,12.0,-12.0*(GLfloat)h/(GLfloat)w, 12.0*(GLfloat)h/(GLfloat)w,-30.0,30.0);
    else
      glOrtho(-12.0*(GLfloat)h/(GLfloat)w, 12.0*(GLfloat)h/(GLfloat)w,-12.0,12.0,-30.0,30.0); 
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
  }

  void CALLBACK display(void)
  {
    InitPalette();
    DrawColorFans();
    glFlush();
  }

  void DrawColorFans(void)
  {
    GLint n;
    GLfloat pp[8][2]={
      {7.0,-7.0},{0.0,-10.0},{-7.0,-7.0},{-10.0,0.0},
      {-7.0,7.0}, {0.0,10.0},{7.0,7.0},{10.0,0.0}};

    /* draw some filled_fan_triangles */
    glBegin(GL_TRIANGLE_FAN);
      glVertex2f(0.0,0.0);
      glVertex2f(10.0,0.0);
      for(n=0;n<8;n++)
      {
        glIndexi(n+1);
        glVertex2fv(pp[n]);
      }
    glEnd();
  }

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

  这个程序运行结果是在屏幕上显示八个连成扇形的不同颜色的三角形,每个三角形的颜色定义采用颜色表模式。其中,调用了辅助库函数auxSetOneColor()来装载颜色映射表,即调色板。因为将某个颜色装载到颜色查找表(color lookup table)中的过程必须依赖窗口系统,而OpenGL函数与窗口系统无关,所以这里就调用辅助库的函数来完成这个过程,然后才调用OpenGL自己的函数glIndex()设置当前的颜色号。

 


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/StFairy/archive/2007/07/15/1691800.aspx

posted @ 2010-11-19 16:03 白了少年头 Views(363) Comments(0) Edit
OpenGL帧缓冲区

在OpenGL窗口中, 左下角的像素为(0, 0). 一般而言, 像素(x, y)占据的矩形区域左下角为(x, y), 右上角为(x+1, y+1).

10.1 缓存及其用途


1) 颜色缓存,  左前,右前,左后,右后和任意数量的辅助颜色缓存;
2) 深度缓存
3) 模板缓存
4) 累积缓存

注意:X窗口系统,RGBA模式至少保证1个颜色缓冲区,模板缓冲区,深度缓冲区,累计缓冲区
颜色索引模式至少保证1个眼色缓冲区,深度缓冲区,模板缓冲区.
可以使用glXGetConfig{}函数查询.

用glGetIntegerv()查询每个像素占据的缓存空间的参数
GL_RED_BITS, GL_GREEN_BITS, GL_BLUE_BITS, GL_ALPHA_BITS --- 颜色缓存中R, G, B, A分量的位数
GL_INDEX_BITS --- 颜色缓存中每个颜色索引的位数
GL_DEPTH_BITS --- 深度缓存中每个像素的位数
GL_STENCIL_BITS --- 模板缓存中每个像素的位数
GL_ACCUM_RED_BITS, GL_ACCUM_GREEN_BITS, GL_ACCUM_BLUE_BITS, GL_ACCUM_ALPHA_BITS --- 累积缓存中R, G, B, A分量的位数.

10.1.1 颜色缓存


1) 颜色缓存存储了颜色索引或RGB颜色数据, 还可能存储了alpha值. 
2) 支持立体观察(stereoscopic viewing)的OpenGL实现有左颜色缓存和右颜色缓存. 它们分别用于左立体图像和右立体图像.
3) 如不支持立体观察, 则只使用左颜色缓存.
4) 双颜色缓存有前缓存和后缓存, 单缓存系统只有前缓存.
5) 支持不可显示的辅助颜色缓存
6) 函数glGetBooleanv()查询是否支持立体观察和双缓存: GL_STEREO和GL_DOUBLE_BUFFER. 
   函数glGetIntegerv()查询多少个辅助缓存可用: GL_AUX_BUFFERES.

10.1.2 深度缓存


深度缓存 --- 存储了每个像素的深度值. 通常是离视点的距离, 因此深度值大的像素会被深度值小的像素覆盖.

10.1.3 模板缓存


用途之一: 绘图范围限制在屏幕的特定区域内.

10.1.4 累积缓存


累积缓存也存储RGBA颜色数据, 将一系列的图像合成一幅图像. 
可以对图像进行超量采样, 然后对样本进行平均, 并将结果写入颜色缓存中, 从而实现场景反走样. 不能将数据直接写入累积缓存.
累加操作总是针对一个矩形块的, 通常是将数据移入或移出颜色缓存.

10.1.5 清空缓存


void glClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha);
void glClearIndex(GLfloat index);
void glClearDepth(GLclampd depth);
void glClearStencil(GLint s);
void glClearAccum(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);
功能: 指定用于清除颜色缓存(RGBA模式或颜色索引模式), 深度缓存, 模板缓存和累积缓存的值. 
类型为GLclampf和GLclampd的参数值被截取到[0.0, 1.0]. 深度缓存的默认清除值为1.0, 其他缓存为0.0.

设置清除值后, 便可以调用函数glClear()来清除缓存.
void glClear(GLbitfield mask);
功能: 清除指定的缓存. 
mask:GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT, GL_STENCIL_BUFFER_BIT, GL_ACCUM_BUFFER_BIT的逻辑或(OR).
清除颜色缓存时, 如果启用了像素所有权测试, 裁剪测试和抖动操作, 它们都会在清除操作中执行. 
屏蔽操作(glColorMask()和glIndexMask())也会生效.alpha测试, 模版测试, 深度测试并不会影响glClear()函数的操作.

10.1.6 指定要读写的颜色缓存


指定要写入的缓存 glDrawBuffer();
指定要读取的缓存 glReadBuffer();

使用双缓存, 通常只绘制后缓存, 并在绘制完成后交换缓存. 你可能想将双缓存窗口视为单缓存窗口: 通过调用函数glDrawBuffer()使得可以同时绘制前缓存和后缓存.
void glDrawBuffer(GLenum mode); 
功能: 指定要写入或消除的颜色缓存以及禁用之前被启用的颜色缓存. 可以一次性启用多个缓存.
GL_FRONT: 单缓存的默认值    
GL_FRONT_RIGHT:    
GL_NONE:
GL_FRONT_LEFT:
GL_FRONT_AND_BACK:
GL_RIGHT:
GL_AUXi: i表示第几个辅助缓存.
GL_LEFT:
GL_BACK_RIGHT:
GL_BACK: 双缓存的默认值
GL_BACK_LEFT:
注意: 启用多个缓存用于写操作时, 只要其中一个缓存存在, 就不会发生错误. 如果指定的缓存都不存在, 就发生错误.

void glReadBuffer(GLenum mode); 
功能: 选择接下来的函数调用glReadPixels(), glCopyPixels(), glCopyTexImage*(), glCopyTexSubImage*() 和 glCopyConvolutionFilter*()将读取的缓存.
      并启用以前被函数glReadBuffer()启用的缓存.
参数mode取值:
GL_FRONT: 单缓存默认
GL_FRONT_RIGHT:
GL_BACK_RIGHT:
GL_FRONT_LEFT:
GL_LEFT:
GL_AUX:
GL_BACK_LEFT:
GL_BACK: 双缓存默认
GL_RIGHT:
注意: 启用缓存用于读取操作时, 指定的缓存必须存在, 否则将发生错误.

10.1.7 屏蔽缓存


void glIndexMask(GLuint mask);
void glColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha);
void glDepthMask(GLboolean flag);
void glStencilMask(GLuint mask);
 
功能: 设置掩码, 用于控制写入到指定缓存中的数据,
glIndexMask: 只用于颜色索引模式中, 掩码中1对应的数据位被写入颜色索引缓存中. 0对应的位不写入.
glColorMask: 只影响RGBA模式下的写入, red, green, blue, alpha决定是否写入相应的分量, GL_TRUE时写入.
glDepthMask(): 如果参数flag的值为GL_TRUE, 则启用深度缓存用于写入, 否则禁用深度缓存.
glStencilMask(): 参数mask的含义与函数glIndexMask()中相同. 
所有GLboolean参数的默认值是GL_TRUE, 两个GLuint参数的默认值都是1.

禁用深度缓存: 如果背景很复杂, 则在背景绘制之后, 禁用深度缓存. 绘制新增的物体, 只要不相互重叠.. 下一帧时, 只需恢复树木图像, 无需恢复深度缓存中的值.
这种方法很有效.

模板缓存的屏蔽操作让你能够使用一个多位模板缓存来存储多个模板(每位一个)
函数glStencilMask()指定的掩码用于控制哪些模板位面可写, 与函数glStencileFunc()的第三个参数指定的掩码无关, 后者指定模板函数将考虑哪些位面.

10.2 片段测试和操作


测试顺序:
1. 裁剪测试 
2. alpha测试 
3. 模版测试
4. 深度测试
5. 混合
6. 抖动
7. 逻辑操作

10.2.1 裁剪测试


void glScissor(GLint x, GLint y, GLsizei width, GLsizei height);
设置裁剪矩形的位置和大小. 需要启用GL_SCISSOR_TEST.

10.2.2 alpha测试


需要启用GL_ALPHA_TEST
void glAlphaFunc(GLenum func, GLclampf ref); 
设置用于alpha测试的参考值和比较函数.
alpha测试可用于透明算法和贴花.

10.2.3 模板测试


void glStencilFunc(GLenum func, GLint ref, GLuint mask);
void glStencilFuncSeparate(GLenum face, GLenum func, GLint ref, GLuint mask);
 
设置模板测试所使用的比较函数(func),参考值(ref),掩码(mask)

void glStencilOp(GLenum fail, GLenum zfail, GLenum zpass);
void glStencilOpSeparate(GLenum face, GLenum fail, GLenum zfail, GLenum zpass);
指定当一个片段通过或未通过模板测试时, 模板缓冲区中的数据如何进行修改.

下图设置模板为中间的方块

11

模板查询glGetInteger()可使用的参数:
GL_STENCIL_FUNC
GL_STENCIL_REF
GL_STENCIL_VALUE_MASK
GL_STENCIL_FAIL
GL_STENCIL_PASS_DEPTH_FAIL
GL_STENCIL_PASS_DEPTH_PASS

 

10.2.4 深度测试


void glDepthFunc(GLenum func); 
为深度测试设置比较函数.

10.2.5 遮挡测试


遮挡测试允许我们判断一组几何图形在进行深度测试之后是否可见.
步骤:
1. 为每个所需的遮挡查询生成一个查询ID
2. 调用glBeginQuery(), 表示开始一个遮挡查询
3. 渲染需要进行遮挡查询的几何图形
4. 调用glEndQuery(), 表示已经完成了遮挡查询
5. 提取通过遮挡查询的样本数量.

生成查询对象
void glGenQueries(GLsizei n, GLuint* ids);

对遮挡查询对象进行初始化
void glBeginQuery(GLenum target, GLuint id);
void glEndQuery(GLenum target);
 
target必须为GL_SAMPLES_PASSED.

判断遮挡查询的结果
void glGetQueryObjectiv(GLenum id, GLenum pname, GLint *params);
void glGetQueryObjectuiv(GLenum id, GLenum pname, GLuint *params);

清除遮挡查询对象
void glDeleteQueries(GLsizei n, const GLuint *ids);

10.2.6 混合,抖动和逻辑操作


void glLogicOp(GLenum opcode); 
选择需要执行的逻辑操作.

10.3 累计缓冲区


void glAccum(GLenum op, GLfloat value); 
控制累积缓冲区
op参数:
GL_ACCUM--用glReadBuffer()所读取的当前缓冲区中读取每个像素.把R,G,B,A值乘以value.而后结果累加到累积缓冲区.
GL_LOAD--同上,只是用结果替换累积缓冲区中的值.
GL_RETURN--累积缓冲区中的值乘以value, 结果放在颜色缓冲区中.
GL_ADD和AL_MULT--累积缓冲区中的值与value相加或者相乘,结果写回累积缓冲区. 另GL_MULT的结果截取[-1.0. 1.0].GL_ADD的结果不截取.

10.3.1 场景抗锯齿


首先清除累积缓冲区, 并启用前缓冲区用于读取和写入.
然后循环执行几次(例如n次)代码, 对图像进行微移和绘制.
对数据进行累积的方法:
glAccum(GL_ACCUM, 1.0/n);    // 绘制到不会显示的颜色缓冲区中, 避免显示中间图像.
并最终调用
glAccum((GL_RETURN, 1.0);    // 绘制到可显示的颜色缓冲区中(或即将交换的后缓冲区).

可提供一用户接口, 来显示每次图像累积之后所获得的改善, 如图像足够满意, 可随时终止累积.

本例主要是逐步在窗口累积各颜色分量.
一共累积八次,且其中每次都用j8数组的数据微移场景, 使用glFrustum函数可以是的我们场景不必对称.

正交投影的偏移只需要用glTranslatef()移动一个像素内的偏移即可.

下图为没有反锯齿没有用累积缓存的图像

 2

下图为使用了累积缓存反锯齿的图像

1

 

累积缓存我分步骤显示,看看效果

 3 4 5 6 7

10.3.2 运动模糊


按照相同的方式设置累积缓冲区, 但不是对图像进行空间上的微移, 而是进行时间上的微移.
glAccum(GL_MULT, decayFactor); 
这样随着场景绘制到累积缓冲区中,整个场景越来越模糊. 其中decayFactor是个0.0到1.0之间的数字, 其值越小, 运动速度越快.
然后使用
glAccum(GL_RETURN, 1.0); 
转移到眼色缓冲区中.

9

10.3.3 景深


距离聚焦平面越远,物体就越模糊.

accPerspective函数
第五个和第六个参数表示在x和y方向上微移, 实现场景抗锯齿
第九个参数设定聚焦平面.
模糊程度有第七个和第八个参数决定, 由这两个参数的乘积决定.

10

10.3.4 柔和阴影


多个光源所产生的柔和阴影-- 可以多次渲染场景,每次只打开一个光源, 然后将渲染结果累积起来.

10.3.5 微移


样本的微移值

posted @ 2010-11-19 14:23 白了少年头 Views(972) Comments(0) Edit
OpenGL位图和图像

  在前面的章节中,已经讲述了几何数据(点、线、多边形)绘制的有关方法,但OpenGL还有另外两种重要的数据类:一是位图,二是图像。这两种数据都是以象素矩阵形式存储,即用一个矩形数组来表示某一位图或图像。二者不同之处是位图包含每个象素的一位信息,而图像数据一般包含每个象素的多位信息(如,红、绿、蓝和Alpha值);还有位图类似于掩码,可用于遮掩别的图像,而图像数据则简单地覆盖先前已经存在的数据或者与之混合。下面将详述这些内容。

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);   
  lBitmap (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的位图和相应的位图数据。

  在图中,字符大小为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_BYTE8位整数GL_BITMAP无符号的8位整数数组中的单个数位GL_UNSIGNED_SHORT无符号的16位整数GL_SHORT16位整数GL_UNSIGNED_INT无符号的32位整数GL_INT32位整数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);  g
  lMatrixMode(GL_MODELVIEW);
} 
void main(void)
{  
  auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);  
  auxInitPosition (0, 0, 500, 500);  
  auxInitWindow ("Pixel Processing");  
  myinit();  
  auxReshapeFunc (myReshape);  
  auxMainLoop(display);
}

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

posted @ 2010-11-19 11:25 白了少年头 Views(453) Comments(0) Edit
OpenGL像素操作

今天我们先简单介绍Windows中常用的BMP文件格式,然后讲OpenGL的像素操作。虽然看起来内容可能有点多,但实际只有少量几个知识点,如果读者对诸如显示BMP图象等内容比较感兴趣的话,可能不知不觉就看完了。
像素操作可以很复杂,这里仅涉及了简单的部分,让大家对OpenGL像素操作有初步的印象。

学过多媒体技术的朋友可能知道,计算机保存图象的方法通常有两种:一是矢量图,一是像素图。矢量图保存了图象中每一几何物体的位置、形状、大小等信息,在显示图象时,根据这些信息计算得到完整的图象。像素图是将完整的图象纵横分为若干的行、列,这些行列使得图象被分割为很细小的分块,每一分块称为像素,保存每一像素的颜色也就保存了整个图象。
这两种方法各有优缺点。矢量图在图象进行放大、缩小时很方便,不会失真,但如果图象很复杂,那么就需要用非常多的几何体,数据量和运算量都很庞大。像素图无论图象多么复杂,数据量和运算量都不会增加,但在进行放大、缩小等操作时,会产生失真的情况。
前面我们曾介绍了如何使用OpenGL来绘制几何体,我们通过重复的绘制许多几何体,可以绘制出一幅矢量图。那么,应该如何绘制像素图呢?这就是我们今天要学习的内容了。

1BMP文件格式简单介绍
BMP文件是一种像素文件,它保存了一幅图象中所有的像素。这种文件格式可以保存单色位图、16色或256色索引模式像素图、24位真彩色图象,每种模式种单一像素的大小分别为1/8字节,1/2字节,1字节和3字节。目前最常见的是256BMP24位色BMP。这种文件格式还定义了像素保存的几种方法,包括不压缩、RLE压缩等。常见的BMP文件大多是不压缩的。
这里为了简单起见,我们仅讨论24位色、不使用压缩的BMP。(如果你使用Windows自带的画图程序,很容易绘制出一个符合以上要求的BMP
Windows所使用的BMP文件,在开始处有一个文件头,大小为54字节。保存了包括文件格式标识、颜色数、图象大小、压缩方式等信息,因为我们仅讨论24位色不压缩的BMP,所以文件头中的信息基本不需要注意,只有大小这一项对我们比较有用。图象的宽度和高度都是一个32位整数,在文件中的地址分别为0x00120x0016,于是我们可以使用以下代码来读取图象的大小信息:

GLint width, height; // 使用OpenGLGLint类型,它是32位的。
                     // C语言本身的int则不一定是32位的。
FILE* pFile;
// 
在这里进行打开文件的操作
fseek(pFile, 0x0012, SEEK_SET);         // 移动到0x0012位置
fread(&width, sizeof(width), 1, pFile); // 读取宽度
fseek(pFile, 0x0016, SEEK_SET);         // 移动到0x0016位置
                                        // 由于上一句执行后本就应该在0x0016位置
                                        // 所以这一句可省略
fread(&height, sizeof(height), 1, pFile); // 读取高度

54个字节以后,如果是16色或256BMP,则还有一个颜色表,但24位色BMP没有这个,我们这里不考虑。接下来就是实际的像素数据了。24位色的BMP文件中,每三个字节表示一个像素的颜色。
注意,OpenGL通常使用RGB来表示颜色,但BMP文件则采用BGR,就是说,顺序被反过来了。
另外需要注意的地方是:像素的数据量并不一定完全等于图象的高度乘以宽度乘以每一像素的字节数,而是可能略大于这个值。原因是BMP文件采用了一种对齐的机制,每一行像素数据的长度若不是4的倍数,则填充一些数据使它是4的倍数。这样一来,一个17*1524BMP大小就应该是834字节(每行17个像素,有51字节,补充为52字节,乘以15得到像素数据总长度780,再加上文件开始的54字节,得到834字节)。分配内存时,一定要小心,不能直接使用图象的高度乘以宽度乘以每一像素的字节数来计算分配空间的长度,否则有可能导致分配的内存空间长度不足,造成越界访问,带来各种严重后果。
一个很简单的计算数据长度的方法如下:

int LineLength, TotalLength;
LineLength = ImageWidth * BytesPerPixel; // 
每行数据长度大致为图象宽度乘以
                                         // 每像素的字节数
while( LineLength % 4 != 0 )             // 修正LineLength使其为4的倍数
    ++LineLenth;
TotalLength = LineLength * ImageHeight;  // 
数据总长 = 每行长度 * 图象高度

这并不是效率最高的方法,但由于这个修正本身运算量并不大,使用频率也不高,我们就不需要再考虑更快的方法了。

2、简单的OpenGL像素操作
OpenGL提供了简洁的函数来操作像素:
glReadPixels:读取一些像素。当前可以简单理解为把已经绘制好的像素(它可能已经被保存到显卡的显存中)读取到内存
glDrawPixels:绘制一些像素。当前可以简单理解为把内存中一些数据作为像素数据,进行绘制
glCopyPixels:复制一些像素。当前可以简单理解为把已经绘制好的像素从一个位置复制到另一个位置。虽然从功能上看,好象等价于先读取像素再绘制像素,但实际上它不需要把已经绘制的像素(它可能已经被保存到显卡的显存中)转换为内存数据,然后再由内存数据进行重新的绘制,所以要比先读取后绘制快很多。
这三个函数可以完成简单的像素读取、绘制和复制任务,但实际上也可以完成更复杂的任务。当前,我们仅讨论一些简单的应用。由于这几个函数的参数数目比较多,下面我们分别介绍。

3glReadPixels的用法和举例
3.1 函数的参数说明
该函数总共有七个参数。前四个参数可以得到一个矩形,该矩形所包括的像素都会被读取出来。(第一、二个参数表示了矩形的左下角横、纵坐标,坐标以窗口最左下角为零,最右上角为最大值;第三、四个参数表示了矩形的宽度和高度)
第五个参数表示读取的内容,例如:GL_RGB就会依次读取像素的红、绿、蓝三种数据,GL_RGBA则会依次读取像素的红、绿、蓝、alpha四种数据,GL_RED则只读取像素的红色数据(类似的还有GL_GREENGL_BLUE,以及GL_ALPHA)。如果采用的不是RGBA颜色模式,而是采用颜色索引模式,则也可以使用GL_COLOR_INDEX来读取像素的颜色索引。目前仅需要知道这些,但实际上还可以读取其它内容,例如深度缓冲区的深度数据等。
第六个参数表示读取的内容保存到内存时所使用的格式,例如:GL_UNSIGNED_BYTE会把各种数据保存为GLubyteGL_FLOAT会把各种数据保存为GLfloat等。
第七个参数表示一个指针,像素数据被读取后,将被保存到这个指针所表示的地址。注意,需要保证该地址有足够的可以使用的空间,以容纳读取的像素数据。例如一幅大小为256*256的图象,如果读取其RGB数据,且每一数据被保存为GLubyte,总大小就是:256*256*3 = 196608字节,即192千字节。如果是读取RGBA数据,则总大小就是256*256*4 = 262144字节,即256千字节。

注意:glReadPixels实际上是从缓冲区中读取数据,如果使用了双缓冲区,则默认是从正在显示的缓冲(即前缓冲)中读取,而绘制工作是默认绘制到后缓冲区的。因此,如果需要读取已经绘制好的像素,往往需要先交换前后缓冲。

再看前面提到的BMP文件中两个需要注意的地方:
3.2 解决OpenGL常用的RGB像素数据与BMP文件的BGR像素数据顺序不一致问题
可以使用一些代码交换每个像素的第一字节和第三字节,使得RGB的数据变成BGR的数据。当然也可以使用另外的方式解决问题:新版本的OpenGL除了可以使用GL_RGB读取像素的红、绿、蓝数据外,也可以使用GL_BGR按照相反的顺序依次读取像素的蓝、绿、红数据,这样就与BMP文件格式相吻合了。即使你的gl/gl.h头文件中没有定义这个GL_BGR,也没有关系,可以尝试使用GL_BGR_EXT。虽然有的OpenGL实现(尤其是旧版本的实现)并不能使用GL_BGR_EXT,但我所知道的Windows环境下各种OpenGL实现都对GL_BGR提供了支持,毕竟Windows中各种表示颜色的数据几乎都是使用BGR的顺序,而非RGB的顺序。这可能与IBM-PC的硬件设计有关。

3.3 消除BMP文件中对齐带来的影响
实际上OpenGL也支持使用了这种对齐方式的像素数据。只要通过glPixelStore修改像素保存时对齐的方式就可以了。像这样:
int alignment = 4;
glPixelStorei(GL_UNPACK_ALIGNMENT, alignment);
第一个参数表示设置像素的对齐值,第二个参数表示实际设置为多少。这里像素可以单字节对齐(实际上就是不使用对齐)、双字节对齐(如果长度为奇数,则再补一个字节)、四字节对齐(如果长度不是四的倍数,则补为四的倍数)、八字节对齐。分别对应alignment的值为1, 2, 4, 8。实际上,默认的值是4,正好与BMP文件的对齐方式相吻合。
glPixelStorei也可以用于设置其它各种参数。但我们这里并不需要深入讨论了。


现在,我们已经可以把屏幕上的像素读取到内存了,如果需要的话,我们还可以将内存中的数据保存到文件。正确的对照BMP文件格式,我们的程序就可以把屏幕中的图象保存为BMP文件,达到屏幕截图的效果。
我们并没有详细介绍BMP文件开头的54个字节的所有内容,不过这无伤大雅。从一个正确的BMP文件中读取前54个字节,修改其中的宽度和高度信息,就可以得到新的文件头了。假设我们先建立一个1*1大小的24位色BMP,文件名为dummy.bmp,又假设新的BMP文件名称为grab.bmp。则可以编写如下代码:

FILE* pOriginFile = fopen("dummy.bmp", "rb);
FILE* pGrabFile = fopen("grab.bmp", "wb");
char  BMP_Header[54];
GLint width, height;



// 
读取dummy.bmp中的头54个字节到数组
fread(BMP_Header, sizeof(BMP_Header), 1, pOriginFile);
// 
把数组内容写入到新的BMP文件
fwrite(BMP_Header, sizeof(BMP_Header), 1, pGrabFile);

// 
修改其中的大小信息
fseek(pGrabFile, 0x0012, SEEK_SET);
fwrite(&width, sizeof(width), 1, pGrabFile);
fwrite(&height, sizeof(height), 1, pGrabFile);

// 
移动到文件末尾,开始写入像素数据
fseek(pGrabFile, 0, SEEK_END);



fclose(pOriginFile);
fclose(pGrabFile);

 

我们给出完整的代码,演示如何把整个窗口的图象抓取出来并保存为BMP文件。

#define WindowWidth  400
#define WindowHeight 400

#include <stdio.h>
#include <stdlib.h>


#define BMP_Header_Length 54
void grab(void)
{
    FILE*    pDummyFile;
    FILE*    pWritingFile;
    GLubyte* pPixelData;
    GLubyte  BMP_Header[BMP_Header_Length];
    GLint    i, j;
    GLint    PixelDataLength;

    // 
计算像素数据的实际长度
    i = WindowWidth * 3;   // 得到每一行的像素数据长度
    while( i%4 != 0 )      // 补充数据,直到i是的倍数
        ++i;               // 本来还有更快的算法,
                           // 但这里仅追求直观,对速度没有太高要求
    PixelDataLength = i * WindowHeight;

    // 
分配内存和打开文件
    pPixelData = (GLubyte*)malloc(PixelDataLength);
    if( pPixelData == 0 )
        exit(0);

    pDummyFile = fopen("dummy.bmp", "rb");
    if( pDummyFile == 0 )
        exit(0);

    pWritingFile = fopen("grab.bmp", "wb");
    if( pWritingFile == 0 )
        exit(0);

    // 
读取像素
    glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
    glReadPixels(0, 0, WindowWidth, WindowHeight,
        GL_BGR_EXT, GL_UNSIGNED_BYTE, pPixelData);

    // 
dummy.bmp的文件头复制为新文件的文件头
    fread(BMP_Header, sizeof(BMP_Header), 1, pDummyFile);
    fwrite(BMP_Header, sizeof(BMP_Header), 1, pWritingFile);
    fseek(pWritingFile, 0x0012, SEEK_SET);
    i = WindowWidth;
    j = WindowHeight;
    fwrite(&i, sizeof(i), 1, pWritingFile);
    fwrite(&j, sizeof(j), 1, pWritingFile);

    // 
写入像素数据
    fseek(pWritingFile, 0, SEEK_END);
    fwrite(pPixelData, PixelDataLength, 1, pWritingFile);

    // 
释放内存和关闭文件
    fclose(pDummyFile);
    fclose(pWritingFile);
    free(pPixelData);
}



把这段代码复制到以前任何课程的样例程序中,在绘制函数的最后调用grab函数,即可把图象内容保存为BMP文件了。(在我写这个教程的时候,不少地方都用这样的代码进行截图工作,这段代码一旦写好,运行起来是很方便的。)

 

4glDrawPixels的用法和举例
glDrawPixels函数与glReadPixels函数相比,参数内容大致相同。它的第一、二、三、四个参数分别对应于glReadPixels函数的第三、四、五、六个参数,依次表示图象宽度、图象高度、像素数据内容、像素数据在内存中的格式。两个函数的最后一个参数也是对应的,glReadPixels中表示像素读取后存放在内存中的位置,glDrawPixels则表示用于绘制的像素数据在内存中的位置。
注意到glDrawPixels函数比glReadPixels函数少了两个参数,这两个参数在glReadPixels中分别是表示图象的起始位置。在glDrawPixels中,不必显式的指定绘制的位置,这是因为绘制的位置是由另一个函数glRasterPos*来指定的。glRasterPos*函数的参数与glVertex*类似,通过指定一个二维/三维/四维坐标,OpenGL将自动计算出该坐标对应的屏幕位置,并把该位置作为绘制像素的起始位置。
很自然的,我们可以从BMP文件中读取像素数据,并使用glDrawPixels绘制到屏幕上。我们选择Windows XP默认的桌面背景Bliss.bmp作为绘制的内容(如果你使用的是Windows XP系统,很可能可以在硬盘中搜索到这个文件。当然你也可以使用其它BMP文件来代替,只要它是24位的BMP文件。注意需要修改代码开始部分的FileName的定义),先把该文件复制一份放到正确的位置,我们在程序开始时,就读取该文件,从而获得图象的大小后,根据该大小来创建合适的OpenGL窗口,并绘制像素。
绘制像素本来是很简单的过程,但是这个程序在骨架上与前面的各种示例程序稍有不同,所以我还是打算给出一份完整的代码。

#include <gl/glut.h>

#define FileName "Bliss.bmp"

static GLint    ImageWidth;
static GLint    ImageHeight;
static GLint    PixelLength;
static GLubyte* PixelData;

#include <stdio.h>
#include <stdlib.h>

void display(void)
{
    // 
清除屏幕并不必要
    // 每次绘制时,画面都覆盖整个屏幕
    // 因此无论是否清除屏幕,结果都一样
    // glClear(GL_COLOR_BUFFER_BIT);

    // 
绘制像素
    glDrawPixels(ImageWidth, ImageHeight,
        GL_BGR_EXT, GL_UNSIGNED_BYTE, PixelData);

    // 
完成绘制
    glutSwapBuffers();
}

int main(int argc, char* argv[])
{
    // 
打开文件
    FILE* pFile = fopen("Bliss.bmp", "rb");
    if( pFile == 0 )
        exit(0);

    // 
读取图象的大小信息
    fseek(pFile, 0x0012, SEEK_SET);
    fread(&ImageWidth, sizeof(ImageWidth), 1, pFile);
    fread(&ImageHeight, sizeof(ImageHeight), 1, pFile);

    // 
计算像素数据长度
    PixelLength = ImageWidth * 3;
    while( PixelLength % 4 != 0 )
        ++PixelLength;
    PixelLength *= ImageHeight;

    // 
读取像素数据
    PixelData = (GLubyte*)malloc(PixelLength);
    if( PixelData == 0 )
        exit(0);

    fseek(pFile, 54, SEEK_SET);
    fread(PixelData, PixelLength, 1, pFile);

    // 
关闭文件
    fclose(pFile);

    // 
初始化GLUT并运行
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
    glutInitWindowPosition(100, 100);
    glutInitWindowSize(ImageWidth, ImageHeight);
    glutCreateWindow(FileName);
    glutDisplayFunc(&display);
    glutMainLoop();

    // 
释放内存
    // 实际上,glutMainLoop函数永远不会返回,这里也永远不会到达
    // 这里写释放内存只是出于一种个人习惯
    // 不用担心内存无法释放。在程序结束时操作系统会自动回收所有内存
    free(PixelData);

    return 0;
}



这里仅仅是一个简单的显示24BMP图象的程序,如果读者对BMP文件格式比较熟悉,也可以写出适用于各种BMP图象的显示程序,在像素处理时,它们所使用的方法是类似的。
OpenGL在绘制像素之前,可以对像素进行若干处理。最常用的可能就是对整个像素图象进行放大/缩小。使用glPixelZoom来设置放大/缩小的系数,该函数有两个参数,分别是水平方向系数和垂直方向系数。例如设置glPixelZoom(0.5f, 0.8f);则表示水平方向变为原来的50%大小,而垂直方向变为原来的80%大小。我们甚至可以使用负的系数,使得整个图象进行水平方向或垂直方向的翻转(默认像素从左绘制到右,但翻转后将从右绘制到左。默认像素从下绘制到上,但翻转后将从上绘制到下。因此,glRasterPos*函数设置的开始位置不一定就是矩形的左下角)。

5glCopyPixels的用法和举例
从效果上看,glCopyPixels进行像素复制的操作,等价于把像素读取到内存,再从内存绘制到另一个区域,因此可以通过glReadPixelsglDrawPixels组合来实现复制像素的功能。然而我们知道,像素数据通常数据量很大,例如一幅1024*768的图象,如果使用24BGR方式表示,则需要至少1024*768*3字节,即2.25兆字节。这么多的数据要进行一次读操作和一次写操作,并且因为在glReadPixelsglDrawPixels中设置的数据格式不同,很可能涉及到数据格式的转换。这对CPU无疑是一个不小的负担。使用glCopyPixels直接从像素数据复制出新的像素数据,避免了多余的数据的格式转换,并且也可能减少一些数据复制操作(因为数据可能直接由显卡负责复制,不需要经过主内存),因此效率比较高。
glCopyPixels函数也通过glRasterPos*系列函数来设置绘制的位置,因为不需要涉及到主内存,所以不需要指定数据在内存中的格式,也不需要使用任何指针。
glCopyPixels函数有五个参数,第一、二个参数表示复制像素来源的矩形的左下角坐标,第三、四个参数表示复制像素来源的举行的宽度和高度,第五个参数通常使用GL_COLOR,表示复制像素的颜色,但也可以是GL_DEPTHGL_STENCIL,分别表示复制深度缓冲数据或模板缓冲数据。
值得一提的是,glDrawPixelsglReadPixels中设置的各种操作,例如glPixelZoom等,在glCopyPixels函数中同样有效。
下面看一个简单的例子,绘制一个三角形后,复制像素,并同时进行水平和垂直方向的翻转,然后缩小为原来的一半,并绘制。绘制完毕后,调用前面的grab函数,将屏幕中所有内容保存为grab.bmp。其中WindowWidthWindowHeight是表示窗口宽度和高度的常量。

void display(void)
{
    // 
清除屏幕
    glClear(GL_COLOR_BUFFER_BIT);

    // 
绘制
    glBegin(GL_TRIANGLES);
        glColor3f(1.0f, 0.0f, 0.0f);    glVertex2f(0.0f, 0.0f);
        glColor3f(0.0f, 1.0f, 0.0f);    glVertex2f(1.0f, 0.0f);
        glColor3f(0.0f, 0.0f, 1.0f);    glVertex2f(0.5f, 1.0f);
    glEnd();
    glPixelZoom(-0.5f, -0.5f);
    glRasterPos2i(1, 1);
    glCopyPixels(WindowWidth/2, WindowHeight/2,
        WindowWidth/2, WindowHeight/2, GL_COLOR);

    // 
完成绘制,并抓取图象保存为BMP文件
    glutSwapBuffers();
    grab();
}

小结:
本课结合Windows系统常见的BMP图象格式,简单介绍了OpenGL的像素处理功能。包括使用glReadPixels读取像素、glDrawPixels绘制像素、glCopyPixels复制像素。
本课仅介绍了像素处理的一些简单应用,但相信大家已经可以体会到,围绕这三个像素处理函数,还存在一些外围函数,比如glPixelStore*glRasterPos*,以及glPixelZoom等。我们仅使用了这些函数的一少部分功能。
本课内容并不多,例子足够丰富,三个像素处理函数都有例子,大家可以结合例子来体会。

posted @ 2010-11-19 11:02 白了少年头 Views(348) Comments(0) Edit
OpenGL纹理

  在三维图形中,纹理映射(Texture Mapping)的方法运用得很广,尤其描述具有真实感的物体。比如绘制一面砖墙,就可以用一幅真实的砖墙图像或照片作为纹理贴到一个矩形上,这样,一面逼真的砖墙就画好了。如果不用纹理映射的方法,则墙上的每一块砖都必须作为一个独立的多边形来画。另外,纹理映射能够保证在变换多边形时,多边形上的纹理图案也随之变化。例如,以透视投影方式观察墙面时,离视点远的砖块的尺寸就会缩小,而离视点 较近的就会大些。此外,纹理映射也常常运用在其他一些领域,如飞行仿真中常把一大片植被的图像映射到一些大多边形上用以表示地面,或用大理石、木材、布匹等自然物质的图像作为纹理映射到多边形上表示相应的物体。
  纹理映射有许多种情况。例如,任意一块纹理可以映射到平面或曲面上,且对光亮的物体进行纹理映射,其表面可以映射出周围环境的景象;纹理还可按不同的方式映射到曲面上,一是可以直接画上去(或称移画印花法),二是可以调整曲面颜色或把纹理颜色与曲面颜色混合;纹理不仅可以是二维的,也可以是一维或其它维的。
  本章将详细介绍OpenGL纹理映射有关的内容:基本步骤、纹理定义、纹理控制、映射方式和纹理坐标等。

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 64GLubyte 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);
} 

  以上程序运行结果是将一块纹理映射到两个正方形上去。这两个正方形都是按透视投影方式绘制,其中一个正对观察者,另一个向后倾斜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_SGL_CLAMP
GL_REPEATGL_TEXTURE_WRAP_TGL_CLAMP
GL_REPEATGL_TEXTURE_MAG_FILTERGL_NEAREST
GL_LINEARGL_TEXTURE_MIN_FILTERGL_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 64GLubyte 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);
}

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

posted @ 2010-11-19 10:02 白了少年头 Views(392) Comments(0) Edit
OpenGL显示列表

 OpenGL显示列表(Display List)是由一组预先存储起来的留待以后调用的OpenGL函数语句组成的,当调用这张显示列表时就依次执行表中所列出的函数语句。前面内容所举出的例子都是瞬时给出函数命令,则OpenGL瞬时执行相应的命令,这种绘图方式叫做立即或瞬时方式(immediate mode)。本章将详细地讲述显示列表的基本概论、创建、执行、管理以及多级显示列表的应用等内容。

16.1、显示列表概论

  16.1.1 显示列表的优势
  OpenGL显示列表的设计能优化程序运行性能,尤其是网络性能。它被设计成命令高速缓存,而不是动态数据库缓存。也就是说,一旦建立了显示列表,就不能修改它。因为若显示列表可以被修改,则显示列表的搜索、内存管理的执行等开销会降低性能。
  采用显示列表方式绘图一般要比瞬时方式快,尤其是显示列表方式可以大量地提高网络性能,即当通过网络发出绘图命令时,由于显示列表驻留在服务器中,因而使网络的负担减轻到最小。另外,在单用户的机器上,显示列表同样可以提高效率。因为一旦显示列表被处理成适合于图形硬件的格式,则不同的OpenGL实现对命令的优化程度也不同。例如旋转矩阵函数glRotate*(),若将它置于显示列表中,则可大大提高性能。因为旋转矩阵的计算并不简单,包含有平方、三角函数等复杂运算,而在显示列表中,它只被存储为最终的旋转矩阵,于是执行起来如同硬件执行函数glMultMatrix()一样快。一般来说,显示列表能将许多相邻的矩阵变换结合成单个的矩阵乘法,从而加快速度。

  16.1.2 显示列表的适用场合
  并不是只要调用显示列表就能优化程序性能。因为调用显示列表本身时程序也有一些开销,若一个显示列表太小,这个开销将超过显示列表的优越性。下面给出显示列表能最大优化的场合:
 

  • 矩阵操作
    大部分矩阵操作需要OpenGL计算逆矩阵,矩阵及其逆矩阵都可以保存在显示列表中。

     
  • 光栅位图和图像
    程序定义的光栅数据不一定是适合硬件处理的理想格式。当编译组织一个显示列表时,OpenGL可能把数据转换成硬件能够接受的数据,这可以有效地提高画位图的速度。

     
  • 光、材质和光照模型
    当用一个比较复杂的光照环境绘制场景时,可以为场景中的每个物体改变材质。但是材质计算较多,因此设置材质可能比较慢。若把材质定义放在显示列表中,则每次改换材质时就不必重新计算了。因为计算结果存储在表中,因此能更快地绘制光照场景。

     
  • 纹理
    因为硬件的纹理格式可能与OpenGL格式不一致,若把纹理定义放在显示列表中,则在编译显示列表时就能对格式进行转换,而不是在执行中进行,这样就能大大提高效率。

     
  • 多边形的图案填充模式
    即可将定义的图案放在显示列表中。

16.2、创建和执行显示列表

  16.2.1 创建显示列表
  OpenGL提供类似于绘制图元的结构即glBegin()与glEnd()的形式创建显示列表,其相应的函数为:

 

void glNewList(GLuint list,GLenum mode);

  说明一个显示列表的开始,其后的OpenGL函数存入显示列表中,直至调用结束表的函数(见下面)。参数list是一个正整数,它标志唯一的显示列表。参数mode的可能值有GL_COMPILE和GL_COMPILE_AND_EXECUTE。若要使后面的函数语句只存入而不执行,则用GL_COMPILE;若要使后面的函数语句存入表中且按瞬时方式执行一次,则用GL_COMPILE_AND_EXECUTE。
 

void glEndList(void);

  标志显示列表的结束。
  注意:并不是所有的OpenGL函数都可以在显示列表中存储且通过显示列表执行。一般来说,用于传递参数或返回数值的函数语句不能存入显示列表,因为这张表有可能在参数的作用域之外被调用;如果在定义显示列表时调用了这样的函数,则它们将按瞬时方式执行并且不保存在显示列表中,有时在调用执行显示列表函数时会产生错误。以下列出的是不能存入显示列表的OpenGL函数:
 

 glDeleteLists()  glIsEnable()glFeedbackBuffer()  glIsList()glFinish()  glPixelStore()glGenLists()  glRenderMode()glGet*()  glSelectBuffer()

  16.2.2 执行显示列表
  在建立显示列表以后就可以调用执行显示列表的函数来执行它,并且允许在程序中多次执行同一显示列表,同时也可以与其它函数的瞬时方式混合使用。显示列表执行的函数形式如下:

 

void glCallList(GLuint list);

  执行显示列表。参数list指定被执行的显示列表。显示列表中的函数语句按它们被存放的顺序依次执行;若list没有定义,则不会产生任何事情。下面举出一个应用显示列表的简单例子:

  例16-1 显示列表例程displist.c
 

#include "glos.h"
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h> 
void myinit(void);
void drawLine(void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
 GLuint listName = 1;
 void myinit (void)
{
  glNewList (listName, GL_COMPILE);
  glColor3f (1.0, 0.0, 0.0); 
  glBegin (GL_TRIANGLES); 
  glVertex2f (0.0, 0.0); 
  glVertex2f (1.0, 0.0); 
  glVertex2f (0.0, 1.0);  
  glEnd ();  
  glTranslatef (1.5, 0.0, 0.0); 
  glEndList (); 
  glShadeModel (GL_FLAT);
}
 void drawLine (void)
{ 
   glColor3f(1.0,1.0,0.0); 
   glBegin (GL_LINES); 
   glVertex2f (0.0, 0.5);
   glVertex2f (5.0, 0.5); 
   glEnd ();
} 
void CALLBACK display(void)
{  
    GLuint i; 
    glClear (GL_COLOR_BUFFER_BIT);  
    glColor3f (0.0, 1.0, 0.0);  
    glPushMatrix();  
    for (i = 0; i <5; i++)    
        glCallList (listName); 
    drawLine (); 
    glPopMatrix();  
    glFlush ();} 
void CALLBACK myReshape(GLsizei w, GLsizei h)
{  
    glViewport(0, 0, w, h);  
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();  
    if (w <= h)  
        gluOrtho2D (0.0, 2.0, -0.5 * (GLfloat) h/(GLfloat) w, 1.5 * (GLfloat) h/(GLfloat) w); 
    else   
        gluOrtho2D (0.0, 2.0 * (GLfloat) w/(GLfloat) h, -0.5, 1.5); 
    glMatrixMode(GL_MODELVIEW);  glLoadIdentity();
} 
void main(void)
{  
    auxInitDisplayMode (AUX_SINGLE | AUX_RGBA); 
    auxInitPosition (10, 200, 400, 50);
    auxInitWindow ("Display List"); 
    myinit (); 
    auxReshapeFunc (myReshape); 
    auxMainLoop(display);
}

  以上程序运行结果是显示五个显示列表中定义的红色三角形,然后再绘制一条非表中的黄色线段。

 


16.3、管理显示列表
  在上一节例子中,我们使用了一个正整数作为显示列表的索引。但是在实际应用中,一般不采用这种方式,尤其在创建多个显示列表的情况下。如果这样做,则有可能选用某个正在被占用的索引,并且覆盖这个已经存在的显示列表,对程序运行造成危害。为了避免意外删除,可以调用函数glGenList()来产生一个没有用过的显示列表,或调用glIsList()来决定是否指定的显示列表被占用。此外,在管理显示列表的过程中,还可调用函数glDeleteLists()来删除一个或一个范围内的显示列表。下面分别介绍这些函数:

GLuint glGenList(GLsizei range);

  分配range个相邻的未被占用的显示列表索引。这个函数返回的是一个正整数索引值,它是一组连续空索引的第一个值。返回的索引都标志为空且已被占用,以后再调用这个函数时不再返回这些索引。若申请索引的指定数目不能满足或range为0则函数返回0。
 

GLboolean glIsList(GLuint list);

 询问显示列表是否已被占用的情况。若索引list已被占用,则函数返回TURE;反之,返回FAULSE。

void glDeleteLists(GLuint list,GLsizei range);

  删除一组连续的显示列表,即从参数list所指示的显示列表开始,删除range个显示列表,并且删除后的这些索引重新有效。若删除一个没有建立的显示列表则忽略删除操作。
  当建立一个与已经存在的显示列表索引相同的显示列表时,OpenGL将自动删除旧表。这一节举个例子来说,如果将上一节程序/***.c*/中所创建的显示列表改为以下代码:
 

 listIndex=glGenLists(1);if(listIndex!=0){  glNewList(listIndex,GL_COMPILE);  ...  glEndList();}

  那么,这个程序将更加优化实用。读者自己不妨试试,同时还可用它多创建几个显示列表,或者再删除一个,看看效果怎样?

16.4、多级显示列表
  多级显示列表的建立就是在一个显示列表中调用另一个显示列表,也就是说,在函数glNewList()与glEndList()之间调用glCallList()。多级显示列表对于构造由多个元件组成的物体十分有用,尤其是某些元件需要重复使用的情况。但为了避免无穷递归,显示列表的嵌套深度最大为64(也许更高些,这依赖于不同的OpenGL实现),当然也可调用函数glGetIntegerv()来获得这个最大嵌套深度值。
  OpenGL在建立的显示列表中允许调用尚未建立的表,当第一个显示列表调用第二个并没 定义的表时,不会发生任何操作。另外,也允许用一个显示列表包含几个低级的显示列表来模拟建立一个可编辑的显示列表。如下一段代码:

 

glNewList(1,GL_COMPILE);
glVertex3fv(v1);
glEndList()
glNewList(2,GL_COMPILE);
glVertex3fv(v2);
glEndList()
glNewList(3,GL_COMPILE);
glVertex3fv(v3);
glEndList()
glNewList(4,GL_COMPILE);
glBegin(GL_POLYGON);
glCallList(1);
glCallList(2);
glCallList(3);
glEnd();
glEndList();

  这样,要绘制三角形就可以调用显示列表4了,即调用glCallList(4);要编辑顶点,只需重新建立相应的该顶点显示列表

原创粉丝点击