OpenGL中的位图渲染方法?

我最近在纯C中一直在研究一点位图解析器,只是为了理解更简单的图像格式的低级工作。 到目前为止,使用维基百科关于位图文件的文章 ,我能够(至少我认为)正确地解析信息 – 至少,大部分都是如此。

问题是我不太确定该怎么做:因为我一直在使用3.1上下文,我可以访问更多现代化的function,这很好,尽管我还是迷路了。 我有一个使用GLFW的窗口设置,到目前为止还没有真正渲染任何东西,因为我一直专注于解析/低级细节。

由于我正在努力避免查看实际的代码示例, 如果有人能够向我解释渲染位图背后的过程是什么,只使用OpenGL / GLFW和ISO C标准库,那就太酷了。

虽然我有几个着色器,并且我能够毫无问题地加载它们,但我认为我需要做的是渲染一个符合尺寸(宽度,高度)的[不可见]四边形。图像本身,然后将像素数据传递给OpenGL。 然而,主要问题是着色器的设置如下:

顶点着色器

#version 150 #extension GL_ARB_separate_shader_objects : enable layout(location = 0) in vec2 Position; layout(location = 1) in vec2 UV_In; out vec2 UV; void main() { gl_Position = vec4( Position, 0.0f, 1.0f ); UV = UV_In; } 

片段着色器

 #version 150 #extension GL_ARB_separate_shader_objects : enable in vec2 UV; out vec3 Output; uniform sampler2D TheSampler; void main() { Output = texture2D( TheSampler, UV ).rgb; } 

而且我不确定如何获得着色器所需的实际UV坐标。 我想我需要生成顶点,将它们存储在一个数组中,然后沿着glVertexAttribPointer(...)的行调用UV坐标,但我不确定我应该使用哪些数据图像来获得这个,甚至我是否已经在函数内解析了它。 我想它会涉及使用内/外for循环(外部代表x,内部是y)以行/列方式爬行图像。 尽管如此,我对此感到有些困惑,我不确定这是否是我需要的。

无论哪种方式,任何关于如何做到这一点的建议将不胜感激。


解析图像的实际代码( HEADER_LENGTH = 54字节):

 GLuint Image_LoadBmp( const char* fname, image_bmp_t* data ) { uint8_t header[ HEADER_LENGTH ]; FILE* f = fopen( fname, "rb" ); if ( !f ) { printf( "ERROR: file \"%s\" could not be opened [likely] due to incorrect path. :/ \n", fname ); return 0; // return false } data->filename = strdup( fname ); // TODO: write a wrapper for strdup which exits program on NULL returns const size_t num_bytes_read = fread( ( void* )header, sizeof( uint8_t ), HEADER_LENGTH, f ); if ( num_bytes_read != HEADER_LENGTH ) { printf( "ERROR: file \"%s\" could not be opened due to header size being " _SIZE_T_SPECIFIER " bytes; "\ "this is an invalid format. \n", fname, num_bytes_read ); return 0; } if ( header[ 0 ] != *( uint8_t* )"B" || header[ 1 ] != *( uint8_t* )"M" ) { printf( "ERROR: file \"%s\" does NOT have a valid signature \n", fname ); return 0; } data->image_size = *( uint32_t* )&( header[ 0x22 ] ); data->header_size = ( uint32_t )( header[ 0x0E ] ); data->width = ( uint32_t )( header[ 0x12 ] ); data->height = ( uint32_t )( header[ 0x16 ] ); data->pixel_data_pos = ( uint32_t )( header[ 0x0A ] ); data->compression_method = ( uint8_t )( header[ 0x1E ] ); data->bpp = ( uint8_t )( header[ 0x1C ] ); // TODO (maybe): add support for other compression methods if ( data->compression_method != CM_RGB ) { puts( "ERROR: file \"%s\" does NOT have a supported compression method for this parser; \n" \ "\t Currently, the compression methods supported are: \n" \ "\t - BI_RGB \n\n" ); return 0; } return 1; } 

根据从当前图像收集的图像信息,我的调试输出如下所示:

 Info for "assets/sprites/nave/nave0001.bmp" { Size = 3612 Header Size = 40 Width = 27 Height = 43 Pixel Array Address = 54 Compression Method = 0 Bits Per Pixel = 24 } 

首先让我说:你阅读标题的方法几乎是完美的。 唯一的缺点:您的代码不处理Endianess而您正在截断标题的字段(对于任何维度中大于255的图像,它都会中断。

这是一个修复

 data->image_size = (uint32_t)header[0x22] | (uint32_t)header[0x23] << 8 | (uint32_t)header[0x24] << 16 | (uint32_t)header[0x25] << 24; 

对于大于8位的所有其他字段,也使用相同的模式。 每个头字段的强制转换都是必要的,以防止截断。 将其强制转换为目标变量类型。 同样不要担心性能,现代编译器将其转换为非常高效的代码。

到目前为止,您的function仍然缺乏读取图像数据。 我将假设数据将在以后的字段data->pixels中。

阅读完图像后,可以将其传递给OpenGL。 OpenGL在所谓的“纹理对象”中管理其图像。 通常的节是:

  1. 使用glGenTextures创建纹理对象名称
  2. 使用glBindTexture绑定纹理对象
  3. 在所有GL_UNPACK_ ...参数上使用glPixelStorei设置像素传输参数
  4. 使用glTexImage2D 5上传纹理。

    • 转向mipmapping

    要么

    • 生成Mipmap。

这如下

 GLuint texName; glGenTexture(1, &texName); glBindTexture(GL_TEXTURE_2D, texName); glPixelStorei(GL_UNPACK_SWAP_BYTES, GL_FALSE); glPixelStorei(GL_UNPACK_LSB_FIRST, GL_FALSE); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); // could also be set to image size, but this is used only glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0); // if one wants to load only a subset of the image glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); glPixelStorei(GL_UNPACK_SKIP_IMAGES, 0); glPixelStorei(GL_UNPACK_SKIP_ALIGNMENT, 4); // that one's pretty important. For TrueColor DIBs the alignment is 4 GLenum internalformat; switch(data->bpp) { case 24: internalformat = GL_RGB; break; case 32: internalformat = GL_RGBA; break; } glTexImage2D(GL_TEXTURE_2D, 0, internalformat, data->width, data->height, 0 GL_BRGA, GL_UNSIGNED_INT_8_8_8_8, data->pixels); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 

GL_UNSIGNED_INT_8_8_8_8类型需要说明。 你看,DIB将32位无符号整数视为复合颜色结构。 事实上,在Windows中,您可以找到一种颜色类型,它是一个类型定义的整数。 这就是DIB中包含的内容。 通过使用具有4×8分量整数类型的BGRA格式,我们使OpenGL以完全相同的格式解包像素。

这里有很多,您可能需要将其分解为多个问题,但这里是概述:

您无需将实际像素数据传递给着色器; 你需要做的是使用像素数据在前面的GL中创建一个纹理对象,然后在着色器中引用该纹理。 您需要绘制的实际几何体(正如您所怀疑的那样)只有一个四边形及其四个角和相应的纹理坐标(在这种情况下这是微不足道的,角上的每个轴只有0.0和1.0)。

着色器的神奇之处在于片段着色器将针对输出中的每个像素运行,您只需在GL处理着色器的不同纹理坐标处对纹理进行采样。

(如果你是GL的新手,尝试先用一些固定颜色绘制一个简单的四边形,以便在尝试将BMP数据导入纹理之前使其工作。)