原文:http://blog.chinaunix.net/uid-20638550-id-1909184.html
分类:
十一、位图与图像
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 );
/* 设置以下线段的宽度为 1 */
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);
}
/* 第六行绘制的是一条虚点线,其中线型模式每个元素被重复操作5次 */
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)
{
/* 填充模式定义 (32x32) */
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,则三角平面法向的计算方式见下列一段代码:
/* ------ get value of N (normal vector) ---------- */
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
|
图13-5 曲面顶点的平均法向计算 |
13.2.2 法向定义
OpenGL法向定义函数为:
void glNormal3{bsifd}(TYPE nx,TYPE ny,TYPE nz);
void glNormal3{bsifd}v(const TYPE *v);
设置当前法向值。非向量形式定义法向采用第一种方式,即在函数中分别给出法向三个分量值nx、ny和nz;向量形式定义采用第二种,即将v设置为一个指
向拥有三个元素的指针,例如v[3]={nx,ny,nz}。因为法向的各分量值只定义法向的方向,因此它的大小不固定,但建议最好将各值限制在
[-1.0,1.0]之间,即法向归一化;若法向不归一化,则在定义法向之前必须启动法向归一,即调用函数
glEnable(GL_NORMALIZE),这样会降低整个程序运行性能。下面举出一个自己定义法向的例子:
例13-3 自定义颜色立方体法向例程(nmlcolr.c)
#include "glos.h"
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
static GLfloat p1[]={0.5,-0.5,-0.5}, p2[]={0.5,0.5,-0.5},
p3[]={0.5,0.5,0.5}, p4[]={0.5,-0.5,0.5},
p5[]={-0.5,-0.5,0.5}, p6[]={-0.5,0.5,0.5},
p7[]={-0.5,0.5,-0.5}, p8[]={-0.5,-0.5,-0.5};
static GLfloat m1[]={1.0,0.0,0.0}, m2[]={-1.0,0.0,0.0},
m3[]={0.0,1.0,0.0}, m4[]={0.0,-1.0,0.0},
m5[]={0.0,0.0,1.0}, m6[]={0.0,0.0,-1.0};
static GLfloat c1[]={0.0,0.0,1.0}, c2[]={0.0,1.0,1.0},
c3[]={1.0,1.0,1.0}, c4[]={1.0,0.0,1.0},
c5[]={1.0,0.0,0.0}, c6[]={1.0,1.0,0.0},
c7[]={0.0,1.0,0.0}, c8[]={1.0,1.0,1.0};
void myinit(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);
void DrawColorBox(void);
void myinit(void)
{
GLfloat light_ambient[]={0.3,0.2,0.5};
GLfloat light_diffuse[]={1.0,1.0,1.0};
GLfloat light_position[] = { 2.0, 2.0, 2.0, 1.0 };
GLfloat light1_ambient[]={0.3,0.3,0.2};
GLfloat light1_diffuse[]={1.0,1.0,1.0};
GLfloat light1_position[] = { -2.0, -2.0, -2.0, 1.0 };
glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
glLightfv(GL_LIGHT0, GL_POSITION, light_position);
glLightfv(GL_LIGHT1, GL_AMBIENT, light1_ambient);
glLightfv(GL_LIGHT1, GL_DIFFUSE, light1_diffuse);
glLightfv(GL_LIGHT1, GL_POSITION, light1_position);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_LIGHT1);
glDepthFunc(GL_LESS);
glEnable(GL_DEPTH_TEST);
glColorMaterial(GL_FRONT_AND_BACK,GL_DIFFUSE);
glEnable(GL_COLOR_MATERIAL);
}
void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glRotatef(45,0.0,1.0,0.0);
glRotatef(315,0.0,0.0,1.0);
DrawColorBox();
glPopMatrix();
glFlush();
}
void CALLBACK myReshape(GLsizei w, GLsizei h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= h)
glOrtho (-1.5, 1.5, -1.5*(GLfloat)h/(GLfloat)w,1.50*(GLfloat)h/(GLfloat)w, -10.0, 10.0);
else
glOrtho (-1.5*(GLfloat)w/(GLfloat)h,1.5*(GLfloat)w/(GLfloat)h, -1.5, 1.5, -10.0, 10.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity ();
}
void DrawColorBox(void)
{
glBegin (GL_QUADS);
glColor3fv(c1);
glNormal3fv(m1);
glVertex3fv(p1);
glColor3fv(c2);
glVertex3fv(p2);
glColor3fv(c3);
glVertex3fv(p3);
glColor3fv(c4);
glVertex3fv(p4);
glColor3fv(c5);
glNormal3fv(m5);
glVertex3fv(p5);
glColor3fv(c6);
glVertex3fv(p6);
glColor3fv(c7);
glVertex3fv(p7);
glColor3fv(c8);
glVertex3fv(p8);
glColor3fv(c5);
glNormal3fv(m3);
glVertex3fv(p5);
glColor3fv(c6);
glVertex3fv(p6);
glColor3fv(c3);
glVertex3fv(p3);
glColor3fv(c4);
glVertex3fv(p4);
glColor3fv(c1);
glNormal3fv(m4);
glVertex3fv(p1);
glColor3fv(c2);
glVertex3fv(p2);
glColor3fv(c7);
glVertex3fv(p7);
glColor3fv(c8);
glVertex3fv(p8);
glColor3fv(c2);
glNormal3fv(m5);
glVertex3fv(p2);
glColor3fv(c3);
glVertex3fv(p3);
glColor3fv(c6);
glVertex3fv(p6);
glColor3fv(c7);
glVertex3fv(p7);
glColor3fv(c1);
glNormal3fv(m6);
glVertex3fv(p1);
glColor3fv(c4);
glVertex3fv(p4);
glColor3fv(c5);
glVertex3fv(p5);
glColor3fv(c8);
glEnd();
}
void main(void)
{
auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);
auxInitPosition (0, 0, 500,400);
auxInitWindow ("ColorBox");
myinit();
auxReshapeFunc (myReshape);
auxMainLoop(display);
}
以上程序运行结果是一个自定义法向的彩色正方体,其中每个顶点的颜色值不一样,且为光滑的明暗处理模式。
|
||
图13-6 自定义法向的彩色立方体 |
13.3、曲线生成
计算机图形学中,所有的光滑曲线都采用线段逼近来模拟,而且许多有用的曲线在数学上只用少数几个参数(如控制点等)来描述。本节简要地介绍一下OpenGL中Bezier曲线的绘制方法。
13.3.1 曲线绘制举例
下面我们来看一个简单的例子,这是用四个控制顶点来画一条三次Bezier曲线。程序如下:
例13-4 Bezier曲线绘制例程(bzcurve.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);
GLfloat ctrlpoints[4][3] = {
{ -4.0, -4.0, 0.0 }, { -2.0, 4.0, 0.0 },
{ 2.0, -4.0, 0.0 }, { 4.0, 4.0, 0.0 }
};
void myinit(void)
{
glClearColor(0.0, 0.0, 0.0, 1.0);
glMap1f(GL_MAP1_VERTEX_3, 0.0, 1.0, 3, 4, &ctrlpoints[0][0]);
glEnable(GL_MAP1_VERTEX_3);
glShadeModel(GL_FLAT);
}
void CALLBACK display(void)
{
int i;
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(1.0, 1.0, 1.0);
glBegin(GL_LINE_STRIP);
for (i = 0; i <= 30; i++)
glEvalCoord1f((GLfloat) i/30.0);
glEnd();
/* 显示控制点 */
glPointSize(5.0);
glColor3f(1.0, 1.0, 0.0);
glBegin(GL_POINTS);
for (i = 0; i < 4; i++)
glVertex3fv(&ctrlpoints[i][0]);
glEnd();
glFlush();
}
void CALLBACK myReshape(GLsizei w, GLsizei h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= h)
glOrtho(-5.0, 5.0, -5.0*(GLfloat)h/(GLfloat)w, 5.0*(GLfloat)h/(GLfloat)w, -5.0, 5.0);
else
glOrtho(-5.0*(GLfloat)w/(GLfloat)h, 5.0*(GLfloat)w/(GLfloat)h, -5.0, 5.0, -5.0, 5.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void main(void )
{
auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);
auxInitPosition (0, 0, 500, 500);
auxInitWindow ("Bezier Curves");
myinit();
auxReshapeFunc (myReshape);
auxMainLoop(display);
}
图13-7 一条光滑的Bezier曲线 |
13.3.2 曲线定义和启动
OpenGL中曲线定义的函数为:
void glMap1{fd}(GLenum target,TYPE u1,TYPE u2,GLint stride, GLint order,const TYPE *points);
函数的第一个参数target指出控制顶点的意义以及在参数points中需要提供多少值,具体值见表13-1所示。参数points指针可以指向控制
点集、RGBA颜色值或纹理坐标串等。例如若target是GL_MAP1_COLOR_4,则就能在RGBA四维空间中生成一条带有颜色信息的曲线,这
在数据场可视化中应用极广。参数u1和u2,指明变量U的范围,U一般从0变化到1。参数stride是跨度,表示在每块存储区内浮点数或双精度数的个
数,即两个控制点间的偏移量,比如上例中的控制点集ctrpoint[4][3]的跨度就为3,即单个控制点的坐标元素个数。函数参数order是次数加
1,叫阶数,与控制点数一致。
参数 | 意义 |
GL_MAP1_VERTEX_3 | x,y,z 顶点坐标 |
GL_MAP1_VERTEX_4 | x,y,z,w 顶点坐标 |
GL_MAP1_INDEX | 颜色表 |
GL_MAP1_COLOR_4 | R,G,B,A |
GL_MAP1_NORMAL | 法向量 |
GL_MAP1_TEXTURE_COORD_1 | s 纹理坐标 |
GL_MAP1_TEXTURE_COORD_2 | s,t 纹理坐标 |
GL_MAP1_TEXTURE_COORD_3 | s,t,r 纹理坐标 |
GL_MAP1_TEXTURE_COORD_4 | s,t,r,q 纹理坐标 |
表13-1 用于glMap1*()控制点的数据类型
|
曲线定义后,必须要启动,才能进行下一步的绘制工作。启动函数仍是glEnable(),其中参数与glMap1*()的第一个参数一致。同样,关闭函数为glDisable(),参数也一样。
13.3.3 曲线坐标计算
这里提到的坐标概念是广义的,与以前定义的有点不同,具体地说就是表13-1所对应的类型值。OpenGL曲线坐标计算的函数形式如下:
void glEvalCoord1{fd}[v](TYPE u);
产生曲线坐标值并绘制。参数u是定义域内的值,这个函数调用一次只产生一个坐标。
13.3.4 定义均匀间隔曲线坐标值
在使用glEvalCoord1*()计算坐标,因为u可取定义域内的任意值,所以由此计算出的坐标值也是任意的。但是,目前用得最普遍的仍是取等间隔
值。要获得等间隔值,OpenGL提供了两个函数,即先调用glMapGrid1*()定义一个一维网格,然后用glEvalMesh1()计算响应的坐
标值。下面详细解释这两个函数:
void glMapGrid1{fd}(GLint n,TYPE u1,TYPE u2);
定义一个网格,从u1到u2分为n步,它们是等间隔的。实际上,这个函数定义的是参数空间网格。
void glEvalMesh1(GLenum mode,GLint p1,GLint p2);
计算并绘制坐标点。参数mode可以是GL_POINT或GL_LINE,即沿曲线绘制点或沿曲线绘制相连的线段。这个函数的调用效果同在p1和p2之
间的每一步给出一个glEvalCoord1()的效果一样。从编程角度来说,除了当i=0或i=n,它准确以u1或u2作为参数调用
glEvalCoord1()之外,它等价于一下代码:
glBegin(GL_POINT); /* glBegin(GL_LINE_STRIP); */
for(i=p1;i<=p2;i++)
glEvalCoord1(u1+i*(u2-u1)/n);
glEnd();
13.4、曲面构造
同样,计算机图形学中的所有光滑曲面也都采用多边形逼近来绘制,而且许多有用的曲面在数学上也只用少数几个参数(如控制点或网等)来描述。通常,若用
16个控制点描述一个曲面,要比用1000多个三角形和每个顶点的法向信息要节省很多内存。而且,1000个三角形仅仅只逼近曲面,而控制点可以精确地描
述实际曲面,且可自动计算法向。本节简要地介绍一下OpenGL中Bezier曲面的绘制方法,所有相关的函数都与曲线的情况类似,只是二维空间而已。
13.4.1 曲面定义和坐标计算
曲面定义函数为:
void glMap2{fd}(GLenum target,TYPE u1,TYPE u2,GLint ustride,GLint uorder,
TYPE v1,TYPE v2,GLint vstride,GLint vorder,TYPE points);
参数target可以是表13-1中任意值,不过需将MAP1改为MAP2。同样,启动曲面的函数仍是glEnable(),关闭是
glDisable()。u1、u2为u的最大值和最小值;v1、v2为v的最大值和最小值。参数ustride和vstride指出在控制点数组中u和
v向相邻点的跨度,即可从一个非常大的数组中选择一块控制点长方形。例如,若数据定义成如下形式:
GLfloat ctlpoints[100][100][3];
并且,要用从ctlpoints[20][30]开始的4x4子集,选择ustride为100*3,vstride为3,初始点设置为ctlpoints[20][30][0]。最后的参数都是阶数,uorder和vorder,二者可以不同。曲面坐标计算函数为:
void glEvalCoord2{fd}[v](TYPE u,TYPE v);
产生曲面坐标并绘制。参数u和v是定义域内的值。下面看一个绘制Bezier曲面的例子:
例13-5 Bezier网状曲面绘制例程(bzwiresf.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);
/* 控制点的坐标 */
GLfloat ctrlpoints[4][4][3] = {
{{-1.5, -1.5, 2.0}, {-0.5, -1.5, 2.0},
{0.5, -1.5, -1.0}, {1.5, -1.5, 2.0}},
{{-1.5, -0.5, 1.0}, {-0.5, 1.5, 2.0},
{0.5, 0.5, 1.0}, {1.5, -0.5, -1.0}},
{{-1.5, 0.5, 2.0}, {-0.5, 0.5, 1.0},
{0.5, 0.5, 3.0}, {1.5, -1.5, 1.5}},
{{-1.5, 1.5, -2.0}, {-0.5, 1.5, -2.0},
{0.5, 0.5, 1.0}, {1.5, 1.5, -1.0}}
};
void myinit(void)
{
glClearColor (0.0, 0.0, 0.0, 1.0);
glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4, &ctrlpoints[0][0][0]);
glEnable(GL_MAP2_VERTEX_3);
glMapGrid2f(20, 0.0, 1.0, 20, 0.0, 1.0);
glEnable(GL_DEPTH_TEST);
}
void CALLBACK display(void)
{
int i, j;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glColor3f(0.3, 0.6, 0.9);
glPushMatrix ();
glRotatef(35.0, 1.0, 1.0, 1.0);
for (j = 0; j <= 8; j++)
{
glBegin(GL_LINE_STRIP);
for (i = 0; i <= 30; i++)
glEvalCoord2f((GLfloat)i/30.0, (GLfloat)j/8.0);
glEnd();
glBegin(GL_LINE_STRIP);
for (i = 0; i <= 30; i++)
glEvalCoord2f((GLfloat)j/8.0, (GLfloat)i/30.0);
glEnd();
}
glPopMatrix ();
glFlush();
}
void CALLBACK myReshape(GLsizei w, GLsizei h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= h)
glOrtho(-4.0, 4.0, -4.0*(GLfloat)h/(GLfloat)w, 4.0*(GLfloat)h/(GLfloat)w, -4.0, 4.0);
else
glOrtho(-4.0*(GLfloat)w/(GLfloat)h, 4.0*(GLfloat)w/(GLfloat)h, -4.0, 4.0, -4.0, 4.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void main(void)
{
auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);
auxInitPosition (0, 0, 500, 500);
auxInitWindow ("Wireframe Bezier Surface");
myinit();
auxReshapeFunc (myReshape);
auxMainLoop(display);
}
以上程序运行结果是一个网状的曲面。
图13-8 Bezier网状曲面 |
13.4.2 定义均匀间隔的曲面坐标值
OpenGL中定义均匀间隔的曲面坐标值的函数与曲线的类似,其函数形式为:
void glMapGrid2{fd}(GLenum nu,TYPE u1,TYPE u2, GLenum nv,TYPE v1,TYPE v2);
void glEvalMesh2(GLenum mode,GLint p1,GLint p2,GLint q1,GLint q2);
第一个函数定义参数空间的均匀网格,从u1到u2分为等间隔的nu步,从v1到v2分为等间隔的nv步,然后glEvalMesh2()把这个网格应用
到已经启动的曲面计算上。第二个函数参数mode除了可以是GL_POINT和GL_LINE外,还可以是GL_FILL,即生成填充空间曲面。下面举出
一个用网格绘制一个经过光照和明暗处理的Bezier曲面的例程:
例13-6 加光照的均匀格网Bezier曲面绘制例程(bzmesh.c)
#include "glos.h"
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
void myinit(void);
void initlights(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);
/* 控制点坐标 */
GLfloat ctrlpoints[4][4][3] = {
{{-1.5, -1.5, 2.0}, {-0.5, -1.5, 2.0},
{0.5, -1.5, -1.0}, {1.5, -1.5, 2.0}},
{{-1.5, -0.5, 1.0}, {-0.5, 1.5, 2.0},
{0.5, 0.5, 1.0}, {1.5, -0.5, -1.0}},
{{-1.5, 0.5, 2.0}, {-0.5, 0.5, 1.0},
{0.5, 0.5, 3.0}, {1.5, -1.5, 1.5}},
{{-1.5, 1.5, -2.0}, {-0.5, 1.5, -2.0},
{0.5, 0.5, 1.0}, {1.5, 1.5, -1.0}}
};
void initlights(void)
{
GLfloat ambient[] = { 0.4, 0.6, 0.2, 1.0 };
GLfloat position[] = { 0.0, 1.0, 3.0, 1.0 };
GLfloat mat_diffuse[] = { 0.2, 0.4, 0.8, 1.0 };
GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat mat_shininess[] = { 80.0 };
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
glLightfv(GL_LIGHT0, GL_POSITION, position);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
}
void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glRotatef(35.0, 1.0, 1.0, 1.0);
glEvalMesh2(GL_FILL, 0, 20, 0, 20);
glPopMatrix();
glFlush();
}
void myinit(void)
{
glClearColor (0.0, 0.0, 0.0, 1.0);
glEnable (GL_DEPTH_TEST);
glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4, &ctrlpoints[0][0][0]);
glEnable(GL_MAP2_VERTEX_3);
glEnable(GL_AUTO_NORMAL);
glEnable(GL_NORMALIZE);
glMapGrid2f(20, 0.0, 1.0, 20, 0.0, 1.0);
initlights();
}
void CALLBACK myReshape(GLsizei w, GLsizei h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= h)
glOrtho(-4.0, 4.0, -4.0*(GLfloat)h/(GLfloat)w, 4.0*(GLfloat)h/(GLfloat)w, -4.0, 4.0);
else
glOrtho(-4.0*(GLfloat)w/(GLfloat)h, 4.0*(GLfloat)w/(GLfloat)h, -4.0, 4.0, -4.0, 4.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void main(void)
{
auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);
auxInitPosition (0, 0, 500, 500);
auxInitWindow ("Lighted and Filled Bezier Surface");
myinit();
auxReshapeFunc (myReshape);
auxMainLoop(display);
}
以上程序运行结果是一个加上光影的曲面。
图13-9 带光影的曲面 |
13.4.3 纹理曲面
在前面我们已经讲过纹理的用法,这一节将结合曲面的生成试试纹理的应用。下面我们先看一个例子:
例13-17 纹理曲面例程绘制(texsurf.c)
#include "glos.h"
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
#include <math.h>
void myinit(void);
void makeImage(void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
GLfloat ctrlpoints[4][4][3] = {
{{-1.5, -1.5, 2.0}, {-0.5, -1.5, 2.0},
{0.5, -1.5, -1.0}, {1.5, -1.5, 2.0}},
{{-1.5, -0.5, 1.0}, {-0.5, 1.5, 2.0},
{0.5, 0.5, 1.0}, {1.5, -0.5, -1.0}},
{{-1.5, 0.5, 2.0}, {-0.5, 0.5, 1.0},
{0.5, 0.5, 3.0}, {1.5, -1.5, 1.5}},
{{-1.5, 1.5, -2.0}, {-0.5, 1.5, -2.0},
{0.5, 0.5, 1.0}, {1.5, 1.5, -1.0}} };
GLfloat texpts[2][2][2] = {{{0.0, 0.0}, {0.0, 1.0}}, {{1.0, 0.0}, {1.0, 1.0}}};
void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glColor3f(1.0, 1.0, 1.0);
glEvalMesh2(GL_FILL, 0, 20, 0, 20);
glFlush();
}
#define imageWidth 64
#define imageHeight 64
GLubyte image[3*imageWidth*imageHeight];
void makeImage(void)
{
int i, j;
float ti, tj;
for (i = 0; i < imageWidth; i++)
{
ti = 2.0*3.14159265*i/imageWidth;
for (j = 0; j < imageHeight; j++)
{
tj = 2.0*3.14159265*j/imageHeight;
image[3*(imageHeight*i+j)] = (GLubyte) 127*(1.0+sin(ti));
image[3*(imageHeight*i+j)+1] = (GLubyte) 127*(1.0+cos(2*tj));
image[3*(imageHeight*i+j)+2] = (GLubyte) 127*(1.0+cos(ti+tj));
}
}
}
void myinit(void)
{
glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4, &ctrlpoints[0][0][0]);
glMap2f(GL_MAP2_TEXTURE_COORD_2, 0, 1, 2, 2, 0, 1, 4, 2, &texpts[0][0][0]);
glEnable(GL_MAP2_TEXTURE_COORD_2);
glEnable(GL_MAP2_VERTEX_3);
glMapGrid2f(20, 0.0, 1.0, 20, 0.0, 1.0);
makeImage();
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, 3, imageWidth, imageHeight, 0,
GL_RGB, GL_UNSIGNED_BYTE, image);
glEnable(GL_TEXTURE_2D);
glEnable(GL_DEPTH_TEST);
glEnable(GL_NORMALIZE);
glShadeModel (GL_FLAT);
}
void CALLBACK myReshape(GLsizei w, GLsizei h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= h)
glOrtho(-4.0, 4.0, -4.0*(GLfloat)h/(GLfloat)w, 4.0*(GLfloat)h/(GLfloat)w, -4.0, 4.0);
else
glOrtho(-4.0*(GLfloat)w/(GLfloat)h, 4.0*(GLfloat)w/(GLfloat)h, -4.0, 4.0, -4.0, 4.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glRotatef(35.0, 1.0, 1.0, 1.0);
}
void main(void)
{
auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);
auxInitPosition (0, 0, 500, 400);
auxInitWindow ("Texture Surface");
myinit();
auxReshapeFunc (myReshape);
auxMainLoop(display);
}
以上程序运行结果是一个带纹理的曲面。
图13-10 带纹理的曲面 |
13.4.4 NURBS曲面
OpenGL的功能库提供了一系列NURBS曲面(非均匀有理B样条曲面)的函数。本节不具体讲各函数的用法,仅举出一个应用例子,其余的读者可以参考有关手册。例程如下:
例13-8 NURBS曲面绘制例程(nurbsurf.c)
#include "glos.h"
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
void myinit(void);
void init_surface(void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
GLfloat ctlpoints[4][4][3];
GLUnurbsObj *theNurb;
/* 初始化控制点坐标,x,y,z范围从-3到3 */
void init_surface(void)
{
int u, v;
for (u = 0; u < 4; u++)
{
for (v = 0; v < 4; v++)
{
ctlpoints[u][v][0] = 2.0*((GLfloat)u - 1.5);
ctlpoints[u][v][1] = 2.0*((GLfloat)v - 1.5);
if ( (u == 1 || u == 2) && (v == 1 || v == 2))
ctlpoints[u][v][2] = 3.0;
else
ctlpoints[u][v][2] = -3.0;
}
}
}
/* 定义曲面材质 (金色) */
void myinit(void)
{
GLfloat mat_diffuse[] = { 0.88, 0.66, 0.22, 1.0 };
GLfloat mat_specular[] = { 0.92, 0.9, 0.0, 1.0 };
GLfloat mat_shininess[] = { 80.0 };
glClearColor (0.0, 0.0, 0.0, 1.0);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess); glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glDepthFunc(GL_LESS);
glEnable(GL_DEPTH_TEST);
glEnable(GL_AUTO_NORMAL);
glEnable(GL_NORMALIZE);
init_surface();
theNurb = gluNewNurbsRenderer();
gluNurbsProperty(theNurb, GLU_SAMPLING_TOLERANCE, 25.0);
gluNurbsProperty(theNurb, GLU_DISPLAY_MODE, GLU_FILL);
}
void CALLBACK display(void)
{
GLfloat knots[8] = {0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0};
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glRotatef(330.0, 1.,0.,0.);
glScalef (0.5, 0.5, 0.5);
gluBeginSurface(theNurb);
gluNurbsSurface(theNurb,
8,
knots,
8,
knots,
4 * 3,
3,
&ctlpoints[0][0][0],
4, 4,
GL_MAP2_VERTEX_3);
gluEndSurface(theNurb);
glPopMatrix();
glFlush();
}
void CALLBACK myReshape(GLsizei w, GLsizei h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective (45.0, (GLdouble)w/(GLdouble)h, 3.0, 8.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef (0.0, 1.0, -5.0);
}
void main(void)
{
auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);
auxInitPosition (0, 0, 500, 500);
auxInitWindow ("NURBS Surface");
myinit();
auxReshapeFunc (myReshape);
auxMainLoop(display);
}
以上程序运行结果是一个银白色的NURBS曲面。
图13-11 NURBS曲面 |
十四、特殊光处理
14.1、光照模型
OpenGL光照模型的概念由一下三部分组成:1)全局泛光强度、2)视点位置在景物附近还是在无穷远处、3)物体的正面和背面是否分别进行光照计算。
14.1.1 全局环境光
正如前面基础篇中所提到的一样,每个光源都能对一个场景提供环境光。此外,还有一个环境光,它不来自任何特定的光源,即称为全局环境光。下面用参数GL_LIGHT_MODEL_AMBIENT来说明全局环境光的RGBA强度:
GLfloat lmodel_ambient[]={0.2,0.2,0.2,1.0};
glLightModelfv(GLLIGHT_MODEL_AMBIENT,lmodel_ambient);
在这个例子中,lmodel_ambient所用的值为GL_LIGHT_MODEL_AMBIENT的缺省值。这些数值产生小量的白色环境光。
14.1.2 近视点与无穷远视点
视点位置能影响镜面高光的计算,也就是说,顶点的高光强度依赖于顶点法向量,从顶点到光源的方向和从顶点到视点的方向。实际上,调用光照函数并不能移动
视点。但是可以对光照计算作出不同的假定,这样视点似乎移动了。对于一个无穷远视点,视点到任何顶点的方向保持固定,缺省时为无穷远视点。对于一个近视
点,物体每个顶点到视点的方向是不同的,需要逐个计算,从而整体性能降低,但效果更真实。下面一句函数代码是假定为近视 点:
glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER,GL_TRUE);
这个调用把视点放在视点坐标系的原点处。若要切换到无穷远视点,只需将参数GL_TRUE 改为GL_FALSE即可。
14.1.3 双面光照
光照计算通常是对所有多边形进行的,无论其是正面或反面。一般情况下,设置光照条件时总是正面多边形,因此不能对背面多边形进行正确地光照。在基础篇的
第七章中的例子里,物体是一个球,只有正面多边形能看到,即球的外部可见,这种情况下不必考虑背面光照。若球被砍开,其内部的曲面是可见的,那么对内部多
边形需进行光照计算,这时应该调用如下函数:
glLightModeli(LIGHT_MODEL_TWO_SIDE,GL_TRUE);
启动双面光照。实际上,这就是OpenGL给背面多边形定义一个相反的法向量(相对于正面多边形而言)。一般来说,这意味着可见正面多边形和可见反面多
边形的法向量都面朝观察者,而不是向里,这样所有多边形都能进行正确的光照。关闭双面光照,只需将参数GL_TRUE改为GL_FALSE即可。
14.2、光源位置与衰减
在基础篇中已经提到过,光源有无穷远光源和近光源两种形式。无穷远光源又称作定向光源,即这种光到达物体时是平行光,例如现实生活中的太阳光。近光源又
称作定位光源,光源在场景中的位置影响场景的光照效果,尤其影响光到达物体的方向。台灯是定位光源的范例。在以前所有与光照有关的例子里都采用的是定向光
源,如:
GLfloat light_position[]={1.0,1.0,1.0,0.0};
glLightfv(GL_LIGHT0,GL_POSITION,light_position);
光源位置坐标采用的齐次坐标(x, y, z, w),这里的w为0,所以相应的光源是定向光,(x,
y,z)描述光源的方向,这个方向也要进行模型变换。GL_POSITION的缺省值是(0.0, 0.0,
1.0,0.0),它定义了一个方向指向Z负轴的平行光源。若w非零,光源为定位光源。(x, y,
z,w)指定光源在齐次坐标系下的具体位置,这个位置经过模型变换等在视点坐标系下保存下来。
真实的光,离光源越远则光强越小。因为定向光源
是无穷远光源,因此距离的变换对光强的影响几乎没有,所以定向光没有衰减,而定位光有衰减。OpenGL的光衰减是通过光源的发光量乘以衰减因子计算出来
的。其中衰减因子在第十章表10-2中说明过。缺省状态下,常数衰减因子是1.0,其余两个因子都是0.0。用户也可以自己定义这些值,如:
glLightf(GL_LIGHT0,GL_CONSTANT_ATTENUATION,2.0);
glLightf(GL_LIGHT0,GL_LINEAR_ATTENUATION,1.0);
glLightf(GL_LIGHT0,GL_QUADRATIC_ATTENUATION,0.5);
注意:环境光、漫反射光和镜面光的强度都衰减,只有辐射光和全局环境光的强度不衰减。
14.3、聚光和多光源
14.3.1 聚光
定位光源可以定义成聚光灯形式,即将光的形状限制在一个圆锥内。OpenGL中聚光的定义有以下几步:
1)定义聚光源位置。因为聚光源也是定向光源,所以他的位置同一般定向光一样。如:
GLfloat light_position[]={1.0,1.0,1.0,1.0};
glLightfv(GL_LIGHT0,LIGHT_POSITION,light_position);
2)定义聚光截止角。
参数GL_SPOT_CUTOFF给定光锥的轴与中心线的夹角,也可说成是光锥顶角的一半,如图14-1所示。缺省时,这个参数为180.0,即顶角为
360度,光向所有的方向发射,因此聚光关闭。一般在聚光启动情况下,聚光截止角限制在[0.0, 90.0]之间,如下面一行代码设置截止角为45度:
glLightf(GL_LIGHT0,GL_SPOT_CUTOFF,45.0);
3)定义聚光方向。聚光方向决定光锥的轴,它齐次坐标定义,其缺省值为(0.0, 0.0, -1.0),即指向Z负轴。聚光方向也要进行几何变换,其结果保存在视点坐标系中。它的定义如下:
GLfloat spot_direction[]={-1.0,-1.0,0.0};
glLightfv(GL_LIGHT0,GL_SPOT_DIRECTION,spot_direction);
4)定义聚光指数。参数GL_SPOT_EXPONENT控制光的集中程度,光锥中心的光强最大,越靠边的光强越小,缺省时为0。如:
glLightf(GL_LIGHT0,GL_SPOT_EXPONENT,2.0);
此外,除了定义聚光指数控制光锥内光强的分布,还可利用上一节所讲的衰减因子的设置来实现,因为衰减因子与光强相乘得最终光强值。
14.3.2 多光源及例程
在前面已提到过场景中最多可以设置八个光源(根据OpenGL的具体实现也许会更多一些)。在多光源情况下,OpenGL需计算每个顶点从每个光源接受
的光强,这样会增加计算量,降低性能,因此在实时仿真中最好尽可能地减少光源数目。在前面都已讨论过八个光源的常数:GL_LIGHT0、
GL_LIGHT1、…、LIGHT7,注意:GL_LIGHT0的参数缺省值与其它光源的参数缺省值不同。下面这段代码定义了红色的聚光:
GLfloat light1_ambient[]= { 0.2, 0.2, 0.2, 1.0 };
GLfloat light1_diffuse[]= { 1.0, 0.0, 0.0, 1.0 };
GLfloat light1_specular[] = { 1.0, 0.6, 0.6, 1.0 };
GLfloat light1_position[] = { -3.0, -3.0, 3.0, 1.0 };
GLfloat spot_direction[]={ 1.0,1.0,-1.0};
glLightfv(GL_LIGHT1, GL_AMBIENT, light1_ambient);
glLightfv(GL_LIGHT1, GL_DIFFUSE, light1_diffuse);
glLightfv(GL_LIGHT1, GL_SPECULAR,light1_specular);
glLightfv(GL_LIGHT1, GL_POSITION,light1_position);
glLightf (GL_LIGHT1, GL_SPOT_CUTOFF, 30.0);
glLightfv(GL_LIGHT1, GL_SPOT_DIRECTION,spot_direction);
glEnable(GL_LIGHT1);
// 这段代码描述的是一个红色的立方体,用它来代表所定义的聚光光源 //
// 可加到程序的函数display()中去。 //
glPushMatrix();
glTranslated (-3.0, -3.0, 3.0);
glDisable (GL_LIGHTING);
glColor3f (1.0, 0.0, 0.0);
auxWireCube (0.1);
glEnable (GL_LIGHTING);
glPopMatrix ();
//////////////////////////////////////////////////////////////////
将这些代码加到基础篇第十章光照的例程(light2.c)中去:
例14-1 聚光和多光源运用例程(spmulght.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);
/* 初始化光源、材质等 */
void myinit(void)
{
GLfloat mat_ambient[]= { 0.2, 0.2, 0.2, 1.0 };
GLfloat mat_diffuse[]= { 0.8, 0.8, 0.8, 1.0 };
GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat mat_shininess[] = { 50.0 };
GLfloat light0_diffuse[]= { 0.0, 0.0, 1.0, 1.0};
GLfloat light0_position[] = { 1.0, 1.0, 1.0, 0.0 };
GLfloat light1_ambient[]= { 0.2, 0.2, 0.2, 1.0 };
GLfloat light1_diffuse[]= { 1.0, 0.0, 0.0, 1.0 };
GLfloat light1_specular[] = { 1.0, 0.6, 0.6, 1.0 };
GLfloat light1_position[] = { -3.0, -3.0, 3.0, 1.0 };
GLfloat spot_direction[]={ 1.0,1.0,-1.0};
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_SHININESS,mat_shininess);
glLightfv(GL_LIGHT0, GL_DIFFUSE, light0_diffuse);
glLightfv(GL_LIGHT0, GL_POSITION,light0_position);
glLightfv(GL_LIGHT1, GL_AMBIENT, light1_ambient);
glLightfv(GL_LIGHT1, GL_DIFFUSE, light1_diffuse);
glLightfv(GL_LIGHT1, GL_SPECULAR,light1_specular);
glLightfv(GL_LIGHT1, GL_POSITION,light1_position);
glLightf (GL_LIGHT1, GL_SPOT_CUTOFF, 30.0);
glLightfv(GL_LIGHT1, GL_SPOT_DIRECTION,spot_direction);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_LIGHT1);
glDepthFunc(GL_LESS);
glEnable(GL_DEPTH_TEST);
}
void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glTranslated (-3.0, -3.0, 3.0);
glDisable (GL_LIGHTING);
glColor3f (1.0, 0.0, 0.0);
auxWireCube (0.1);
glEnable (GL_LIGHTING);
glPopMatrix ();
auxSolidSphere(2.0);
glFlush();
}
void CALLBACK myReshape(GLsizei w, GLsizei h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= h)
glOrtho (-5.5, 5.5, -5.5*(GLfloat)h/(GLfloat)w, 5.5*(GLfloat)h/(GLfloat)w,
-10.0, 10.0);
else
glOrtho (-5.5*(GLfloat)w/(GLfloat)h, 5.5*(GLfloat)w/(GLfloat)h,
-5.5, 5.5, -10.0, 10.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void main(void)
{
auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);
auxInitPosition (0, 0, 500, 500);
auxInitWindow ("Spotlight and Multi_lights ");
myinit();
auxReshapeFunc (myReshape);
auxMainLoop(display);
}
以上程序运行结果是球被两个光源照射,一个是蓝色的定向光,另一个红色的是聚光。
图14-1 聚光与多光源 |
14.4、光源位置与方向的控制
OpenGL光源的位置和方向与其它几何图元的位置和方向一样都必须经过变换矩阵的作用。尤其是当用glLight*()说明光源的位置和方向时,位置
和方向都要经过当前变换矩阵的作用并保存在视点坐标系中,注意投影矩阵变换对它们不起作用。OpenGL可通过调整光源函数和视点变换函数在程序中的相对
位置来获得三种不同的效果:1)光源位置保持固定、2)光源绕静止物体移动、3)光源随视点移动。
在第一种情况下,为获得光源位置固定的效
果,必须在所有视点和模型变换之后设置光源位置。第二种情况下,有两种方法可以达到这种效果,一种是在模型变换后设置光源位置,变换的改变将修改光源位
置;另一种是使物体和视点绕光源移动,相对地光源就能绕物体移动了。第三种情况下,要建立一个沿视线移动的光源,必须在视点变换之前设置光源位置,则视点
变换按同样的方式影响光源和视点。下面有一个光源移动的例子:
例14-1 光源移动例程(mvlight.c)
#include "glos.h"
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
void myinit(void);
void CALLBACK movelight (AUX_EVENTREC *event);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
static int step = 0;
void CALLBACK movelight (AUX_EVENTREC *event)
{
step = (step + 15) % 360;
}
void myinit (void)
{
GLfloat mat_diffuse[]={0.0,0.5,1.0,1.0};
GLfloat mat_ambient[]={0.0,0.2,1.0,1.0};
GLfloat light_diffuse[]={1.0,1.0,1.0,1.0};
GLfloat light_ambient[]={0.0,0.5,0.5,1.0};
glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,mat_diffuse);
glLightfv(GL_LIGHT0,GL_DIFFUSE,light_diffuse);
glLightfv(GL_LIGHT0,GL_AMBIENT,light_ambient);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glDepthFunc(GL_LESS);
glEnable(GL_DEPTH_TEST);
}
void CALLBACK display(void)
{
GLfloat position[] = { 0.0, 0.0, 1.5, 1.0 };
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix ();
glTranslatef (0.0, 0.0, -5.0);
glPushMatrix ();
glRotated ((GLdouble) step, -1.0, 1.0, 1.0);
glRotated (0.0, 1.0, 0.0, 0.0);
glLightfv (GL_LIGHT0, GL_POSITION, position);
glTranslated (0.0, 0.0, 1.5);
glDisable (GL_LIGHTING);
glColor3f (1.0, 1.0, 0.0);
auxWireSphere (0.1);
glEnable (GL_LIGHTING);
glPopMatrix ();
auxSolidTorus (0.275, 0.85);
glPopMatrix ();
glFlush ();
}
void CALLBACK myReshape(GLsizei w, GLsizei h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(40.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0);
glMatrixMode(GL_MODELVIEW);
}
void main(void)
{
auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);
auxInitPosition (0, 0, 500, 500);
auxInitWindow ("Moving Light");
myinit();
auxMouseFunc (AUX_LEFTBUTTON, AUX_MOUSEDOWN, movelight);
auxReshapeFunc (myReshape);
auxMainLoop(display);
}
以上程序运行结果是在屏幕*显示一个蓝色的环形圈,按下鼠标左键则可移动光源,其中一个黄色的小球代表光源。
图14-2 光源移动 |
14.5、辐射光
在前面的章节中已经应用了辐射光,可以参见10.4.4材质改变的例程(chgmat1.c)的运行效果。这里再一次强调提出,通过给GL_EMISSION定义一个RGBA值,可以使物体看起来象发出这种 颜色的光一样,以达到某种特殊效果。实际上,现实生活中的物体除光源外都不发光,但我们可以利用这一特性来模拟灯和其他光源。代码举例如下:
GLfloat mat_emission[]={0.3,0.3,0.5,0.0};
glMaterialfv(GL_FRONT,GL_EMISSION,mat_emission);
这样,物体看起来稍微有点发光。比如绘制一个打开的台灯,就可以将一个小球的材质定义成上述形式,并且在小球内部建立一个聚光源,这样台灯的灯泡效果就出来了。
十五、效果处理
15.1、融合
15.1.1 Alpha值与融合(Blending)
Alpha值在前面几章中已经提到过,但是几乎所有例程都将它设置为1.0,没有详细讨论它为其它值时的情况。融合,是本章的重点,它是透明技术、数字
合成和计算机绘画技术的核心。固名思义,融合就是指两种颜色各分量依据一定的比例混在一起合二为一。而这种比例就来源于Alpha值,即RGBA中的A或
(r、g、b、a)中的a值,通常称a为不透明性,称(1-a)为透明性。因为在颜色表方式下不能说明a值,因此融合操作不能在颜色表方式下进行。
为了更好地理解这些概念,我们可以举出一个例子来简要说明。例如,坐在汽车内透过车窗的茶色玻璃看车外的绿树,这些树木的颜色并不是它们本来的绿色,而
是透明的茶色与不透明的绿色的混合颜色。也就是说,最终的颜色来源于两部分,一部分来自于玻璃的茶色,另一部分来自于树木的绿色。两部分所占的百分比依据
玻璃的透射变化,若玻璃透射率为80%(即一束光照在其上有80%的透射过去),其不透明度为20%,即这里的a值就等于0.2,这样进入眼中的树木颜色
是20%的玻璃颜色与80%的树木本身颜色的合成。
15.1.2 融合因子(Blending Factor)
在OpenGL的融合操作中,实际上包含了两个因子的操作,这两个因子就是源因子(Source Factor)和目的因子(Destination Factor)。从数学的角度来看,设源因子和目的因子分别为(Sr、Sg、Sb、Sa)与(Dr、Dg、Db、Da),则融合的最终结果是:
(Rs*Sr+Rd*Dr, Gs*Sg+Gd*Dg, Bs*Sb+Bd*Db, As*Sa+Ad*Da)
|
并且其中每个元素值都约简到[0, 1]之间。在OpenGL中,由函数gjBlendFunc()来产生这两个融合因子的值。其函数形式为:
void glBlendFunc(GLenum sfactor,GLenum dfactor)
控制源因子和目的因子的结合。参数sfactor指明怎样计算源因子,dfactor指明怎样计算目的因子。这些参数的可能值见表15-1所示。注意:融合因子的值在[0,1]范围内,且两因子结合后也要约简到[0,1]内,源与目的的RGBA值分别带有s和d下标。
常数 | 相关因子 | 计算后得到的融合因子 |
GL_ZERO | 源因子或目的因子 | (0,0,0,0) |
GL_ONE | 源因子或目的因子 | (1,1,1,1) |
GL_DST_COLOR | 源因子 | (Rd,Gd,Bd,Ad) |
GL_SRC_COLOR | 目的因子 | (Rs,Gs,Bs,As) |
GL_ONE_MINUS_DST_COLOR | 源因子 | (1,1,1,1)-(Rd,Gd,Bd,Ad) |
GL_ONE_MINUS_SRC_COLOR | 目的因子 | (1,1,1,1)-(Rs,Gs,Bs,As) |
GL_SRC_ALPHA | 源因子或目的因子 | (As,As,As,As) |
GL_ONE_MINUS_SRC_ALPHA | 源因子或目的因子 | (1,1,1,1)-(As,As,As,As) |
GL_DST_ALPHA | 源因子或目的因子 | (Ad,Ad,Ad,Ad) |
GL_ONE_MINUS_DST_ALPHA | 源因子或目的因子 | (1,1,1,1)-(Ad,Ad,Ad,Ad) |
GL_SRC_ALPHA_SATURATE | 源因子 | (f,f,f,1); f=min(As,1-Ad) |
表15-1 源因子和目的因子
|
当用函数glBlendFunc()说明了融合因子的产生后,需调用函数glEnable(GL_BLEND)来启动融合操作,不用时可调用
glDisable(GL_BLEND)来关闭它。若源因子的参数为常数GL_ONE,目的因子的参数为常数GL_ZERO,则相当于关闭融合操作,这些
值为缺省值。
15.1.3 融合实例
首先来看一个融合运用的简单例子:
例15-1 Alpha二维融合例程(blend2d.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);
/* 初始化 alpha 融合的参数 */
void myinit(void)
{
glEnable (GL_BLEND);
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glShadeModel (GL_FLAT);
glClearColor (0.0, 0.0, 0.0, 0.0);
}
void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT);
glColor4f (1.0, 0.0, 0.0, 0.7);
glRectf (0.25, 0.4, 0.75, 0.9);
glColor4f (0.0, 1.0, 0.0, 0.5);
glRectf (0.1, 0.1, 0.6, 0.6);
glColor4f (0.0, 0.0, 1.0, 0.3);
glRectf (0.4, 0.1, 0.9, 0.6);
glFlush();
}
void CALLBACK myReshape(GLsizei w, GLsizei h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= h)
gluOrtho2D (0.0, 1.0, 0.0, 1.0*(GLfloat)h/(GLfloat)w);
else
gluOrtho2D (0.0, 1.0*(GLfloat)w/(GLfloat)h, 0.0, 1.0);
glMatrixMode(GL_MODELVIEW);
}
void main(void)
{
auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);
auxInitPosition (0, 0, 500, 500);
auxInitWindow ("Alpha 2D Blending");
myinit();
auxReshapeFunc (myReshape);
auxMainLoop(display);
}
以上程序运行结果是显示三个不同颜色(红、绿、蓝)的方块部分融合的效果。
图15-1 红绿蓝方块融合效果 |
15.2、反走样
反走样(Antialiasing),
又叫反混淆,是计算机图形学中的一个重要概念。由于计算机生成的图形是由离散点组成的数字化图像,因而生成的图形必然与真实景物之间存在一定误差。这种误
差表现为图形上的直线或光滑的曲线呈现锯齿状、彩色花纹失去原有的形态和色彩、细小物体在画面上得不到反映等等。这种锯齿就叫做走样。见图15-2所示,
左边为走样线,右边为反走样线。
图15-2 走样线与反走样线 |
15.2.1 行为控制函数
在OpenGL中,许多细节的实现算法有所不同。这样,可以调用函数glHint()对图像质量和绘制速度之间的权衡作一些控制,但并非所有的实现都采用它。其函数形式为:
void glHint(GLenum target,GLenum hint);
控制OpenGL行为的某些方面。参数target说明控制什么行为,其可能值见表15-2所示。参数hint可以是:GL_FASTEST(即给出最有效的选择)、GL_NICEST(即给出最高质量的选择)、GL_DONT_CARE(即没有选择)。
参数 | 意义 |
GL_POINT_SMOOTH_HINT | 指定点、线、多边形的采样质量 |
GL_LINE_SMOOTH_HINT | |
GL_POLYGON_SMOOTH_HINT | |
GL_FOG_HINT | 指出雾的计算是按每个象素进行(GL_NICEST) 还是按每个顶点进行(GL_FASTEST) |
GL_PERSPECTIVE_CORRECTION_HINT | 指定颜色和纹理插值的质量 |
表15-2 函数glHint()参数及其意义
|
实际上,对于提示的解释依赖于OpenGL的具体实现,有时可以忽略。参数GL_PERSPECTIVE_CORRECTION_HINT用于指定一个
图元中的颜色值和纹理值怎样进行插值,要么在屏幕空间进行插值,要么按照透视投影纠正方式进行插值(这种方式需更多计算)。通常,系统对颜色进行线性插
值,虽然从原理和技术的角度来讲未必正确,但是人眼可以接受,且实现速度很快。然而纹理在大多数情况下,为了看起来可以接受,需要对其进行透视纠正插值。
因此,可以用这个参数控制插值方法。同样,在进行反走样计算时,也要用到这个函数来控制图形实现的行为。
15.2.2 点和线的反走样
在OpenGL中虽然颜色表方式可以实现反走样,但建议最好用RGBA方式进行。不管何种方式,对图元进行反走样,首先要用glEnable()启动反
走样(参数为GL_POINT、GL_LINE_SMOOTH或GL_POLYGON_SMOOTH),同时,也可以调用glHint()提供一个图像质
量提示。但需注意的是,在RGBA方式下,必须启动混合,最可能用的混合因子是GL_SRC_ALPHA(源)和
GL_ONE_MINUS_SRC_ALPHA(目的)。另外,可以使目的因子为GL_ONE,则线的交点处要亮一些。下面举出一个例子:
例15-2 反走样线例程(antiline.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);
/* 初始化反走样为 RGBA 模式,同时包括alpha混合、提示、线宽等的设置。 */
void myinit(void)
{
glEnable (GL_LINE_SMOOTH);
glEnable (GL_BLEND);
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glHint (GL_LINE_SMOOTH_HINT, GL_DONT_CARE);
glLineWidth (5.0);
glShadeModel(GL_FLAT);
glClearColor(0.0, 0.0, 0.0, 0.0);
glDepthFunc(GL_LESS);
glEnable(GL_DEPTH_TEST);
}
void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glColor4f (0.0, 0.6, 1.0, 1.0);
auxWireOctahedron(1.0);
glFlush();
}
void CALLBACK myReshape(GLsizei w, GLsizei h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective (45.0, (GLfloat) w/(GLfloat) h, 3.0, 5.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity ();
glTranslatef (0.0, 0.0, -4.0); /* 将物体移至视见区内 */
glRotatef(15.0,1.0,1.0,0.0);
}
void main(void)
{
auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);
auxInitPosition (0, 0, 400, 400);
auxInitWindow ("Antialiased Lines Using RGBA");
myinit();
auxReshapeFunc (myReshape);
auxMainLoop(display);
}
以上程序运行结果是显示一个反走样的网状八面体。
图15-3 反走样线 |
15.2.3 多边形的反走样
填充多边形的反走样类似于点线的反走样,不同的只是将所有POINT或LINE的地方改为POLYGON而已。如下面的一个在RGBA模式下的多边形反走样例子:
例15-3 多边形反走样例程(antipoly.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);
void myinit(void)
{
GLfloat mat_ambient[] = { 0.5, 0.5, 0.0, 1.00 };
GLfloat mat_diffuse[] = { 1.0, 0.8, 0.1, 1.0 };
GLfloat position[] = { 1.0, 0.0, 1.0, 0.0 };
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
glMaterialfv(GL_FRONT, GL_DIFFUSE,mat_diffuse);
glLightfv (GL_LIGHT0, GL_POSITION, position);
glEnable (GL_LIGHTING);
glEnable (GL_LIGHT0);
glEnable (GL_BLEND);
glCullFace (GL_BACK);
glEnable (GL_CULL_FACE);
glEnable (GL_POLYGON_SMOOTH);
glDisable (GL_DEPTH_TEST);
glBlendFunc (GL_SRC_ALPHA_SATURATE, GL_ONE);
glClearColor (0.0, 0.0, 0.0, 0.0);
}
void CALLBACK display(void)
{
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glTranslatef (0.0, 0.0, -8.0);
glRotatef (-45.0, 1.0, 0.0, 0.0);
glRotatef (45.0, 0.0, 1.0, 0.0);
auxSolidIcosahedron (1.0);
glFlush ();
}
void CALLBACK myReshape(GLsizei w, GLsizei h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(30.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0);
glMatrixMode(GL_MODELVIEW);
}
void main(void)
{
auxInitDisplayMode (AUX_SINGLE | AUX_RGBA | AUX_ALPHA );
auxInitPosition (0, 0, 400, 400);
auxInitWindow ("Antialiased Polygons");
myinit();
auxReshapeFunc (myReshape);
auxMainLoop(display);
}
以上程序运行结果是显示一个黄色的填充反走样二十面体。
图15-4 反走样多边形 |
15.3、雾
15.3.1 雾的概论和例程
雾化效果在当今的计算机图形学中应用极广,它不仅可以使场景中的物体看起来更加真实,而且还可提高绘制速度。在很多情况下,计算机图像有时会出现不符合
实际的精细和棱角分明,上一节的反走样技术可以通过光顺着物体的边界反走样,使其看起来更真实,这一节的雾化处理可以使物体看起来更加自然,即在雾中,离
视点远的物体会变得模糊。
雾,是一个描述类似于大气效果的一般术语,在视景仿真中应用很广。它可以模拟烟雾(haze)、薄雾(mist)、浓烟(smoke)和污染(pollution)等效果。当启动雾后,离视点较远的物体开始淡化成雾的颜色,同时,雾的浓度可以控制,即随着距离的增加物体变淡的速率可控,雾的颜色也可以任意设定。雾在两种颜色方式下都能使用。下面举出一个在RGBA方式下使用雾的例程:
例15-4 RGBA方式下雾化例程(fogrgb.c)
#include "glos.h"
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
#include <math.h>
#include <stdio.h>
void myinit(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);
void myinit(void)
{
GLfloat mat_ambient[] = { 0.7, 0.6, 0.4, 1.00 };
GLfloat mat_diffuse[] = {0.7,0.0,0.99,1.0};
GLfloat mat_specular[] = { 1.0, 0.0, 1.0, 1.00 };
GLfloat mat_shininess[] = { 15.0 };
GLfloat position[] = { 5.0, 5.0, 5.0, 1.0 };
GLfloat fogColor[4] = {0.6, 0.6, 0.6, 1.0};
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glLightfv(GL_LIGHT0, GL_POSITION, position);
glFrontFace (GL_CW);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_FOG);
{
glFogi (GL_FOG_MODE, GL_LINEAR);
glFogfv (GL_FOG_COLOR, fogColor);
glFogf (GL_FOG_START, 3.0);
glFogf (GL_FOG_END,15.0);
glHint (GL_FOG_HINT, GL_DONT_CARE);
glClearColor(0.3, 0.3, 0.3, 1.0);
}
}
void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glTranslatef(-3.0,-1.5,-2.0);
auxSolidTorus(0.6,1.5);
glPopMatrix();
glPushMatrix();
glTranslatef(-0.5,-0.5,-7.0);
auxSolidTorus(0.6,1.5);
glPopMatrix();
glPushMatrix();
glTranslatef(2.0,0.8,-10.0);
auxSolidTorus(0.6,1.5);
glPopMatrix();
glFlush();
}
void CALLBACK myReshape(GLsizei w, GLsizei h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= (h*3))
glOrtho (-6.0, 6.0, -2.0*((GLfloat) h*3)/(GLfloat) w, 2.0*((GLfloat) h*3)/(GLfloat) w, 0.0, 10.0);
else
glOrtho (-6.0*(GLfloat) w/((GLfloat) h*3), 6.0*(GLfloat) w/((GLfloat) h*3), -2.0, 2.0, 0.0, 10.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity ();
}
void main(void)
{
auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);
auxInitPosition (0, 0, 500, 400);
auxInitWindow ("Fog_RGB_mode");
myinit();
auxReshapeFunc (myReshape);
auxMainLoop(display);
}
以上程序运行结果是显示三个不同远近的蓝紫色环形圈在雾中的效果,这里的雾化计算采用线性方式(GL_LINEAR)。
图15-5 雾化效果 |
15.3.2 雾化步骤
在OpenGL程序中使用雾化效果非常容易。其步骤有三,如下:
1)启动雾。函数调用为glEnable(GL_FOG);
2)控制雾。函数调用为glFog*(),用它选择控制浓度和颜色的雾方程,其具体形式为:
void glFog{if}[v](GLenum pname,TYPE param);
设置计算雾的参数和函数。若pname为GL_FOG_MODE,则param可以是GL_EXP(缺省)、GL_EXP2或GL_LINEAR,分别
代表三个雾因子。若pname为GL_FOG_DENSITY、GL_FOG_START或GL_FOG_END,则param为雾方程中对应的
density、start和end的值。缺省值为1、0、1。在RGBA方式下,pname可以是GL_FOG_COLOR,此时参数param为指向
含有RGBA值的向量指针。同样,在颜色表方式下,pname相应值为GL_FOG_INDEX,其对应的param是雾的颜色索引值。
3)若有必要,可用函数glHint(GL_FOG_HINT)提供一个值。
十六、显示列表
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-1 显示列表 |
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);要编辑顶点,只需重新建立相应的该顶点显示列表。