 |
第33课
|
 |
 |
加载压缩和未压缩的TGA文件:
在这一课里,你将学会如何加载压缩和为压缩的TGA文件,由于它使用RLE压缩,所以非常的简单,你能很快地熟悉它的。 |
|
 |
 |
我见过很多人在游戏开发论坛或其它地方询问关于TGA读取的问题。接下来的程序及注释将会向你展示如何读取未压缩的TGA文件和RLE压缩的文件。这个详细的教程适合于OpenGL,但是我计划改进它使其在将来更具普遍性。
我们将从两个头文件开始。第一个文件控制纹理结构,在第二个里,结构和变量将为程序读取所用。
就像每个头文件那样,我们需要一些包含保护措施以防止文件被重复包含。
在文件的顶部加入这样几行程序:
|  |
#ifndef __TEXTURE_H__
#define __TEXTURE_H__
 |
然后滚动到程序底部并添加: |
 |
#endif
 |
这三行程序防止此文件被重复包含。文件中剩下的代码将处于这头两行和这最后一行之间。
在这个头文件中,我们将要加入完成每件工作所需的标准头文件。在#define __TGA_H__后添加如下几行: |
 |
#pragma comment(lib, "OpenGL32.lib")
#include <windows.h>
#include <stdio.h>
#include <gl\gl.h>
 |
第一个头文件是标准Windows头文件,第二个是为我们稍后的文件I/O所准备的,第三个是OpenGL32.lib所需的标准OpenGL头文件。
我们将需要一块空间存储图像数据以及OpenGL生成纹理所需的类型。我们将要用到以下结构: |
 |
typedef struct
{
GLubyte* imageData;
GLuint bpp;
GLuint width;
GLuint height;
GLuint texID;
GLuint type;
} Texture;
 |
现在说说其它的,更长的头文件。同样我们需要一些包含保护措施,这和上述最后一个是一样的。
接下来,看看另外两个结构,它们将在处理TGA文件的过程中使用。
|  |
typedef struct
{
GLubyte Header[12];
} TGAHeader;
typedef struct
{
GLubyte header[6];
GLuint bytesPerPixel;
GLuint imageSize;
GLuint type;
GLuint Height;
GLuint Width;
GLuint Bpp;
} TGA;
 |
现在我们声明那两个结构的一些实例,那样我们可以在程序中使用它们。 |
 |
TGAHeader tgaheader;
TGA tga;
 |
我们需要定义一对文件头,那样我们能够告诉程序什么类型的文件头处于有效的图像上。如果是未压缩的TGA图像,前12字节将会是0
0 2 0 0 0 0 0 0 0 0 0,如果是RLE压缩的,则是0 0 10 0 0 0 0 0 0 0 0 0。这两个值允许我们检查正在读取的文件是否有效。
|  |
GLubyte uTGAcompare[12] = {0,0, 2,0,0,0,0,0,0,0,0,0};
GLubyte cTGAcompare[12] = {0,0,10,0,0,0,0,0,0,0,0,0};
 |
最后,我们声明两个函数用于读取过程。
|  |
bool LoadUncompressedTGA(Texture *, char *, FILE *);
bool LoadCompressedTGA(Texture *, char *, FILE *);
 |
现在,回到cpp文件,和程序中真正首当其冲部分,我将会省去一些错误消息处理代码并且使教程更短、更具可读性。你可以参看教程包含的文件(在文章的尾部有链接)。
马上,我们就可以在文件开头包含我们刚刚建立的头文件。 |
 |
#include "tga.h"
 |
不我们不需要包含其它任何文件了,因为我们已经在自己刚刚完成的头文件中包含他们了。
接下来,我们要做的事情是看看第一个函数,名为LoadTGA(…)。 |
 |
bool LoadTGA(Texture * texture, char * filename)
{
 |
它有两个参数。前者是一个指向纹理结构的指针,你必须在你的代码中声明它(见包含的例子)。后者是一个字符串,它告诉计算机在哪里去找你的纹理文件。
函数的前两行声明了一个文件指针,然后打开由“filename”参数指定的文件,它由函数的第二个指针传递进去。 |
 |
FILE * fTGA;
fTGA = fopen(filename, "rb");
 |
接下来的几行检查指定的文件是否已经正确地打开。 |
 |
if(fTGA == NULL)
{
...Error code...
return false;
}
 |
下一步,我们尝试读取文件的首12个字节的内容并且将它们存储在我们的TGAHeader结构中,这样,我们得以检查文件类型。如果fread失败,则关闭文件,显示一个错误,并且函数返回false。 |
 |
if(fread(&tgaheader, sizeof(TGAHeader), 1, fTGA) == 0)
{
...Error code here...
return false;
}
 |
接着,通过我们用辛苦编的程序刚读取的头,我们继续尝试确定文件类型。这可以告诉我们它是压缩的、未压缩甚至是错误的文件类型。为了达到这个目的,我们将会使用memcmp(…)函数。 |
 |
if(memcmp(uTGAcompare, &tgaheader, sizeof(tgaheader)) == 0)
{
LoadUncompressedTGA(texture, filename, fTGA);
}
else if(memcmp(cTGAcompare, &tgaheader, sizeof(tgaheader)) == 0)
{
LoadCompressedTGA(texture, filename, fTGA);
}
else
{
...Error code here...
return false;
}
 |
我们将要开始读取一个未压缩格式文件的章节。
下面开始我们要做的第一件事,像往常一样,是函数头。 |
 |
bool LoadUncompressedTGA(Texture * texture, char * filename, FILE * fTGA)
{
 |
这个函数有3个参数。头两个和LoadTGA中的一样,仅仅是简单的传递。第三个是来自前一个函数中的文件指针,因此我们没有丢失我们的空间。
接下来我们试着再从文件中读取6个字节的内容,并且存储在tga.header中。如果他失败了,我们运行一些错误处理代码,并且返回false。 |
 |
if(fread(tga.header, sizeof(tga.header), 1, fTGA) == 0)
{
...Error code here...
return false;
}
 |
现在我们有了计算图像的高度、宽度和BPP的全部信息。我们在纹理和本地结构中都将存储它。 |
 |
texture->width = tga.header[1] * 256 + tga.header[0];
texture->height = tga.header[3] * 256 + tga.header[2];
texture->bpp = tga.header[4];
tga.Width = texture->width;
tga.Height = texture->height;
tga.Bpp = texture->bpp;
 |
现在,我们需要确认高度和宽度至少为1个像素,并且bpp是24或32。如果这些值中的任何一个超出了它们的界限,我们将再一次显示一个错误,关闭文件,并且离开此函数。 |
 |
if((texture->width <= 0) || (texture->height <= 0) || ((texture->bpp != 24) && (texture->bpp !=32)))
{
...Error code here...
return false;
}
 |
接下来我们设置图像的类型。24 bit图像是GL_RGB,32 bit 图像是GL_RGBA |
 |
if(texture->bpp == 24)
{
texture->type = GL_RGB;
}
else
{
texture->type = GL_RGBA;
}
 |
现在我们计算每像素的字节数和总共的图像数据。 |
 |
tga.bytesPerPixel = (tga.Bpp / 8);
tga.imageSize = (tga.bytesPerPixel * tga.Width * tga.Height);
 |
我们需要一些空间去存储整个图像数据,因此我们将要使用malloc分配正确的内存数量
然后我们确认内存已经分配,并且它不是NULL。如果出现了错误,则运行错误处理代码。 |
 |
texture->imageData = (GLubyte *)malloc(tga.imageSize);
if(texture->imageData == NULL)
{
...Error code here...
return false;
}
 |
这里我们尝试读取所有的图像数据。如果不能,我们将再次触发错误处理代码。 |
 |
if(fread(texture->imageData, 1, tga.imageSize, fTGA) != tga.imageSize)
{
...Error code here...
return false;
}
 |
TGA文件用逆OpenGL需求顺序的方式存储图像,因此我们必须将格式从BGR到RGB。为了达到这一点,我们交换每个像素的第一个和第三个字节的内容。
Steve Thomas补充:我已经编写了能稍微更快速读取TGA文件的代码。它涉及到仅用3个二进制操作将BGR转换到RGB的方法。
然后我们关闭文件,并且成功退出函数。 |
 |
for(GLuint cswap = 0; cswap < (int)tga.imageSize; cswap += tga.bytesPerPixel)
{
texture->imageData[cswap] ^= texture->imageData[cswap+2] ^=
texture->imageData[cswap] ^= texture->imageData[cswap+2];
}
fclose(fTGA);
return true;
}
 |
以上是读取未压缩型TGA文件的方法。读取RLE压缩型文件的步骤稍微难一点。我们像平时一样读取文件头并且收集高度/宽度/色彩深度,这和读取未压缩版本是一致的。 |
 |
bool LoadCompressedTGA(Texture * texture, char * filename, FILE * fTGA)
{
if(fread(tga.header, sizeof(tga.header), 1, fTGA) == 0)
{
...Error code here...
}
texture->width = tga.header[1] * 256 + tga.header[0];
texture->height = tga.header[3] * 256 + tga.header[2];
texture->bpp = tga.header[4];
tga.Width = texture->width;
tga.Height = texture->height;
tga.Bpp = texture->bpp;
if((texture->width <= 0) || (texture->height <= 0) || ((texture->bpp != 24) && (texture->bpp !=32)))
{
...Error code here...
} }
tga.bytesPerPixel = (tga.Bpp / 8);
tga.imageSize = (tga.bytesPerPixel * tga.Width * tga.Height);
 |
现在我们需要分配存储图像所需的空间,这是为我们解压缩之后准备的,我们将使用malloc。如果内存分配失败,运行错误处理代码,并且返回false。 |
 |
texture->imageData = (GLubyte *)malloc(tga.imageSize);
if(texture->imageData == NULL)
{
...Error code here...
return false;
}
 |
下一步我们需要决定组成图像的像素数。我们将它存储在变量“pixelcount”中。
我们也需要存储当前所处的像素,以及我们正在写入的图像数据的字节,这样避免溢出写入过多的旧数据。
我们将要分配足够的内存来存储一个像素。 |
 |
GLuint pixelcount = tga.Height * tga.Width;
GLuint currentpixel = 0;
GLuint currentbyte = 0;
GLubyte * colorbuffer = (GLubyte *)malloc(tga.bytesPerPixel);
 |
接下来我们将要进行一个大循环。
让我们将它分解为更多可管理的块。
首先我们声明一个变量来存储“块”头。块头指示接下来的段是RLE还是RAW,它的长度是多少。如果一字节头小于等于127,则它是一个RAW头。头的值是颜色数,是负数,在我们处理其它头字节之前,我们先读取它并且拷贝到内存中。这样我们将我们得到的值加1,然后读取大量像素并且将它们拷贝到ImageData中,就像我们处理未压缩型图像一样。如果头大于127,那么它是下一个像素值随后将要重复的次数。要获取实际重复的数量,我们将它减去127以除去1bit的的头标示符。然后我们读取下一个像素并且依照上述次数连续拷贝它到内存中。 |
 |
do
{
GLubyte chunkheader = 0;
if(fread(&chunkheader, sizeof(GLubyte), 1, fTGA) == 0)
{
...Error code...
return false;
}
 |
接下来我们将要看看它是否是RAW头。如果是,我们需要将此变量的值加1以获取紧随头之后的像素总数。 |
 |
if(chunkheader < 128)
{
chunkheader++;
 |
我们开启另一个循环读取所有的颜色信息。它将会循环块头中指定的次数,并且每次循环读取和存储一个像素。
首先,我们读取并检验像素数据。单个像素的数据将被存储在colorbuffer变量中。然后我们将检查它是否为RAW头。如果是,我们需要添加一个到变量之中以获取头之后的像素总数。 |
 |
for(short counter = 0; counter < chunkheader; counter++)
{
if(fread(colorbuffer, 1, tga.bytesPerPixel, fTGA) != tga.bytesPerPixel)
{
...Error code...
return false;
}
 |
我们循环中的下一步将要获取存储在colorbuffer中的颜色值并且将其写入稍后将要使用的imageData变量中。在这个过程中,数据格式将会由BGR翻转为RGB或由BGRA转换为RGBA,具体情况取决于每像素的比特数。当我们完成任务后我们增加当前的字节和当前的像素计数器。 |
 |
texture->imageData[currentbyte] = colorbuffer[2];
texture->imageData[currentbyte + 1 ] = colorbuffer[1];
texture->imageData[currentbyte + 2 ] = colorbuffer[0];
if(tga.bytesPerPixel == 4)
{
texture->imageData[currentbyte + 3] = colorbuffer[3];
}
currentbyte += tga.bytesPerPixel;
currentpixel++;
 |
下一段处理描述RLE段的“块”头。首先我们将chunkheader减去127来得到获取下一个颜色重复的次数。 |
 |
else
{
chunkheader -= 127;
 |
然后我们尝试读取下一个颜色值。 |
 |
if(fread(colorbuffer, 1, tga.bytesPerPixel, fTGA) != tga.bytesPerPixel)
{
...Error code...
return false;
}
 |
接下来,我们开始循环拷贝我们多次读到内存中的像素,这由RLE头中的值规定。
然后,我们将颜色值拷贝到图像数据中,预处理R和B的值交换。
随后,我们增加当前的字节数、当前像素,这样我们再次写入值时可以处在正确的位置。
|  |
for(short counter = 0; counter < chunkheader; counter++)
{
texture->imageData[currentbyte] = colorbuffer[2];
texture->imageData[currentbyte + 1 ] = colorbuffer[1];
texture->imageData[currentbyte + 2 ] = colorbuffer[0];
if(tga.bytesPerPixel == 4)
{
texture->imageData[currentbyte + 3] = colorbuffer[3];
}
currentbyte += tga.bytesPerPixel;
currentpixel++;
 |
只要仍剩有像素要读取,我们将会继续主循环。
最后,我们关闭文件并返回成功。
|  |
while(currentpixel < pixelcount);
fclose(fTGA);
return true;
}
 |
现在你已经为glGenTextures和glBindTexture准备好了数据。我建议你查看Nehe的教程6和24以获取这些命令的更多信息。那证实了我先前写的教程的正确性,我不确保的代码中没有错误,虽然我努力使之不发生错误。特别感谢Jeff“Nehe”Molofee写了这个伟大的教程,以及Trent“ShiningKnight”Polack帮助我修订这个教程。如果你发现了错误、有建议或者注释,请自由地给我发Email
 |
版权与使用声明:
我是个对学习和生活充满激情的普通男孩,在网络上我以DancingWind为昵称,我的联系方式是[email protected],如果你有任何问题,都可以联系我。
引子
网络是一个共享的资源,但我在自己的学习生涯中浪费大量的时间去搜索可用的资料,在现实生活中花费了大量的金钱和时间在书店中寻找资料,于是我给自己起了个昵称DancingWind,其意义是想风一样从各个知识的站点中吸取成长的养料。在飘荡了多年之后,我决定把自己收集的资料整理为一个统一的资源库。
版权声明
所有DancingWind发表的内容,大多都来自共享的资源,所以我没有资格把它们据为己有,或声称自己为这些资源作出了一点贡献。故任何人都可以复制,修改,重新发表,甚至以自己的名义发表,我都不会追究,但你在做以上事情的时候必须保证内容的完整性,给后来的人一个完整的教程。最后,任何人不能以这些资料的任何部分,谋取任何形式的报酬。
发展计划
在国外,很多资料都是很多人花费几年的时间慢慢积累起来的。如果任何人有兴趣与别人共享你的知识,我很欢迎你与我联系,但你必须同意我上面的声明。
感谢
感谢我的母亲一直以来对我的支持和在生活上的照顾。
感谢我深爱的女友田芹,一直以来默默的在精神上和生活中对我的支持,她甚至把买衣服的钱都用来给我买书了,她真的是我见过的最好的女孩,希望我能带给她幸福。
源码 RAR格式 |
| <
第32课 |
第34课
> |