正如标题所示,我在加载.tga文件时遇到了问题。我一直在使用this作为参考,据我所知(除了使用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_;
};
.cpp的:
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;
}
答案 0 :(得分:3)
当您读入数据时,假设targa文件每像素有24位(file.read((char*)data_, imageSize * 3)
),而不检查bpp_
的值。然后,您将图像数据传递给OpenGL,并说您的数据是32-bpp BGRA格式。
您应该检查bpp_
的值是什么,根据该值分配和读取数据,并将正确的数据格式传递给OpenGL(BGR或BGRA,具体取决于alpha通道是否包含在图像与否)。
答案 1 :(得分:1)
是的,你正在做同样的事情:)
如果你查看下面用于参考的代码的注释,它会读取数据的代码,假设运行它的计算机是大端(如PS3)。我猜这里,但你是在PC上运行(使用小端)?
获取endian-ness错误会使所有数据类型大于一个字节得到错误的值(height_ / width_),您需要检查运行该程序的计算机是否使用big-little-endian并更正相应的价值观。
要将height_和width_转换为正确的字节序,您应该只需使用; ntohs and ntohl(网络字节顺序为大端)
height_ = ntohs(height_);
width_ = ntohs(width_);
答案 2 :(得分: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_t
({C}标准的一部分)中的stdint.h
或更好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的标记。因此,请使用一个小的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, ...);