我正在使用OpenGL(freeglut和glew)编写一个应用程序。
我也想要纹理,所以我对位图文件格式做了一些研究,并为主标题写了一个结构,为DIB标题写了另一个结构(信息标题)。
然后我开始编写加载器。它会自动将纹理绑定到OpenGL。以下是功能:
static unsigned int ReadInteger(FILE *fp)
{
int a, b, c, d;
// Integer is 4 bytes long.
a = getc(fp);
b = getc(fp);
c = getc(fp);
d = getc(fp);
// Convert the 4 bytes to an integer.
return ((unsigned int) a) + (((unsigned int) b) << 8) +
(((unsigned int) c) << 16) + (((unsigned int) d) << 24);
}
static unsigned int ReadShort(FILE *fp)
{
int a, b;
// Short is 2 bytes long.
a = getc(fp);
b = getc(fp);
// Convert the 2 bytes to a short (int16).
return ((unsigned int) a) + (((unsigned int) b) << 8);
}
GLuint LoadBMP(const char* filename)
{
FILE* file;
// Check if a file name was provided.
if (!filename)
return 0;
// Try to open file.
fopen_s(&file, filename, "rb");
// Return if the file could not be open.
if (!file)
{
cout << "Warning: Could not find texture '" << filename << "'." << endl;
return 0;
}
// Read signature.
unsigned char signature[2];
fread(&signature, 2, 1, file);
// Use signature to identify a valid bitmap.
if (signature[0] != BMPSignature[0] || signature[1] != BMPSignature[1])
{
fclose(file);
return 0;
}
// Read width and height.
unsigned long width, height;
fseek(file, 16, SEEK_CUR); // After the signature we have 16bytes until the width.
width = ReadInteger(file);
height = ReadInteger(file);
// Calculate data size (we'll only support 24bpp).
unsigned long dataSize;
dataSize = width * height * 3;
// Make sure planes is 1.
if (ReadShort(file) != 1)
{
cout << "Error: Could not load texture '" << filename << "' (planes is not 1)." << endl;
return 0;
}
// Make sure bpp is 24.
if (ReadShort(file) != 24)
{
cout << "Error: Could not load texture '" << filename << "' (bits per pixel is not 24)." << endl;
return 0;
}
// Move pointer to beggining of data. (after the bpp we have 24 bytes until the data)
fseek(file, 24, SEEK_CUR);
// Allocate memory and read the image data.
unsigned char* data = new unsigned char[dataSize];
if (!data)
{
fclose(file);
cout << "Warning: Could not allocate memory to store data of '" << filename << "'." << endl;
return 0;
}
fread(data, dataSize, 1, file);
if (data == NULL)
{
fclose(file);
cout << "Warning: Could no load data from '" << filename << "'." << endl;
return 0;
}
// Close the file.
fclose(file);
// Create the texture.
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); //NEAREST);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, width, height, GL_BGR_EXT, GL_UNSIGNED_BYTE, data);
return texture;
}
我知道位图的数据是正确读取的,因为我将其数据输出到控制台,并与绘画中打开的图像进行比较。
这里的问题是这一行:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, dibheader.width,
dibheader.height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
大多数时候,我运行应用程序时,这一行会崩溃,并出现错误:
GunsGL.exe中0x008ffee9处未处理的异常:0xC0000005:读取位置0x00af7002时发生访问冲突。
这是错误发生位置的拆卸:
movzx ebx,byte ptr [esi+2]
这不是我的加载程序的错误,因为我已经下载了其他加载程序。我使用的下载加载程序是NeHe的。
编辑:(代码更新以上)
我重写了加载程序,但仍然在同一行发生崩溃。不是崩溃,有时我在mlock.c上崩溃(我记得正确的是相同的错误消息):
void __cdecl _lock (
int locknum
)
{
/*
* Create/open the lock, if necessary
*/
if ( _locktable[locknum].lock == NULL ) {
if ( !_mtinitlocknum(locknum) )
_amsg_exit( _RT_LOCK );
}
/*
* Enter the critical section.
*/
EnterCriticalSection( _locktable[locknum].lock );
}
在线:
EnterCriticalSection( _locktable[locknum].lock );
此外,以下是其中一次应用程序没有崩溃的屏幕截图(纹理显然不正确):https://i.stack.imgur.com/4Mtso.jpg
第2版:
用新的工作代码更新了代码。(标记为答案的回复并不包含此操作所需的所有内容,但它至关重要)
在调用glTexImage2D()
之前尝试glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
。
我知道,读取像这样的二进制数据很诱人
BitmapHeader header; BitmapInfoHeader dibheader; /*...*/ // Read header. fread(&header, sizeof(BitmapHeader), 1, file); // Read info header. fread(&dibheader, sizeof(BitmapInfoHeader), 1, file);
但你真的不应该那样做。为什么?因为结构的内存布局可能会被填充以满足对齐约束(是的,我知道打包杂注),所以使用的编译器的类型大小可能与二进制文件中的数据大小不匹配,最后但并非最不重要的是,端序可能不匹配。
始终将二进制数据读取到中间缓冲区中,通过精确指定的偏移量和类型,以定义良好的方式提取其中的字段。
// Allocate memory for the image data. data = (unsigned char*)malloc(dibheader.dataSize);
如果这是C++,则使用new
运算符。如果这是C,那么不要从void *
强制转换为L值类型,这是一种糟糕的风格,可能会包含有用的编译器警告。
// Verify memory allocation. if (!data) { free(data);
如果data
为NULL,则不能释放它。
// Swap R and B because bitmaps are BGR and OpenGL uses RGB. for (unsigned int i = 0; i < dibheader.dataSize; i += 3) { B = data[i]; // Backup Blue. data[i] = data[i + 2]; // Place red in right place. data[i + 2] = B; // Place blue in right place. }
OpenGL确实支持BGR对齐。格式参数是GL_BGR
// Generate texture image. glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, dibheader.width, dibheader.height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
好吧,这没有设置所有的像素存储参数。在进行像素传输之前,始终设置每个像素存储参数,它们可能会因先前的操作而处于某种不希望的状态。安全总比抱歉好。