2

正如标题所示,我在加载 .tga 文件时遇到了问题。我一直在使用作为参考,据我所知(除了使用 c++ 函数而不是 c 函数)我正在做同样的事情。我确实得到了纹理,但它应该是乱码版本,我不确定为什么。请原谅所有的错误,我只是想让它工作。

标题:

struct TGA
{
    GLuint Load(const char* filename);
    unsigned char header_[12];
    unsigned char bpp_;
    unsigned char id_;
    unsigned short width_;
    unsigned short height_;
    unsigned char* data_;
};

价格:

GLuint TGA::Load(const char* filename)
{
width_ = 0; height_ = 0;
bpp_ = 32; id_ = 8;
data_ = 0;

std::ifstream file;

file.open(filename, std::ios::binary | std::ios::ate);

if(file.fail())
{
    std::cout<<filename<<" could not be opened."<<std::endl;
    return 0;
}

file.seekg(0, std::ios::beg);
file.read((char*)header_, sizeof(unsigned char)*12);
file.read((char*)&width_, sizeof(unsigned short));
file.read((char*)&height_, sizeof(unsigned short));
file.read((char*)&bpp_, sizeof(unsigned char));
file.read((char*)&id_, sizeof(unsigned char));

int imageSize = width_ * height_;
std::cout<<imageSize<<std::endl;

data_ = new unsigned char[imageSize * 3];  //3 means RGB 
file.read((char*)data_, imageSize * 3);

file.close();

GLuint texture;
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width_, height_, 0, GL_BGRA, GL_UNSIGNED_BYTE, data_);

glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );

glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

gluBuild2DMipmaps(GL_TEXTURE_2D, 3, width_, height_, GL_RGBA, GL_UNSIGNED_BYTE, data_);


delete [] data_;

return texture;
}
4

3 回答 3

3

当您读入数据时,您假设 targa 文件每像素有 24 位 ( file.read((char*)data_, imageSize * 3)) 而不检查bpp_. 然后将图像数据传递给 OpenGL,并说您的数据是 32-bpp BGRA 格式。

您应该检查值bpp_是什么,根据该值分配和读取数据,并将正确的数据格式传递给 OpenGL(BGR 或 BGRA,取决于图像中是否包含 alpha 通道)。

于 2012-01-28T17:12:18.743 回答
1

是的,你在做同样的事情:)

如果您查看用作参考的代码下方的注释,它读取数据的代码假设运行它的计算机是大端(如 PS3)。我在这里猜,但是您是否在 PC 上运行(使用小端序)?

弄错字节序会使你所有大于一个字节的数据类型得到错误的值(height_/width_),你需要检查运行程序的计算机是使用大端还是小端并相应地更正这些值。

要将 height_ 和 width_ 转换为正确的字节序,您应该能够使用;ntohs 和 ntohl(网络字节顺序为大端)

height_ = ntohs(height_);
width_  = ntohs(width_);
于 2012-01-28T17:09:02.123 回答
1

首先让我注意到,你做的事情比最糟糕的方法更好(读入一个打包的结构)。对于强大的文件解析器,请始终逐段读取文件。这是避免错误和漏洞利用的唯一方法。

不幸的是,仍然存在一些问题,但这些问题很容易解决:

struct TGA
{
    GLuint Load(const char* filename);
    unsigned char header_[12];
    unsigned char bpp_;
    unsigned char id_;

    unsigned short width_;
    unsigned short height_;

宽度和高度短可能会成为问题。魔鬼在细节中,但我会及时解释。现在我们应该尊重在某些架构short上实际上可能对我们来说太短了。为了uint16_tstdint.h(C99 标准的一部分)或更好的安全方面使用uint32_t。我们会看看为什么。

    unsigned char* data_;
};

GLuint TGA::Load(const char* filename)
{
width_ = 0; height_ = 0;
bpp_ = 32; id_ = 8;
data_ = 0;

无论如何我们都会覆盖那些,所以不需要清除它们,但也没有伤害。

std::ifstream file;

file.open(filename, std::ios::binary | std::ios::ate);

if(file.fail())
{
    std::cout<<filename<<" could not be opened."<<std::endl;
    return 0;
}

file.seekg(0, std::ios::beg);
file.read((char*)header_, sizeof(unsigned char)*12);

file.read((char*)&width_, sizeof(unsigned short));
file.read((char*)&height_, sizeof(unsigned short));

好的,这些有点问题,因为他们假设程序在小端机器上运行。假设您要为 PowerPC 开发(想想 PlayStation 3、XBox 或以大端模式运行的 ARM),此代码会中断。解决方案:

char buf[2]; file.read(buf, 2); width = buf[0] | buf[1]<<8;高度也一样。如果在 char 大小不是 8 的机器上运行,此代码仍然存在问题,但这些天在很远和很少之间。如果您想安全起见,请在#if CHAR_BITS!=8 #error "char bits not 8" #endif某处添加。

file.read((char*)&bpp_, sizeof(unsigned char));
file.read((char*)&id_, sizeof(unsigned char));

现在以下行是错误的:

int imageSize = width_ * height_; // <<<<<<<<<

由于width_height_都是类型short,因此乘法将强制转换为宽度短。C 和 C++ 的这种特殊行为是讨厌的、可利用的错误的最大来源之一。假设我会给你一张声明了标头的图片width = height 2^15-1,这个数字很适合预期的 16 位。如果您将这两个相乘并强制将其转换为短,您将得到…… 1. 然后还有一点其他问题。

std::cout<<imageSize<<std::endl;

data_ = new unsigned char[imageSize * 3];  //3 means RGB 
file.read((char*)data_, imageSize * 3);

好消息是,您只能读取通过代理变量分配的字节数,因此我无法将代码注入您的程序。但是我可以让它崩溃,因为稍后您的 glTexImage2D 调用将尝试从未分配的存储中读取 (2^15 -1)^2 字节,从而导致段错误。避免这种情况的技巧是将单个变量转换为计算中足够大的类型。或者你按照我的建议做,也可以使用uint32_t宽度和高度。我按照这样的规则进行编码,即每个变量都应至少保留其中最大允许值所需的位的两倍。不幸的是,C 标准不能“固定”以强制声明 L 类型的大小,因为这会破坏许多现有代码。

您遇到的另一个问题是,您对 3 个通道进行了硬编码。为什么?你有 bpp-header,它告诉你格式。所以让我们改变一下:uint32_t imageSize = (uint_32)width_ * height_ * bpp_/8;. 请注意,只需将表达式的一个变量转换为较大的类型。但是不要犯这个错误:(uint32_t)(width_ * height_ * bpp_/8)这会将短类型结果转换为 uint32_t - 请注意括号。

最后但同样重要的是,我们可以将其传递给 glTexImage2D 或 gluBuildMipMap2D。我强烈建议不要使用 gluBuildMipmap,因为它会将您的纹理转换为 2 次方维度,而从 OpenGL-2 开始就不再需要了。而是调用glGenerateMipmap的格式参数不应该是通道数,而是GL_RED、GL_RG、GL_RGB、GL_RGBA的一个token。所以为此使用一个小的 switch 语句:

GLenum internal_format;
GLenum format;
GLenum buffer_format;
switch(bpp_) {
case 8:
    internal_format = GL_R8;
    format = GL_RED;
    buffer_format = GL_UNSIGNED_BYTE;
    break;
case 16:
    internal_format = GL_RGB5;
    format = GL_RGB;
    buffer_format = GL_UNSIGNED_SHORT_5_6_5;
    break;
case 24:
    internal_format = GL_RGB8;
    format = RGB;
    buffer_format = GL_UNSIGNED_BYTE;
    break;
case 32:
    internal_format = GL_RGBA8;
    format = RGB;
    buffer_format = GL_UNSIGNED_BYTE;
    break;
default:
    format_unsupported_error(); return;
}

glTexImage2D(..., internal_format, ..., internal_format, format, ...);
于 2012-01-28T19:03:49.767 回答