Chinaunix首页 | 论坛 | 博客
  • 博客访问: 547643
  • 博文数量: 179
  • 博客积分: 3845
  • 博客等级: 中校
  • 技术积分: 2003
  • 用 户 组: 普通用户
  • 注册时间: 2010-08-16 21:25
文章分类
文章存档

2012年(74)

2011年(105)

分类: 嵌入式

2012-01-17 12:01:28

OpenGL入门学习[十三]



前一段时间里,论坛有位朋友问什么是状态机。按我的理解,状态机就是一种存在于理论中的机器,它具有以下的特点:

1. 它有记忆的能力,能够记住自己当前的状态。

2. 它可以接收输入,根据输入的内容和自己的状态,修改自己的状态,并且可以得到输出。

3. 当它进入某个特殊的状态(停机状态)的时候,它不再接收输入,停止工作。

理论说起来很抽象,但实际上是很好理解的。

首先,从本质上讲,我们现在的电脑就是典型的状态机。可以对照理解:

1. 电脑的存储器(内存、硬盘等等),可以记住电脑自己当前的状态(当前安装在电脑中的软件、保存在电脑中的数据,其实都是二进制的值,都属于当前的状态)。

2. 电脑的输入设备接收输入(键盘输入、鼠标输入、文件输入),根据输入的内容和自己的状态(主要指可以运行的程序代码),修改自己的状态(修改内存中的值),并且可以得到输出(将结果显示到屏幕)。

3. 当它进入某个特殊的状态(关机状态)的时候,它不再接收输入,停止工作。

OpenGL也可以看成这样的一种机器。让我们先对照理解一下:

1. OpenGL可以记录自己的状态(比如:当前所使用的颜色、是否开启了混合功能,等等,这些都是要记录的)

2. OpenGL可以接收输入(当我们调用OpenGL函数的时候,实际上可以看成OpenGL在接收我们的输入),根据输入的内容和自己的状态,修改自己的状态,并且可以得到输出(比如我们调用glColor3f,则OpenGL接收到这个输入后会修改自己的“当前颜色”这个状态;我们调用glRectf,则OpenGL会输出一个矩形)

3. OpenGL可以进入停止状态,不再接收输入。这个可能在我们的程序中表现得不太明显,不过在程序退出前,OpenGL总会先停止工作的。

还是没理解?呵呵,看来这真不是个好的开始呀,难得等了这么久,好不容易教程有更新了,怎么如此的难懂啊??没关系,实在没理解,咱就不理解它了。接着往下看。

为什么我要提到“状态机”这个枯燥的、晦涩的概念呢?其实它可以帮助我们理解一些东西。

比如我在前面的教程里面,经常说:

可以使用glColor*函数来选择一种颜色,以后绘制的所有物体都是这种颜色,除非再次使用glColor*函数重新设定。

可以使用glTexCoord*函数来设置一个纹理坐标,以后绘制的所有物体都是采用这种纹理坐标,除非再次使用glTexCoord*函数重新设置。

可以使用glBlendFunc函数来指定混合功能的源因子和目标因子,以后绘制的所有物体都是采用这个源因子和目标因子,除非再次使用glBlendFunc函数重新指定。

可以使用glLight*函数来指定光源的位置、颜色,以后绘制的所有物体都是采用这个光源的位置、颜色,除非再次使用glBlendFunc函数重新指定。

……

呵呵,很繁,是吧?“状态机”可以简化这个描述。

OpenGL是一个状态机,它保持自身的状态,除非用户输入一条命令让它改变状态。

颜色、纹理坐标、源因子和目标因子、光源的各种参数,等等,这些都是状态,所以这一句话就包含了上面叙述的所有内容。

此外,“是否启用了光照”、“是否启用了纹理”、“是否启用了混合”、“是否启用了深度测试”等等,这些也都是状态,也符合上面的描述:OpenGL会保持状态,除非我们调用OpenGL函数来改变它。

取得OpenGL的当前状态

OpenGL保存了自己的状态,我们可以通过一些函数来取得这些状态。

首先来说一些启用/禁用的状态。

我们通过glEnable来启用状态,通过glDisable来禁用它们。例如:

glEnable(GL_DEPTH_TEST);

glEnable(GL_BLEND);

glEnable(GL_CULL_FACE);

glEnable(GL_LIGHTING);

glEnable(GL_TEXTURE_2D);

可以用glIsEnabled函数来检测这些状态是否被开启。例如:

glIsEnabled(GL_DEPTH_TEST);

glIsEnabled(GL_BLEND);

glIsEnabled(GL_CULL_FACE);

glIsEnabled(GL_LIGHTING);

glIsEnabled(GL_TEXTURE_2D);

如果状态是开启的,则glIsEnabled函数返回GL_TRUE(这是一个不为零的常量,一般被定义为1);否则返回GL_FALSE(这是一个常量,其值为零)

我们可以在程序里面写:

if( glIsEnabled(GL_BLEND) ) {

     // 当前开启了混合功能

} else {

     // 当前没有开启混合功能

}

再看其它类型的状态。

比如当前颜色,其值是四个浮点数,当前设置的直线宽度,其值是一个浮点数,当前的视口(Viewport,参见第五课),其值是四个整数。

为了取得整数类型、浮点数类型的状态,OpenGL提供了glGetBooleanv, glGetIntegerv, glGetFloatv, glGetDoublev这四个函数。调用函数时,指定需要得到的状态的名称,以及需要将状态值存放到的位置(一个指针),则这四个函数可以把状态值存放到指针所值位置。例如:

// 取得当前的直线宽度

GLfloat lw;

glGetFloatv(GL_LINE_WIDTH, &lw);

// 取得当前的颜色

GLfloat cc[4];

glGetFloatv(GL_CURRENT_COLOR, cc);

// 取得当前的视口

GLint viewport[4];

glGetIntegerv(GL_VIEWPORT, viewport);

说明:

1. 注意元素的个数。比如GL_LINE_WIDTH状态只有一个值,而GL_CURRENT_COLOR有四个值。应该小心的定义变量或者数组,避免下标越界。

2. 使用四个不同的函数,同一种状态也可以返回为不同类型的值。比如要得到当前的颜色,一般可以返回GLfloat类型或者GLdouble类型。代码如下:

GLfloat cc[4];

GLdouble dcc[4];

glGetFloatv(GL_CURRENT_COLOR, cc);

glGetDoublev(GL_CURRENT_COLOR, dcc);

glGetBooleanv, glGetIntegerv, glGetFloatv, glGetDoublev这四个函数可以得到OpenGL中多数的状态,但是还有一些状态不便用这四个函数来取得。比如光源的状态,因为可能有多个光源,所以不可能使用类似glGetFloatv(GL_LIGHT_POSITION, pos);这样的方法来得到光源位置。为了解决这个问题,OpenGL专门提供了glGetLight*系列函数,来取得光源的状态。

类似的,还有glGetMaterial*, glGetTexParameter*等,每个函数都有自己的适用范围。

设置OpenGL状态

呵呵,读者可能会有疑问。既然有getXXX这样的函数来取得OpenGL的状态,那么为什么没有setXXX这样的函数来设置OpenGL状态呢?

答案很简单,因为OpenGL已经提供了大量的函数来设置状态了:glColor*, glMaterial*, glEnable, glDisable, 等等,大多数OpenGL函数都是用来设置OpenGL状态的,因此不需要再设计一个setXXX函数来设置OpenGL状态。

从“状态机”的角度来看。状态机根据输入来修改自己的状态,而不是由外界直接修改自己的状态。所以不设置setXXX这样的函数,也是很合理的。

OpenGL工作流程

教程都放到第十三课了,但是我一直没有对“工作流程”这种东西做过说明。OpenGL是按照什么样的流程来进行工作的呢?下面的图片可以简要的说明一下:

声明:该图片来自,该图片是《OpenGL编程指南》一书的附图,由于该书的旧版(第一版,1994年)已经流传于网络,我希望没有触及到版权问题。

因为图片中的文字是英语,这里还翻译一下。说明文字也夹杂在翻译之中了。

1. Vertex data: 顶点数据。比如我们指定的颜色、纹理坐标、法线向量、顶点坐标等,都属于顶点数据。

2. Pixel data: 像素数据。我们在绘制像素、指定纹理时都会用到像素数据。

3. Display list: 显示列表。可以把调用的OpenGL函数保存起来。(参见第八课)

4. Evaluators: 求值器。这个我们在前面的课程中没有提到,以后估计也不太会提到。利用求值器可以指定贝赛尔曲线或者贝赛尔曲面,但是实际上还是可以理解为指定顶点、指定纹理坐标、指定法线向量等。

5. Per-vertex operations and primitive assembly: 单一的顶点操作以及图元装配。首先对单一的顶点进行操作,比如变换(参见第五课)。然后把顶点装配为图元(图元就是OpenGL所能绘制的最简单的图形,比如点、线段、三角形、四边形、多边形等,参见第二课)

6. Pixel operations: 像素操作。例如把内存中的像素数据格式转化为图形硬件所支持的数据格式。对于纹理,可以替换其中的一部分像素,这也属于像素操作。

7. Rasterization: 光栅化。顶点数据和像素数据在这里交汇(可以想像成:顶点和纹理,一起组合成了具有纹理的三角形),形成完整的、可以显示的一整块(可能是点、线段、三角形、四边形,或者其它不规则图形),里面包含若干个像素。这一整块被称为fragment(片段)。

8. Per-fragment operations: 片段操作。包括各种片段测试(参见第十二课)。

9. Framebuffer: 帧缓冲。这是一块存储空间,显示设备从这里读取数据,然后显示到屏幕。

10. Texture assembly: 纹理装配,这里我也没怎么弄清楚:(,大概是说纹理的操作和像素操作是相关的吧。

说明:图片中实线表示正常的处理流程,虚线表示数据可以反方向读取,比如可以用glReadPixels从帧缓冲中读取像素数据(实际上是从帧缓冲读取数据,经过像素操作,把显示设备中的像素数据格式转化为内存中的像素数据格式,最终成为内存中的像素数据)。

小结

本课是枯燥的理论知识。

OpenGL是一个状态机,它维持自己的状态,并根据用户调用的函数来改变自己的状态。根据状态的不同,调用同样的函数也可能产生不同的效果。

可以通过一些函数来获取OpenGL当前的状态。常用的函数有:glIsEnabled, glGetBooleanv, glGetIntegerv, glGetFloatv, glGetDoublev。

OpenGL的工作流程,输入像素数据和顶点数据,两种数据分别操作后,通过光栅化,得到片段,再经过片段处理,最后绘制到帧缓冲区。绘制的结果也可以逆方向传送,最终转化为像素数据。



OpenGL入门学习[十四]


OpenGL从推出到现在,已经有相当长的一段时间了。其间,OpenGL不断的得到更新。到今天为止,正式的OpenGL已经有九个版本。(1.0, 1.1, 1.2, 1.2.1, 1.3, 1.4, 1.5, 2.0, 2.1)
每个OpenGL版本的推出,都增加了一些当时流行的或者迫切需要的新功能。同时,到现在为止,OpenGL是向下兼容的,就是说如果某个功能在一个低版本中存在,则在更高版本中也一定存在。这一特性也为我们编程提供了一点方便。
当前OpenGL的最新版本是OpenGL 2.1,但是并不是所有的计算机系统都有这样最新版本的OpenGL实现。举例来说,Windows系统如果没有安装显卡驱动,或者显卡驱动中没有附带OpenGL,则Windows系统默认提供一个软件实现的OpenGL,它没有使用硬件加速,因此速度可能较慢,版本也很低,仅支持1.1版本(听说Windows Vista默认提供的OpenGL支持到1.4版本,我也不太清楚)。nVidia和ATI这样的显卡巨头,其主流显卡基本上都提供了对OpenGL 2.1的支持。但一些旧型号的显卡因为性能不足等原因,只能支持到OpenGL 2.0或者OpenGL 1.5。Intel的集成显卡,很多都只提供了OpenGL 1.4(据说目前也有更高版本的了,但是我没有见到)。
OpenGL 2.0是一次比较大的改动,也因此升级了主版本号。可以认为OpenGL 2.0版本是一个分水岭,是否支持OpenGL 2.0版本,直接关系到运行OpenGL程序时的效果。如果要类比一下的话,我觉得OpenGL 1.5和OpenGL 2.0的差距,就像是DirectX 8.1和DirectX 9.0c的差距了。
检查自己的OpenGL版本
可以很容易的知道自己系统中的OpenGL版本,方法就是调用glGetString函数。

const char* version = (const char*)glGetString(GL_VERSION);
printf("OpenGL 版本:%s\n", version);



glGetString(GL_VERSION);会返回一个表示版本的字符串,字符串的格式为X.X.X,就是三个整数,用小数点隔开,第一个数表示OpenGL主版本号,第二个数表示OpenGL次版本号,第三个数表示厂商发行代号。比如我在运行时得到的是"2.0.1",这表示我的OpenGL版本为2.0(主版本号为2,次版本号为0),是厂商的第一个发行版本。
通过sscanf函数,也可以把字符串分成三个整数,以便详细的进行判断。

int main_version, sub_version, release_version;
const char* version = (const char*)glGetString(GL_VERSION);
sscanf(version, "%d.%d.%d", &main_version, &sub_version, &release_version);
printf("OpenGL 版本:%s\n", version);
printf("主版本号:%d\n", main_version);
printf("次版本号:%d\n", sub_version);
printf("发行版本号:%d\n", release_version);



glGetString还可以取得其它的字符串。
glGetString(GL_VENDOR); 返回OpenGL的提供厂商。
glGetString(GL_RENDERER); 返回执行OpenGL渲染的设备,通常就是显卡的名字。
glGetString(GL_EXTENSIONS); 返回所支持的所有扩展,每两个扩展之间用空格隔开。详细情况参见下面的关于“OpenGL扩展”的叙述。
版本简要历史
版本不同,提供功能的多少就不同。这里列出每个OpenGL版本推出时,所增加的主要功能。当然每个版本的修改并不只是下面的内容,读者如果需要知道更详细的情形,可以查阅OpenGL标准。
OpenGL 1.1
顶点数组。把所有的顶点数据(颜色、纹理坐标、顶点坐标等)都放到数组中,可以大大的减少诸如glColor*, glVertex*等函数的调用次数。虽然显示列表也可以减少这些函数的调用次数,但是显示列表中的数据是不可以修改的,顶点数组中的数据则可以修改。
纹理对象。把纹理作为对象来管理,同一时间OpenGL可以保存多个纹理(但只使用其中一个)。以前没有纹理对象时,OpenGL只能保存一个“当前纹理”。要使用其它纹理时,只能抛弃当前的纹理,重新载入。原来的方式非常影响效率。
OpenGL 1.2
三维纹理。以前的OpenGL只支持一维、二维纹理。
像素格式。新增加了GL_BGRA等原来没有的像素格式。允许压缩的像素格式,例如GL_UNSIGNED_SHORT_5_5_5_1格式,表示两个字节,存放RGBA数据,其中R, G, B各占5个二进制位,A占一个二进制位。
图像处理。新增了一个“图像处理子集”,提供一些图像处理的专用功能,例如卷积、计算柱状图等。这个子集虽然是标准规定,但是OpenGL实现时也可以选择不支持它。
OpenGL 1.2.1
没有加入任何新的功能。但是引入了“ARB扩展”的概念。详细情况参见下面的关于“OpenGL扩展”的叙述。
OpenGL 1.3
压缩纹理。在处理纹理时,使用压缩后的纹理而不是纹理本身,这样可以节省空间(节省显存)和传输带宽(节省从内存到显存的数据流量)
多重纹理。同时使用多个纹理。
多重采样。一种全屏抗锯齿技术,使用后可以让画面显示更加平滑,减轻锯齿现象。对于nvidia显卡,在设置时有一项“3D平滑处理设置”,实际上就是多重采样。通常可以选择2x, 4x,高性能的显卡也可以选择8x, 16x。其它显卡也几乎都有类似的设置选项,但是也有的显卡不支持多重采样,所以是0x。
OpenGL 1.4
深度纹理。可以把深度值像像素值一样放到纹理中,在绘制阴影时特别有用。
辅助颜色。顶点除了有颜色外还有辅助颜色。在使用光照时可以表现出更真实的效果。
OpenGL 1.5
缓冲对象。允许把数据(主要指顶点数据)交由OpenGL保存到较高性能的存储器中,提高绘制速度。比顶点数组有更多优势。顶点数组只是减少函数调用次数,缓冲对象不仅减少函数调用次数,还加快数据访问速度。
遮挡查询。可以计算一个物体有几个像素会被绘制到屏幕上。如果物体没有任何像素会被绘制,则不需要加载相关的数据(例如纹理数据)。
OpenGL 2.0
可编程着色。允许编写一小段代码来代替OpenGL原来的顶点操作/片段操作。这样提供了巨大的灵活性,可以实现各种各样的丰富的效果。
纹理大小不再必须是2的整数次方。
点块纹理。把纹理应用到一个点(大小可能不只一个像素)上,这样比绘制一个矩形可能效率更高。
OpenGL 2.1
可编程着色,编程语言由原来的1.0版本升级为1.2版本。
缓冲对象,原来仅允许存放顶点数据,现在也允许存放像素数据。
获得新版本的OpenGL
要获得新版本OpenGL,首先应该登陆你的显卡厂商网站,并查询相关的最新信息。根据情况,下载最新的驱动或者OpenGL软件包。
如果自己的显卡不支持高版本的OpenGL,或者自己的操作系统根本就没有提供OpenGL,怎么办呢?有一个被称为MESA的开源项目,用C语言编写了一个OpenGL实现,最新的mesa 7.0已经实现了OpenGL 2.1标准中所规定的各种功能。下载MESA的代码,然后编译,就可以得到一个最新版本的OpenGL了。呵呵,不要高兴的太早。MESA是软件实现的,就是说没有用到硬件加速,因此运行起来会较慢,尤其是使用新版本的OpenGL所规定的一些高级特性时,慢得几乎无法忍受。MESA不能让你用旧的显卡玩新的游戏(很可能慢得没法玩),但是如果你只是想学习或尝试一下新版本OpenGL的各种功能,MESA可以满足你的一部分要求。
OpenGL扩展
OpenGL版本的更新并不快。如果某种技术变得流行起来,但是OpenGL标准中又没有相关的规定对这种技术提供支持,那就只能通过扩展来实现了。
厂商在发行OpenGL时,除了遵照OpenGL标准,提供标准所规定的各种功能外,往往还提供其它一些额外的功能,这就是扩展。
扩展的存在,使得各种新的技术可以迅速的被应用到OpenGL中。比如“多重纹理”,它是在OpenGL 1.3中才被加入到标准中的,在OpenGL 1.3出现以前,很多OpenGL实现都通过扩展来支持“多重纹理”。这样,即使OpenGL版本不更新,只要增加新的扩展,也可以提供新的功能了。这也说明,即使OpenGL版本较低,也不一定不支持一些高版本OpenGL才提供的功能。实际上某些OpenGL 1.5的实现,也可能提供了最新的OpenGL 2.1版本所规定的大部分功能。
当然扩展也有缺点,那就是程序在运行的时候必须检查每个扩展功能是否被支持,导致编写程序代码复杂。

扩展的名字
每个OpenGL扩展,都必须向OpenGL的网站注册,确认后才能成为扩展。注册后的扩展有编号和名字。编号仅仅是一个序号,名字则与扩展所提供的功能相关。
名字用下划线分为三部分。举例来说,一个扩展的名字可能为:GL_NV_half_float,其意义如下:
第一部分为扩展的目标。比如GL表示这是一个OpenGL扩展。如果是WGL则表示这是一个针对Windows的OpenGL扩展,如果是GLX则表示这是一个针对linux的X Window系统的OpenGL扩展。
第二部分为提供扩展的厂商。比如NV表示这是nVidia公司所提供的扩展。相应的还有ATI, IBM, SGI, APPLE, MESA等。
剩下的部分就表示扩展所提供的内容了。比如half_float,表示半精度的浮点数,每个浮点数的精度只有单精度浮点数的一半,因此只需要两个字节就可以保存。这种扩展功能可以节省内存空间,也节省从内存到显卡的数据传输量,代价就是精确度有所降低。
EXT扩展和ARB扩展
最初的时候,每个厂商都提供自己的扩展。这样导致的结果就是,即使是提供相同的功能,不同的厂商却提供不同的扩展,这样在编写程序的时候,使用一种功能就需要依次检查每个可能支持这种功能的扩展,非常繁琐。
于是出现了EXT扩展和ARB扩展。
EXT扩展是由多个厂商共同协商后形成的扩展,在扩展名字中,“提供扩展的厂商”一栏将不再是具体的厂商名,而是EXT三个字母。比如GL_EXT_bgra,就是一个EXT扩展。
ARB扩展不仅是由多个厂商共同协商形成,还需要经过OpenGL体系结构审核委员会(即ARB)的确认。在扩展名字中,“提供扩展的厂商”一栏不再是具体的厂商名字,而是ARB三个字母。比如GL_ARB_imaging,就是一个ARB扩展。
通常,一种功能如果有多个厂商提出,则它成为EXT扩展。在以后的时间里,如果经过了ARB确认,则它成为ARB扩展。再往后,如果OpenGL的维护者认为这种功能需要加入到标准规定中,则它不再是扩展,而成为标准的一部分。
例如point_parameters,就是先有GL_EXT_point_parameters,再有GL_ARB_point_parameters,最后到OpenGL 1.4版本时,这个功能为标准规定必须提供的功能,不再是一个扩展。
在使用OpenGL所提供的功能时,应该按照标准功能、ARB扩展、EXT扩展、其它扩展这样的优先顺序。例如有ARB扩展支持这个功能时,就不使用EXT扩展。
在程序中,判断OpenGL是否支持某个扩展
前面已经说过,glGetString(GL_EXTENSIONS)会返回当前OpenGL所支持的所有扩展的名字,中间用空格分开,这就是我们判断是否支持某个扩展的依据。

#include <string.h>
// 判断OpenGL是否支持某个指定的扩展
// 若支持,返回1。否则返回0。
int ha***tension(const char* name) {
    const char* extensions = (const char*)glGetString(GL_EXTENSIONS);
    const char* end = extensions + strlen(extensions);
    size_t name_length = strlen(name);
    while( extensions < end ) {
        size_t position = strchr(extensions, ' ') - extensions;
        if( position == name_length &&
                strncmp(extensions, name, position) == 0 )
            return 1;
         extensions += (position + 1);
     }
    return 0;
}



上面这段代码,判断了OpenGL是否支持指定的扩展,可以看到,判断时完全是靠字符串处理来实现的。循环检测,找到第一个空格,然后比较空格之前的字符串是否与指定的名字一致。若一致,说明扩展是被支持的;否则,继续比较。若所有内容都比较完,则说明扩展不被支持。
编写程序调用扩展的功能
扩展的函数、常量,在命名时与通常的OpenGL函数、常量有少许区别。那就是扩展的函数、常量将以厂商的名字作为后缀。
比如ARB扩展,所有ARB扩展的函数,函数名都以ARB结尾,常量名都以_ARB结尾。例如:
glGenBufferARB(函数)
GL_ARRAY_BUFFER_ARB(常量)
如果已经知道OpenGL支持某个扩展,则如何调用扩展中的函数?大致的思路就是利用函数指针。但是不幸的是,在不同的操作系统中,取得这些函数指针的方法各不相同。为了能够在各个操作系统中都能顺利的使用扩展,我向大家介绍一个小巧的工具:GLEE。
GLEE是一个开放源代码的项目,可以从网络上搜索并下载。其代码由两个文件组成,一个是GLee.c,一个是GLee.h。把两个文件都放到自己的源代码一起编译,运行的时候,GLee可以自动的判断所有扩展是否被支持,如果支持,GLEE会自动读取对应的函数,供我们调用。
我们自己编写代码时,需要首先包含GLee.h,然后才包含GL/glut.h(注意顺序不能调换),然后就可以方便的使用各种扩展功能了。

#include "GLee.h"
#include // 注意顺序,GLee.h要在glut.h之前使用



GLEE也可以帮助我们判断OpenGL是否支持某个扩展,因此有了GLEE,前面那个判断是否支持扩展的函数就不太必要了。
示例代码
让我们用一段示例代码结束本课。
我们选择一个目前绝大多数显卡都支持的扩展GL_ARB_window_pos,来说明如何使用GLEE来调用OpenGL扩展功能。通常我们在绘制像素时,需要用glRasterPos*函数来指定绘制的位置。但是,glRasterPos*函数使用的不是屏幕坐标,例如指定(0, 0)不一定是左下角,这个坐标需要经过各种变换(参见第五课,变换),最后才得到屏幕上的窗口位置。
通过GL_ARB_window_pos扩展,我们可以直接用屏幕上的坐标来指定绘制的位置,不再需要经过变换,这样在很多场合会显得简单。

#include "GLee.h"
#include

void display(void) {
     glClear(GL_COLOR_BUFFER_BIT);

    if( GLEE_ARB_window_pos ) { // 如果支持GL_ARB_window_pos
                                 // 则使用glWindowPos2iARB函数,指定绘制位置
        printf("支持GL_ARB_window_pos\n");
        printf("使用glWindowPos函数\n");
         glWindowPos2iARB(100, 100);
     } else {                     // 如果不支持GL_ARB_window_pos
                                 // 则只能使用glRasterPos*系列函数
                                 // 先计算出一个经过变换后能够得到
                                 //    (100, 100)的坐标(x, y, z)
                                 // 然后调用glRasterPos3d(x, y, z);
         GLint viewport[4];
         GLdouble modelview[16], projection[16];
         GLdouble x, y, z;

        printf("不支持GL_ARB_window_pos\n");
        printf("使用glRasterPos函数\n");

         glGetIntegerv(GL_VIEWPORT, viewport);
         glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
         glGetDoublev(GL_PROJECTION_MATRIX, projection);
         gluUnProject(100, 100, 0.5, modelview, projection, viewport,
             &x, &y, &z);
         glRasterPos3d(x, y, z);
     }

     { // 绘制一个5*5的像素块
         GLubyte pixels[5][5][4];
         // 把像素中的所有像素都设置为红色
        int i, j;
        for(i=0; i<5; ++i)
            for(j=0; j<5; ++j) {
                 pixels[i][j][0] = 255; // red
                 pixels[i][j][1] = 0;    // green
                 pixels[i][j][2] = 0;    // blue
                 pixels[i][j][3] = 255; // alpha
             }
         glDrawPixels(5, 5, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
     }

     glutSwapBuffers();
}

int main(int argc, char* argv[]) {
     glutInit(&argc, argv);
     glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
     glutInitWindowPosition(100, 100);
     glutInitWindowSize(512, 512);
     glutCreateWindow("OpenGL");
     glutDisplayFunc(&display);
     glutMainLoop();
}



可以看到,使用了扩展以后,代码会简单得多了。不支持GL_ARB_window_pos扩展时必须使用较多的代码才能实现的功能,使用GL_ARB_window_pos扩展后即可简单的解决。
如果把代码修改一下,不使用扩展而直接使用else里面的代码,可以发现运行效果是一样的。
工具软件
在课程的最后我还向大家介绍一个免费的工具软件,这就是OpenGL Extension Viewer(各大软件网站均有下载,请自己搜索之),目前较新的版本是3.0。
这个软件可以查看自己计算机系统的OpenGL信息。包括OpenGL版本、提供厂商、设备名称、所支持的扩展等。
软件可以查看的信息很详细,比如查看允许的最大纹理大小、最大光源数目等。
在查看扩展时,可以在最下面一栏输入扩展的名字,按下回车后即可连接到OpenGL官方网站,查找关于这个扩展的详细文档,非常不错。
可以根据电脑的配置情况,自动连接到对应的官方网站,方便下载最新驱动。(比如我是nVidia的显卡,则连接到nVidia的驱动下载页面)
可以进行OpenGL测试,看看运行起来性能如何。
可以给出总体报告,如果一些比较重要的功能不被支持,则会用粗体字标明。
软件还带有一个数据库,可以查询各厂商、各型号的显卡对OpenGL各种扩展的支持情况。
小结

本课介绍了OpenGL版本和OpenGL扩展。
OpenGL从诞生到现在,经历了1.0, 1.1, 1.2, 1.2.1, 1.3, 1.4, 1.5, 2.0, 2.1这些版本。
每个系统中的OpenGL版本可能不同。使用glGetString(GL_VERSION);可以查看当前的OpenGL版本。
新版本的OpenGL将兼容旧版本的OpenGL,同时提供更多的新特性和新功能。
OpenGL在实现时可以通过扩展,来提供额外的功能。
OpenGL扩展有厂家扩展、EXT扩展、ARB扩展。通常应该尽量使用标准功能,其次才是ARB扩展、EXT扩展、厂家扩展。
GLEE是一个可以免费使用的工具,使用它可以方便的判断当前的OpenGL是否支持某扩展,也可以方便的调用扩展。
OpenGL Extension Viewer是一个软件,可以检查系统所支持OpenGL的版本、支持的扩展、以及很多的详细信息。

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