 |
第24课 |
 |
 |
扩展,剪裁和TGA图像文件的加载:
在这一课里,你将学会如何读取你显卡支持的OpenGL的扩展,并在你指定的剪裁区域把它显示出来。 |
|
 |
 |
这个教程有一些难度,但它会让你学到很多东西。我听到很多朋友问我扩展方面的内容和怎样找到它们。这个教程将交给你这
一切。
我将教会你怎样滚动屏幕的一部分和怎样绘制直线,最重要的是从这一课起,我们将不使用AUX库,以及*.bmp文件。我将告诉你如何使用Targa(TGA)图像文件。因为它简单并且支持alpha通道,它可以使你更容易的创建酷的效果。
接下来我们要做的第一件事就是不包含glaux.h头文件和glaux.lib库。另外,在使用glaux库时,经常会发生一些可疑的警告,现在我们可以测定告别它了。 |  |
#include <stdarg.h>
#include <string.h>
 |
接下来我们添加一些变量,第一个为滚动参数。第二给变量记录扩展的个数,swidth和sheight记录剪切矩形的大小。base为字体显示列表的开始值。 |
 |
int scroll;
int maxtokens;
int swidth;
int sheight;
GLuint base;
 |
现在我们创建一个数据结构用来保存TGA文件,接着我们使用这个结构来加载纹理。 |
 |
typedef struct
{
GLubyte *imageData;
GLuint bpp;
GLuint width;
GLuint height;
GLuint texID;
} TextureImage;
TextureImage textures[1];
 |
这个部分的代码将要加载一个TGA文件并把它转换为纹理。必须注意的是这部分代码只能加载24/32位的不压缩的TGA文件。
这个函数包含两个参数,一个保存载入的图像,一个为将载入的文件名。
TGA文件包含一个12个字节的文件头,载入图像后,我们用type来设置图像中像素格式在OpenGL中的对应。如果是24位的图像我们使用GL_RGB,如果是32位的图像我们使用GL_RGBA。 |
 |
bool LoadTGA(TextureImage *texture, char *filename)
{
GLubyte TGAheader[12]={0,0,2,0,0,0,0,0,0,0,0,0};
GLubyte TGAcompare[12];
GLubyte header[6];
GLuint bytesPerPixel;
GLuint imageSize;
GLuint temp;
GLuint type=GL_RGBA;
 |
下面这个函数读取TGA文件,并记录文件信息。TGA文件格式如下所示:
Tga图像格式
无颜色表 rgb 图像
偏移 |
长度 |
描述 |
32位常用图像文件各个字节的值 |
0 |
1 |
指出图像信息字段的长度,其取值范围是 0 到 255 ,当它为 0 时表示没有图像的信息字段。 |
0 |
1 |
1 |
是否使用颜色表,0 表示没有颜色表,1 表示颜色表存在 |
0 |
2 |
1 |
该字段总为 2。图像类型码,tga一共有6种格式,2表示无颜色表 rgb 图像 |
2 |
3 |
5 |
颜色表规格,总为0。 |
0 |
4 |
0 |
5 |
0 |
6 |
0 |
7 |
0 |
8
10 图像规格说明
开始 |
8 |
2 |
图像 x 坐标起始位置,一般为0 |
0 |
9 |
10 |
2 |
图像 y 坐标起始位置,一般为0 |
0 |
11 |
12 |
2 |
图像宽度,以像素为单位 |
256 |
13 |
14 |
2 |
图像高度,以像素为单位 |
256 |
15 |
16 |
1 |
图像每像素存储占用位(bit)数 |
32 |
17 |
1 |
图像描述符字节
bits 3-0 - 每像素对应的属性位的位数,对于 TGA 24,该值为 0
bit 4 - 保留,必须为 0
bit 5 - 屏幕起始位置标志,0 = 原点在左下角,1 = 原点在左上角
一般这个字节设为0x00即可
|
00100000(2) |
18 |
可变 |
图像数据域
这里存储了(宽度)x(高度)个像素,每个像素中的 rgb 色值该色值包含整数个字节 |
... |
如果一切顺利,读取文件后关闭文件。 |  |
FILE *file = fopen(filename, "rb");
if( file==NULL ||
fread(TGAcompare,1,sizeof(TGAcompare),file)!=sizeof(TGAcompare) ||
memcmp(TGAheader,TGAcompare,sizeof(TGAheader))!=0 ||
fread(header,1,sizeof(header),file)!=sizeof(header))
{
if (file == NULL)
return false;
else
{
fclose(file);
return false;
}
}
 |
下面的代码记录文件的宽度和高度,并判断文件是否为24位/32位TGA文件。 |
 |
texture->width = header[1] * 256 + header[0];
texture->height = header[3] * 256 + header[2];
if( texture->width <=0 ||
texture->height <=0 ||
(header[4]!=24 && header[4]!=32))
{
fclose(file);
return false;
}
 |
下面的代码记录文件的位深和加载它需要的内存大小 |
 |
texture->bpp = header[4];
bytesPerPixel = texture->bpp/8;
imageSize = texture->width*texture->height*bytesPerPixel;
 |
下面的代码为图像数据分配内存并载入它 |
 |
texture->imageData=(GLubyte *)malloc(imageSize);
if( texture->imageData==NULL ||
fread(texture->imageData, 1, imageSize, file)!=imageSize)
{
if(texture->imageData!=NULL)
free(texture->imageData);
fclose(file);
return false;
}
 |
TGA文件中,颜色的存储顺序为BGR,而OpenGL中颜色的顺序为RGB,所以我们需要交换每个象素中R和B的值。如果一切顺利,TGA文件中的图像数据将按照OpenGL的要求存储在内存中了。 |
 |
for(GLuint i=0; i<int(imageSize); i+=bytesPerPixel)
{
temp=texture->imageData[i];
texture->imageData[i] = texture->imageData[i + 2];
texture->imageData[i + 2] = temp;
}
fclose (file);
 |
下面的代码创建一个纹理,并设置过滤方式为线性 |
 |
glGenTextures(1, &texture;[0].texID);
glBindTexture(GL_TEXTURE_2D, texture[0].texID);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 |
判断图像的位数是否为24,如果是则设置类型为GL_RGB |
 |
if (texture[0].bpp==24)
{
type=GL_RGB;
}
 |
下面的代码在OpenGL中创建一个纹理 |
 |
glTexImage2D(GL_TEXTURE_2D, 0, type, texture[0].width, texture[0].height, 0, type, GL_UNSIGNED_BYTE, texture[0].imageData);
return true;
}
 |
下面的代码是从图像创建字体的典型的方法,这些代码将包含在后面的课程中,以显示文字。
只有一个不同的地方,纹理0用来保存字符图像。 |
 |
GLvoid BuildFont(GLvoid)
{
base=glGenLists(256);
glBindTexture(GL_TEXTURE_2D, textures[0].texID);
for (int loop1=0; loop1<256; loop1++)
{
float cx=float(loop1%16)/16.0f;
float cy=float(loop1/16)/16.0f;
glNewList(base+loop1,GL_COMPILE);
glBegin(GL_QUADS);
glTexCoord2f(cx,1.0f-cy-0.0625f);
glVertex2d(0,16);
glTexCoord2f(cx+0.0625f,1.0f-cy-0.0625f);
glVertex2i(16,16);
glTexCoord2f(cx+0.0625f,1.0f-cy-0.001f);
glVertex2i(16,0);
glTexCoord2f(cx,1.0f-cy-0.001f);
glVertex2i(0,0);
glEnd();
glTranslated(14,0,0);
glEndList();
}
}
 |
下面的函数用来删除显示字符的显示列表 |
 |
GLvoid KillFont(GLvoid)
{
glDeleteLists(base,256);
}
 |
glPrint函数只有一点变化,我们在Y轴方向把字符拉长一倍 |
 |
GLvoid glPrint(GLint x, GLint y, int set, const char *fmt, ...)
{
char text[1024];
va_list ap;
if (fmt == NULL)
return;
va_start(ap, fmt);
vsprintf(text, fmt, ap);
va_end(ap);
if (set>1)
{
set=1;
}
glEnable(GL_TEXTURE_2D);
glLoadIdentity();
glTranslated(x,y,0);
glListBase(base-32+(128*set));
glScalef(1.0f,2.0f,1.0f);
glCallLists(strlen(text),GL_UNSIGNED_BYTE, text);
glDisable(GL_TEXTURE_2D);
}
 |
窗口改变大小的函数使用正投影,把视口范围设置为(0,0)-(640,480) |
 |
GLvoid ReSizeGLScene(GLsizei width, GLsizei height)
{
swidth=width;
sheight=height;
if (height==0)
{
height=1;
}
glViewport(0,0,width,height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0f,640,480,0.0f,-1.0f,1.0f);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
 |
初始化操作非常简单,我们载入字体纹理,并创建字符显示列表,如果顺利,则成功返回。 |
 |
int InitGL(GLvoid)
{
if (!LoadTGA(&textures;[0],"Data/Font.TGA"))
{
return false;
}
BuildFont();
glShadeModel(GL_SMOOTH);
glClearColor(0.0f, 0.0f, 0.0f, 0.5f);
glClearDepth(1.0f);
glBindTexture(GL_TEXTURE_2D, textures[0].texID);
return TRUE;
}
 |
绘制代码几乎是全新的:),token为一个指向字符串的指针,它将保存OpenGL扩展的全部字符串,cnt纪录扩展的个数。
接下来清楚背景,并显示OpenGL的销售商,实现它的公司和当前的版本。 |
 |
int DrawGLScene(GLvoid)
{
char *token;
int cnt=0;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glColor3f(1.0f,0.5f,0.5f);
glPrint(50,16,1,"Renderer");
glPrint(80,48,1,"Vendor");
glPrint(66,80,1,"Version");
 |
下面的代码显示OpenGL实现方面的相关信息,完成之后我们用蓝色在屏幕的下方写上“NeHe Productions”,当然你可以使用任何你想使用的字符,比如"DancingWind
Translate"。 |
 |
glColor3f(1.0f,0.7f,0.4f);
glPrint(200,16,1,(char *)glGetString(GL_RENDERER));
glPrint(200,48,1,(char *)glGetString(GL_VENDOR));
glPrint(200,80,1,(char *)glGetString(GL_VERSION));
glColor3f(0.5f,0.5f,1.0f);
glPrint(192,432,1,"NeHe Productions");
 |
现在我们绘制显示扩展名的白色线框方块,并用一个更大的白色线框方块把所有的内容包围起来。 |
 |
glLoadIdentity();
glColor3f(1.0f,1.0f,1.0f);
glBegin(GL_LINE_STRIP);
glVertex2d(639,417);
glVertex2d( 0,417);
glVertex2d( 0,480);
glVertex2d(639,480);
glVertex2d(639,128);
glEnd();
glBegin(GL_LINE_STRIP);
glVertex2d( 0,128);
glVertex2d(639,128);
glVertex2d(639, 1);
glVertex2d( 0, 1);
glVertex2d( 0,417);
glEnd();
 |
glScissor函数用来设置剪裁区域,如果启用了GL_SCISSOR_TEST,绘制的内容只能在剪裁区域中显示。
下面的代码设置窗口的中部为剪裁区域,并获得扩展名字符串。 |
 |
glScissor(1 ,int(0.135416f*sheight),swidth-2,int(0.597916f*sheight));
glEnable(GL_SCISSOR_TEST);
char* text=(char*)malloc(strlen((char *)glGetString(GL_EXTENSIONS))+1);
strcpy (text,(char *)glGetString(GL_EXTENSIONS));
 |
下面我们创建一个循环,循环显示每个扩展名,并纪录扩展名的个数 |
 |
token=strtok(text," ");
while(token!=NULL)
{
cnt++;
if (cnt>maxtokens)
{
maxtokens=cnt;
}
 |
现我们已经获得第一个扩展名,下一步我们把它显示在屏幕上。
我们已经显示了三行文本,它们在Y轴上占用了3*32=96个像素的宽度,所以我们显示的第一个行文本的位置是(0,96),一次类推第i行文本的位置是(0,96+(cnt*32)),但我们需要考虑当前滚动过的位置,默认为向上滚动,所以我们得到显示第i行文本的位置为(0,96+(cnt*32)=scroll)。
当然它们不会都显示出来,记得我们使用了剪裁,只显示(0,96)-(0,96+32*9)之间的文本,其它的都被剪裁了。
更具我们上面的讲解,显示的第一个行如下:
1 GL_ARB_multitexture |  |
glColor3f(0.5f,1.0f,0.5f);
glPrint(0,96+(cnt*32)-scroll,0,"%i",cnt);
glColor3f(1.0f,1.0f,0.5f);
glPrint(50,96+(cnt*32)-scroll,0,token);
 |
当我们显示完所有的扩展名,我们需要检查一下是否已经分析完了所有的字符串。我们使用strtok(NULL,"
")函数代替strtok(text," ")函数,把第一个参数设置为NULL会检查当前指针位置到字符串末尾是否包含"
"字符,如果包含返回其位置,否则返回NULL。
我们举例说明上面的过程,例如字符串"GL_ARB_multitexture GL_EXT_abgr GL_EXT_bgra",它是以空格分割字符串的,第一次调用strtok("text","
")返回text的首位置,并在空格" "的位置加入一个NULL。以后每次调用,删除NULL,返回空格位置的下一个位置,接着搜索下一个空格的位置,并在空格的位置加入一个NULL。直道返回NULL。
返回NULL时循环停止,表示已经显示完所有的扩展名。 |  |
token=strtok(NULL," ");
}
 |
下面的代码让OpenGL返回到默认的渲染状态,并释放分配的内存资源 |
 |
glDisable(GL_SCISSOR_TEST);
free (text);
 |
下面的代码让OpenGL完成所有的任务,并返回TRUE |
 |
glFlush();
return TRUE;
}
 |
KillGLWindow函数基本没有变化,唯一改变的是需要删除我们创建的字体 |
 |
KillFont();
 |
CreateGLWindow(), 和 WndProc() 函数保持不变
在WinMain()函数中我们需要加入新的按键控制 |
 |
 | 下面的代码检查向上的箭头是否被按下,如果scroll大于0,我们把它减少2
|  |
if (keys[VK_UP] && (scroll>0))
{
scroll-=2;
}
 |
如果向下的箭头被按住,并且scroll小于32*(maxtoken-9),则增加scroll的值,32是每一个字符的高度,9是可以显示的行数。 |
 |
if (keys[VK_DOWN] && (scroll<32*(maxtokens-9)))
{
scroll+=2;
}
 |
我希望你觉得这个教程有趣,学完了这个教程你应该知道如何获得你的显卡的发售商的名称,实现OpenGL的组织和你的显卡所使用的OpenGL的版本。进一步,你应该知道你的显卡支持的扩展的名称,并熟练的使用剪切矩形和加载TGA图像。
如果你发现任何问题,请让我知道。我想做最好的教程,你的反馈对我很重要。
 |
版权与使用声明:
我是个对学习和生活充满激情的普通男孩,在网络上我以DancingWind为昵称,我的联系方式是[email protected],如果你有任何问题,都可以联系我。
引子
网络是一个共享的资源,但我在自己的学习生涯中浪费大量的时间去搜索可用的资料,在现实生活中花费了大量的金钱和时间在书店中寻找资料,于是我给自己起了个昵称DancingWind,其意义是想风一样从各个知识的站点中吸取成长的养料。在飘荡了多年之后,我决定把自己收集的资料整理为一个统一的资源库。
版权声明
所有DancingWind发表的内容,大多都来自共享的资源,所以我没有资格把它们据为己有,或声称自己为这些资源作出了一点贡献。故任何人都可以复制,修改,重新发表,甚至以自己的名义发表,我都不会追究,但你在做以上事情的时候必须保证内容的完整性,给后来的人一个完整的教程。最后,任何人不能以这些资料的任何部分,谋取任何形式的报酬。
发展计划
在国外,很多资料都是很多人花费几年的时间慢慢积累起来的。如果任何人有兴趣与别人共享你的知识,我很欢迎你与我联系,但你必须同意我上面的声明。
感谢
感谢我的母亲一直以来对我的支持和在生活上的照顾。
感谢我深爱的女友田芹,一直以来默默的在精神上和生活中对我的支持,她甚至把买衣服的钱都用来给我买书了,她真的是我见过的最好的女孩,希望我能带给她幸福。
源码 RAR格式 |
| <
第23课 |
第25课
> |