Redering de Mapas de Quake 2 – Texturas

Este artículo es una continuación de “Rendering de Mapas de Quake 2“. Tras bastante más trabajo del que estimé originalmente, finalmente logré agregar soporte para realizar Mapeo de Texturas sobre los mapas BSP de Quake 2. En este artículo voy a intentar explicar los detalles sobre como funcionan los archivos de textura utilizados por Quake 2, de forma de poder dibujar el mapa textureado por Hardware, utilizando OpenGL.


Primer mapa del Juego, renderizado desde la posición de inicio del jugador con mapeo de texturas.



Quake 2 almacena sus texturas en un formato propio denominado WAL. Para realizar el mapeo de texturas fue necesario implementar soporte para este tipo de archivos de imagen. Los archivos WAL se almacenan dentro del archivo PAK del juego y son básicamente índices a una paleta de colores que también se encuentra dentro del PAK. Para poder recomponer la textura como una matriz RGB, es necesario disponer tanto de cada WAL individual como de la paleta y por lo tanto fue necesario desarrollar una forma de poder cargar estos archivos directamente desde el PAK.

La ventaja de soportar los archivos PAK originales del juego es que ahora puedo cargar los recursos del juego directamente desde ahí (incluso los mapas BSP), sin necesidad de previamente extraerlos.

Comenzemos estudiando la paleta de colores. La paleta se encuentra dentro del PAK bajo el nombre “pics/colormap.pcx”. Para poder acceder a su contenido es necesario cargarla en memoria desde el PAK como un BLOB. Este BLOB es una imagen codificada en PCX, y es necesario descomprimirla para poder utilizarla como una matriz de colores.

Luego, cada WAL correspondiente a un polígono es referenciado mediante su nombre y ruta dentro del PAK. Los WAL consisten principalmente en un header que determina, entre otras cosas, las dimensiones de la imagen y el offset donde comienzan los datos. Los datos a leer son exactamente w*h bytes a partir de offset[0]. Estos datos son una matriz de índices a la paleta de colores. Afortunadamente, estos datos no se encuentran codificados de ninguna forma y pueden utilizarse directamente para indexar la paleta de colores.

A modo de comentario, cada índice leído es exactamente un byte de tamaño, por lo cual los WAL en teoría no pueden referenciar más de 256 colores distintos en la paleta. Consecuentemente, uno esperaría que la paleta sea exactamente de 256*3 bytes, sin embargo, esto no es así. Quizás otros aspectos del motor utilizan la misma paleta y por eso existen colores que en principio no podrán ser referenciados.

Una vez que tenemos el WAL y la paleta, la textura se crea fusionándolos en una matriz de colores RGB. Esto se puede hacer de dos formas: se puede suministrar la paleta a OpenGL y luego crear una textura compuesta por índices, o bien, se puede fusionar la paleta con el WAL manualmente por Software y suministrar texturas 2D ya preparadas a OpenGL.

El primer enfoque tiene la ventaja de que se ahorra mucha memoria de video, ya que por cada textura, necesitamos almacenar solamente 1/3 de los datos que necesitaríamos para almacenar colores RGB (más la paleta una única vez). Por otro lado, este enfoque es más difícil de configurar a nivel de OpenGL (yo no logré hacerlo andar), requiere que la implementación de OpenGL soporte la extensión ARB_imaging y es más lento en tiempo de ejecución, ya que OpenGL deberá fusionar la paleta con los índices por cada cuadro. A su vez, OpenGL no soporta paletas del tamaño de la del Quake 2 e, incluso si lo hiciera, deberíamos convertir todas nuestras matrices de índices a dimensiones potencias de 2.

El segundo enfoque resuelve todos estos problemas al costo de consumir más memoria de video. Personalmente elegí este segundo enfoque, al costo de saber que probablemente mi renderer tenga problemas de performance en placas de video con poca RAM (64 MB?).

Si w,h son las dimensiones del archivo WAL, “textura” es una matriz de w*h*3 bytes, “indx” el i-ésimo índice del archivo WAL y “paleta” la paleta de colores, la forma de fusionar manualmente la paleta con los índices en un array es mediante la siguiente operación:

textura[i*3+0] = paleta[indx*3+0];
textura[i*3+1] = paleta[indx*3+1];
textura[i*3+2] = paleta[indx*3+2];

Donde i es un contador en el intervalo [0, w*h), y teniendo cuidado de no leer fuera del espacio de memoria de la paleta. Es válido referenciar colores fuera de la paleta, si bien los WAL de Quake 2 no parecen hacerlo. Si esto llegase a pasar, se deberá forzar el valor a negro (0x0).

La paleta se asume es un array unidimensional de la forma R,G,B, R,G,B, … Es por esto que la forma en que se indexa la paleta es tal que el índice 0 corresponde a los primeros 3 bytes (0,1,2), el índice 1 corresponde a los siguientes 3 (3,4,5) y así sucesivamente.

Una vez creada nuestra textura RGB, la cargamos a OpenGL y ésta queda pronta para ser utilizada.

El último detalle a tener en cuenta es la diferencia entre los sistema de coordenadas de textura de Quake 2 y de OpenGL. Quake 2 utiliza un sistema de coordenadas en píxels, mientras que OpenGL utiliza un sistema normalizado. Para solucionar esta diferencia es necesario (tras calcular las coordenadas u,v de textura), dividir u y v entre el ancho y largo de la textura respectivamente. Esto no necesariamente producirá coordenadas en el intervalo [0,1], lo cual es esperable, ya que muchas de las texturas en Quake 2 deben dibujarse con Tiling.

Sin más detalles, les dejo a continuación un par de screenshots nuevos, esta vez con la iluminación deshabilitada pero con texturas mapeadas con filtrado bilineal.


Cuarto de Ambiente Exterior en Base 1, con mapeo de texturas. Notar los Triggers puestos en el cuarto para disparar eventos en el Juego y como el cielo se ve "raro" al dibujarlo como cualquier otro polígono.

Cuarto escondido en Base 1.


This entry was posted in C++, Computación Gráfica, OpenGL, Programacion, Quake. Bookmark the permalink.

One Response to Redering de Mapas de Quake 2 – Texturas

  1. Pingback: Varrojo@Linux » Rendering de Mapas de Quake 2 – Lightmaps

Comments are closed.