Metodología de representación de mapas de bits en OpenGL?

He estado trabajando en un poco de un analizador de bitmap últimamente en C pura para entender el funcionamiento de bajo nivel de los formatos de imagen más simples. Hasta ahora, al utilizar el artículo de Wikipedia en archivos de bitmap, he podido (lo que creo al menos) analizar correctamente la información, al menos, la mayoría.

El problema es que no estoy muy seguro de qué hacer a partir de ahí: desde que trabajo con un contexto 3.1, tengo acceso a muchas funciones más modernizadas, lo cual es bueno, aunque todavía estoy perdido. Tengo una configuración de ventana con GLFW y hasta ahora no he renderizado nada porque me he centrado en el análisis / detalles de bajo nivel.

Ya que estoy tratando de evitar realmente mirar los ejemplos de código reales, sería bueno si alguien me explicara qué es el proceso involucrado en la representación de un bitmap, utilizando solo OpenGL / GLFW y la biblioteca estándar de ISO C.

Mientras tengo un par de sombreadores en su lugar y puedo cargarlos sin ningún problema, estoy pensando que lo que necesito hacer es renderizar un quad [invisible] que se ajuste a las dimensiones (ancho, alto) del imagen en sí, y luego pasar los datos de píxeles a OpenGL. El principal problema, sin embargo, es que los shaders están configurados de esta manera:

Vertex Shader

#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; } 

Fragment Shader

 #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; } 

Y no estoy seguro de cómo obtener las coordenadas UV reales que requiere el sombreador. Estoy pensando que necesitaré generar los vértices, almacenarlos en una matriz y llamar a algo a lo largo de las líneas de glVertexAttribPointer(...) para las coordenadas UV, pero no estoy seguro de qué datos debo usar del imagen para obtener esto, o incluso si ya lo tengo analizado dentro de la función. Me imagino que implicaría rastrear la imagen utilizando un bucle interno / externo (el exterior representa la x, la interna y) en una fila / columna. Aún así, me siento algo confundido acerca de esto y no estoy seguro de si esto es lo que necesito.

De cualquier manera, cualquier consejo sobre cómo hacer esto sería muy apreciado.


El código real para analizar la imagen ( HEADER_LENGTH = 54 bytes):

 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; } 

Y mi salida de depuración de acuerdo con la información de la imagen obtenida de la imagen actual tiene el siguiente aspecto:

 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 } 

Primero déjeme decir: su enfoque en la lectura del encabezado es casi perfecto. Solo inconveniente: su código no trata con Endianess y está truncando los campos de su encabezado (se romperá para las imágenes de más de 255 en cualquier dimensión).

Aquí hay una solución

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

Y el mismo patrón para todos los demás campos de más de 8 bits. El lanzamiento para cada campo de encabezado es necesario para evitar el truncamiento. Convertirlo en el tipo de variable de destino. Además, no se preocupe por el rendimiento, los comstackdores modernos convierten esto en un código muy eficiente.

Hasta ahora su función todavía carece de lectura de los datos de la imagen. Asumiré que los datos estarán en un campo data->pixels más adelante.

Después de que hayas leído tu imagen, puedes pasarla a OpenGL. OpenGL gestiona sus imágenes en los llamados "objetos de textura". La estrofa habitual es:

  1. Crear un nombre de objeto de textura con glGenTextures
  2. Enlazar el objeto de textura con glBindTexture
  3. Establezca los parámetros de transferencia de píxeles con glPixelStorei en todos los parámetros GL_UNPACK_ ...
  4. Sube la textura con glTexImage2D 5.

    • Turno de mipmapping

    o

    • Generar Mipmaps.

Esto va como sigue

 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); 

El tipo GL_UNSIGNED_INT_8_8_8_8 necesita una explicación. Verá, los DIB tratan un entero sin signo de 32 bits al igual que una estructura de color compuesta. De hecho, en Windows puede encontrar un tipo de color, que es un entero de tipo typedef-ed. Y eso es lo que contienen los DIB. Al utilizar el formato BGRA con el tipo entero de 4 × 8 componentes, hacemos que OpenGL descomprima un píxel exactamente en ese formato.

Hay mucho aquí, es posible que deba dividir esto en varias preguntas, pero aquí está el resumen:

No es necesario pasar los datos de píxeles reales a los sombreadores; lo que necesitas hacer es crear un objeto de textura en GL por adelantado usando los datos de píxeles, y luego referencia esa textura en tu sombreado. La geometría real que necesita dibujar es (como sospecha) solo un cuadrante con sus cuatro esquinas y las correspondientes coordenadas de textura (que en este caso son triviales, solo 0.0 y 1.0 para cada eje en las esquinas).

La magia de los sombreadores es que el sombreado de fragmentos se ejecutará para cada píxel en la salida, y simplemente muestreará la textura en las diferentes coordenadas de textura que GL le da a su sombreador.

(Si es nuevo en GL, intente dibujar un quad simple primero en un color fijo para que funcione antes de intentar obtener datos BMP en una textura).