本文参考了《计算机图形学》(Donald Hearn著)的第2.9节。
OpenGL基本函数库用来描述图元、属性、几何变换、观察变换和进行许多其他的操作。OpenGL被设计成与硬件无关,因此输入、输出函数等许多操作均不包括在其基本库当中。但在为OpenGL开发的辅助库中有输入和输出函数及许多附加函数。
1 基本的OpenGL语法
OpenGL基本库(也称为核心库)中的函数名要以gl为前缀,且函数名中每一组成词的第一个字母要大写。例如:glBegin,glClear,glCopyPixels等。
有些函数要求一个(或多个)变量符号常量赋值,如参数名、参数的值或特定的模式,所以这些常量均以大写字母GL开头。另外,常量名中各组成词均采用大写,单词之间用下划线(_)隔开。例如:GL_2D,GL_RGB,GL_POLYGON,GL_AMBIENT_AND_DIFFUSE等。
OpenGL函数也要求有专门的数据类型。例如,OpenGL函数的参数可以要求一个32位整数类型的值,但是不同机器上的整数描述范围可能有所不同,OpenGL采用专门的内置数据类型名来描述数据类型。例如:GLbyte,GLshort,GLint,GLfloat等。每个数据类型名以大写字母GL开头,名字中的其余部分是用小写字母表示的标准数据类型名。
2 相关库
除OpenGL的基本库(核心库)之外,还有一些用于处理专门操作的附加库。OpenGL实用函数(OpenGL Utility,GLU)提供了一些例程,可以设置观察和投影矩阵,利用线条和多边形近似法来描述复杂对象,使用线性近似法显示二次曲线和样条曲线,处理表面绘制操作,以及完成其他的复杂任务。每一个OpenGL实现中都包括GLU库,所以GLU函数名均以前缀glu开头。还有一个成为Open Inventor的基于OpenGL的面向对象工具包,它为交互式三维应用提供函数和预定义的对象形状,该工具采用C++编程。
为了使用OpenGL建立一个图形,必须首先在视频屏幕上设置显示窗口。它是一个屏幕上的简单矩形,图形将在其中显示。我们不能直接使用基本的OpenGL函数来创建显示窗口,因为该库中只有与设备无关的函数,且窗口管理操作依赖于所使用的计算机。但是,有多个支持各种计算机上的OpenGL函数的窗口系统库。OpenGL的X窗口系统扩充(OpenGL Extension to the X Window System,GLX)提供了一组以glX为前缀的函数。Apple系统可以使用Apple GL(AGL)接口进行窗口管理操作,该库的函数名以agl为前缀。对于Microsoft的Windows系统,WGL函数提供了窗口系统到OpenGL的接口,这些函数以wgl为前缀。OpenGL实用函数工具包(OpenGL Utility Toolkit,GLUT)提供了与任意屏幕窗口系统进行交互的函数库。GLUT库函数以glut为前缀,该库中也包含了描述和绘制二次和样条曲线及曲面的方法。
由于GLUT是一个与其他依赖于设备的窗口系统之间的接口,我们可以利用GLUT使得程序成为与设备无关的。关于GLUT最新版的信息及源码下载请关注以下链接:
http://www.opengl.org/resources/libraries/glut/
3 头文件
在我们所有的程序中,需要包含一个头文件以引进OpenGL核心库。在许多应用中,我们都需要GLU,需要包含头文件来引入窗口系统。例如,对于Microsoft的Windows系统,存取WGL的头文件是windows.h。该头文件必须列在OpenGL和GLU头文件之前,因为它包含了OpenGL库的Microsoft版本所需的宏。因此,源程序的开头几行为:
1 #include <windows.h> 2 #include <GL/gl.h> 3 #include <GL/glu.h>
但是,如果我们使用GLUT处理窗口管理操作,就不需要引入gl.h和glu.h,因为GLUT保证了它们的正确引入。因此我们可以使用:
1 #include <GL/glut.h>
来替代OpenGL和GLU的头文件。我们也可以再引入gl.h和glu.h,但这将造成冗余且影响了程序的可移植性。
此外,我们总是要C++程序所需的头文件,例如:
#include <cstdio> #include <cstdlib> #include <cmath>
4 使用GLUT进行显示窗口管理
我们从使用简化的最少操作来显示一个图形开始。使用OpenGL实用库的第一步是初始化GLUT。该初始化函数也能处理任何命令行变量,但我们不需要在第一个例子程序中使用参数。完成GLUT初始化的语句是:
glutInit(&argc, argv);
接着,需要说明的是显示窗口在创建时需要给定一个标题。这是用下列语句实现的:
glutCreateWindow("An Example OpenGL Program");
这里的单一变量可以是用作显示窗口标题的任意字符串。
下面,我们需要指定显示窗口中要显示什么内容。为此,我们用OpenGL函数创建一个图并将图的定义传递给GLUT函数glutDisplayFunc,即将图赋给显示窗口。作为一个例子,我们假定在成为lineSegment的过程中已经有了线段的OpenGL描述程序。则调用下列函数就将线段描述送到显示窗口。
glutDisplayFunc(lineSegment);
但是,显示窗口还未出现在屏幕上。我们需要另一个GLUT函数来完成窗口处理操作。在执行下列语句后,所有已创建的显示窗口连同其中的图形内容将被激活。
glutMainLoop();
该函数必须是程序中的最后一个。它显示初始图形并使程序进入检查鼠标或键盘等设备输入的无穷循环之中。我们的第一个例子不是交互式的,所以程序仅显示其中的图形直到显示窗口关闭。
尽管我们创建的显示窗口有默认的位置和大小,但还是可以使用另外的GLUT函数来设定这些参数。glutInitWindowPosition可以用来给出显示窗口左上角的初始位置。该位置用以屏幕左上角为原点的整数坐标来表示。例如,下面的语句指定了显示窗口左上角应该在屏幕左边界向右50个像素、屏幕上边界向下100个像素的位置上。
glutInitWindowPosition(50, 100);
类似的,glutInitWindowSize函数用来设定显示窗口的初始化宽度和高度的像素点数。因此,要指定一个宽度为400像素,高度为300像素的显示窗口,相应的语句为:
glutInitWindowSize(400, 300);
在显示窗口已出现在屏幕上之后,我们可重新设置它的位置和大小。
我们还可以使用glutDisplayMode函数来设定显示窗口的缓存和颜色模型等选项。该函数的变量使用符号化GLUT常量来赋值。例如,下面的命令指出显示窗口使用单个缓存且使用RGB颜色模型来设定颜色值。
glutInitDisplayMode(GL_SINGLE | GL_RGB);
传递给该函数的常量值利用逻辑或操作组合起来。实际上,单缓存和RGB颜色模型是默认的选项,但现在使用该函数是为了强调要使用这些选项来设定我们的显示。
5 完整的OpenGL程序
在给出构成一个完整程序的所有部分之前还是有一些任务需要完成。对于显示窗口,我们可以选择背景颜色。我们需要组织一个过程来包含创建显示的图形所必需的OpenGL函数。将窗口的背景颜色设为白色,可以使用一下语句:
glClearColor(1.0, 1.0, 1.0, 0.0);
该函数的前面三个变量将红、绿、蓝三个颜色分量设定为1.0,这样就得到了白色的显示窗口。如果不是1.0,而是将这些分量都设置为0.0,则得到黑色的背景。如果红、绿、蓝三分量的每个设定为0.0到1.0之间,将得到某种灰色。glClearColor的第四个参数称为指定颜色的α(alpha)值,α的一个用途是作为“调和”参数。在激活OpenGL调和参数时,α值用来为两个重叠对象确定结果颜色。α值为0.0表示完全透明的对象,而α值为1.0表示不透明对象。调和操作暂时不会使用,因此α值与我们前面提到的程序无关,现在我们简单设定为0.0。
尽管glClearColor将某颜色赋给显示窗口,但它不能让显示窗口在屏幕上出现。要使赋值的窗口得到显示,必须引入下面的OpenGL函数。
glClear(GL_COLOR_BUFFER_BIT);
GL_COLOR_BUFFER_BIT是一个OpenGL符号常量,用来指定它是颜色缓存(刷新缓存)中的位值,该缓存将使用glClearColor函数中指定的值来设定。
除了设定显示缓存的背景色,还可以为要显示的场景中的对象选择各种颜色模型。对于最初的程序设计例子,我们简单地把对象颜色设定为红色。
glColor3f(1.0, 0.0, 0.0);
glColor函数的后缀3f表示我们在指定3个RGB颜色分量时使用浮点数,这些值必须在0.0到1.0范围内。
在第一个程序中,我们要简单的显示一条二维线段。为此,需要告诉OpenGL怎样将图形投影到显示窗口中,因为在OpenGL中生成二维线段看做为生成三维线段的特例。因此,尽管我们只要简单的生成二维线段,OpenGL还是采用完整的三维观察操作来处理该图形。我们可以使用下面两个函数来设置投影类型(模式)和其他观察参数。
glMatrixMode(GL_PROJECTION); gluOrtho2D(0.0, 200.0, 0.0, 150.0);
这表示使用正投影将世界坐标系二维矩形区域的内容映射到屏幕上,区域的x坐标值从0到200,y坐标值从0到150。只要是在该矩形内定义的对象,都会出现在显示窗口中,任何在坐标范围外的内容都不会显示出来。因此GLU函数gluOrtho2D定义了显示窗口以(0.0, 0.0)为左下角,以(200.0, 150.0)为右上角。由于我们仅仅描述了一个二维对象,正投影只是将我们前面定义的图形“贴”到显示窗口中。现在我们使用与显示窗口具有一样纵横比的世界坐标矩形,从而使图形不会产生形变。后面讲考虑如何在不依赖显示窗口描述的情况下保持纵横比。
最后,要调用合适的函数来建立线段。下面的程序定义了一个从整数笛卡尔端点坐标(185,15)到(10,145)的二维直线段。
1 glBegin(GL_LINES); 2 glVertex2i(180, 15); 3 glVertex2i(10, 145); 4 glEnd();
6 创建OpenGL工程
本文中使用的集成开发工具为Code::Blocks 12.11,这是一个功能强大的跨平台开发环境。在其开发环境中已经提供了OpenGL开发的支持,如果你是按照默认的安装路径安装的话,则在以下路径中可以找到相关的头文件:
C:Program Files (x86)CodeBlocksMinGWincludeGL
可以看到其中只提供了gl.h和glu.h两个头文件,并没有提供GLUT相关头文件,因此我们还需要在网上下载,由于在此使用的Windows平台,所以可以下载相应的Windows平台对应的已经编译好了的文件,现在地址如下:
http://www.opengl.org/resources/libraries/glut/glut_downloads.php
下载下来的压缩包名称为“glut-3.7.6-bin.zip”,将其解压后得到文件夹中的内容为:
我们需要将解压后的文件添加到集成开发环境中的相应目录中去,首先将头文件glut.h添加到include目录中的GL子目录下,完整路径为:
C:Program Files (x86)CodeBlocksMinGWincludeGL
接着需要将库文件glut32.lib和glut32.dll添加到lib目录下,完整路径为:
C:Program Files (x86)CodeBlocksMinGWlib
然后利用Code::Blocks创建一个新的GLUT工程,新建工程选项如下:
然后设置工程名称及路径,如下图所示:
再下一步需要选择GLUT的位置,由于我们之前已经将相关的文件添加到了IDE的库文件目录下,因此路径设置为如下图所示位置:
再下一步单击完成,这样工程中就自动添加了一个demo程序,但是例子程序在Windows下不能直接运行,还需要在程序的最开始部分添加以下包含语句才行:
#include <windows.h>
运行结果如下:
在本示例中我们需要将main.c的内容替换为如下:
1 #include <windows.h> 2 #include <GL/glut.h> 3 4 void init(void) 5 { 6 glClearColor(1.0, 1.0, 1.0, 0.0); 7 glMatrixMode(GL_PROJECTION); 8 gluOrtho2D(0.0, 200.0, 0.0, 150.0); 9 } 10 11 void lineSegment(void) 12 { 13 glClear(GL_COLOR_BUFFER_BIT); 14 glColor3f(1.0, 0.0, 0.0); 15 glBegin(GL_LINES); 16 glVertex2i(180, 15); 17 glVertex2i(10, 145); 18 glEnd(); 19 20 glFlush(); 21 } 22 23 int main(int argc, char **argv) 24 { 25 glutInit(&argc, argv); 26 glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); 27 glutInitWindowPosition(50, 100); 28 glutInitWindowSize(400, 300); 29 glutCreateWindow("An Example OpenGL Program"); 30 31 init(); 32 glutDisplayFunc(lineSegment); 33 glutMainLoop(); 34 }
程序运行结果如下:
说明:在Code::Blocks中原生态提供了对OpenGL编程的支持,只是在新建工程的时候需要选择“OpenGL Project”,如下图所示:
工程选项设置完成后,会自动生成一个OpenGL的demo程序,编译运行结果如下:
使用这种方式不需要添加GLUT所需要的头文件及库文件。