OpenGL版本和OpenGL扩展

时间:2022-09-10 17:48:40
 

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 hasExtension(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 <GL/glut.h> // 注意顺序,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 <GL/glut.h>

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的版本、支持的扩展、以及很多的详细信息。