- Resumen
- Introducción
- Cargando texturas en
memoria - Pasando las texturas a
opengl - Parámetros de las
texturas - Renderizando con
texturas - Colores, luces y
texturas - Algoritmos
- Conclusiones
- Bibliografía
En este trabajo
veremos un aspecto básico si queremos que una escena
contenga un mínimo de realismo: La
texturización. Por texturización entendemos en el
proceso de
asignar una imagen (bitmap) a
un polígono, de manera que en lugar de ver este de un
color plano, o un
gradiente de colores, veremos
la imagen proyectada en él.
Hasta ahora las primitivas se han dibujado en OpenGL con un
solo color o
interpolando varios colores entre los vértices de
una primitiva. OpenGL dispone de funciones
específicas para el mapeado de texturas (pegar imágenes
en la superficie de las primitivas dibujadas), añadiendo
realismo a la escena. En este trabajo se explica el proceso de
mapeado de texturas a través de un sencillo ejemplo, para
posteriormente emplear las texturas en la
aplicación.
Las texturas en OpenGL pueden ser 1D, 2D o 3D. Las 1D
tienen anchura pero no altura; las 2D son imágenes que
tienen una anchura y una altura de más de 1 píxel,
y son generalmente cargadas a partir de un archivo .bmp
(aunque puede ser en principio cualquier formato). En este
trabajo no se hablará de las texturas 3D (volumen). Un
aspecto muy importante a tener en cuenta es que en OpenGL las
dimensiones de las imágenes deben ser potencia de
2.
Este trabajo se limitara a estudiar la
texturización 2D, y proponer implementaciones para un
mejor cargado y rendereado de texturas.
DESARROLLO
1.1. Cargando
texturas en memoria
El proceso de cargar la textura en memoria, no es
propio de OpenGL, lo tendremos que hacer nosotros mismos. No
obstante, hay que tener en cuenta unas ciertas limitaciones que
la librería nos impone. Primeramente, las dimensiones de
todas las texturas que carguemos tienen que ser potencias de 2,
como por ejemplo 64×64, 128×64, etc.
También hemos de tener en cuenta que si estamos
dibujando en RGB, sin color indexado, o bien cargamos texturas en
formato RGB o las convertimos a RGB. Es decir, si cargamos una
imagen GIF, que tiene color indexado, correrá de nuestra
cuenta pasarla a RGB. Sea cuál sea el método que
escojamos, al final tendremos un puntero a un segmento de memoria
que contiene la imagen:
unsigned char *textura;
Es importante también guardar las propiedades de
la textura, en concreto sus
dimensiones de ancho y alto, así como su profundidad en
bits. Si estamos trabajando en RGB, la profundidad será
24bits.
1.2. Pasando las
texturas a OpenGL
Ahora ya tenemos la textura en la memoria RAM. No
obstante, OpenGL no puede trabajar directamente con esta memoria,
ha de usar su propia memoria para guardar las texturas. El se
encargará de guardarlas en su espacio de memoria RAM o,
directamente, pasarlas a la tarjeta aceleradora. Una vez le
pasemos la textura a OpenGL, éste nos devolverá un
identificador que tendremos de guardar. Cada textura
tendrá un identificador propio, que tendremos que usar
después cuando dibujemos. Veamos el proceso de
obtención de este identificador. Creemos una variable para
almacenarlo:
GLuint idTextura;
A continuación llamaremos a la función
glGenTextures(…), a la cual le pasamos el numero de texturas
que queremos generar, y un array de identificadores donde los
queremos almacenar. En este caso, solo queremos una textura, y
por lo tanto no hace falta pasarle un array, sino un puntero a
una variable de tipo GLuint.
glGenTextures(1, &idTextura);
Con esto OpenGL mirará cuantas texturas tiene ya
almacenadas, y en función de esto pondrá en
idTextura el valor del
identificador. Seguidamente, usaremos la función
glBindTexture(…) para asignar el valor de idTextura, a una
textura de destino. Es como si activáramos la textura
asignada a idTextura, y todas las propiedades que modifiquemos a
partir de entonces, serán modificaciones de esa textura
solamente, y no de las demás, del mismo modo que activamos
una luz y definimos
sus propiedades.
glBindTexture(GL_TEXTURE_2D,
idTextura);
Ahora falta el paso más importante, que es
pasarle la textura a OpenGL. Para ello haremos uso de la
función glTexImage2D(…)
GlTexImage2D(GL_TEXTURE_2D, 0, 3, anchoTextura,
altoTextura, 0,
GL_RGB, GL_UNSIGNED_BYTE, textura);
Pero veamos todos los parámetros, viendo los
parámetros de esta función uno
por uno:
void glTexImage2D(
GLenum tipoTextura,
GLint nivelMipMap,
GLint formatoInterno,
GLsizei ancho,
GLsizei alto,
GLint borde,
GLenum formato,
GLenum tipo,
const GLvoid *pixels
);
· tipoTextura: El tipo de textura que
estamos tratando. Tiene que ser
GL_TEXTURE_2D.
· NivelMipMap: El nivel de MipMapping que
deseemos. De momento
pondremos ‘0’, más adelante veremos
que significa.
· FormatoInterno: El numero de componentes
en la textura. Es decir, si
estamos trabajando en formato RGB, el numero de
componentes será 3.
· Ancho, alto: El ancho y alto de la
textura. Han de ser potencias de 2.
· Borde: La anchura del borde. Puede ser
0.
· Formato: El formato en que esta
almacenada la textura en memoria. Nosotros
usaremos GL_RGB.
· Tipo: El tipo de variables en
los que tenemos almacenada la textura. Si la
hemos almacenado en un unsigned char, usaremos
GL_UNSIGNED_BYTE.
· Pixels: El puntero a la región de
memoria donde esté almacenada la imagen.
Una observación. Teníamos en memoria RAM
una textura cargada desde un archivo. Esta textura, se la hemos
pasado a OpenGL, que se la ha guardado en su propia memoria. Por
tanto, ahora tenemos dos copias en memoria de la misma textura,
solo que una ya no es necesaria, la nuestra. Por tanto, es
recomendable eliminar nuestras texturas de memoria una vez se las
hemos pasado a OpenGL.
free(textura);
1.3.
Parámetros de las texturas
A cada textura le podemos asignar unas ciertas
características. El primero de ellos es el filtro de
visualización. Una textura es un bitmap, formado por un
conjunto de píxels, dispuestos de manera regular. Si la
textura es de 64 x 64 píxel, i la mostramos completa en
una ventana de resolución 1024×768, OpenGL escalará
estos pixels, de manera que cada píxel de la textura (de
ahora en adelante téxel) ocupará 16×12
píxeles en la pantalla.
1024 pixels ancho / 64 texels ancho = 16;
768 pixels alto / 64 texels alto = 12;
Eso quiere decir que lo que veremos serán
"cuadrados" de 16×12, representando cada uno un texel de la
textura. Visualmente queda muy poco realista ver una textura
‘pixelizada’, de manera que le aplicamos filtros para
evitarlo. El más común es el ‘filtro
lineal’, que se basa en hacer una interpolación en
cada píxel en pantalla que dibuja. A pesar de que pueda
parecer costoso a nivel computacional, esto se hace por hardware y no afecta en
absoluto al rendimiento.
Veamos como lo podemos implementar:
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,
GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
GL_LINEAR);
Con esto estamos parametrizando dos filtros. Uno para
cuando la textura se representa más grande de lo que es en
realidad (el ejemplo que hemos comentado) y otro para cuando la
textura es mas pequeña: GL_TEXTURE_MAG_FILTER y
GL_TEXTURE_MIN_FILTER, respectivamente. En los dos le decimos que
haga un filtro lineal. Tambien podriamos decirle que no aplicara
ningún filtro (GL_NEAREST).
Generalmente al texturizar una escena, el modelador
aplica una textura pequeña que se repite a lo largo de una
superficie, de manera que, sacrificando realismo, usamos
menos
memoria. Podemos indicar a OpenGL que prepare la textura
para ser dibujada a manera
de ‘tile’ o para ser dibujada solo una vez.
Por ejemplo, un cartel en una pared. En la pared habría
una textura pequeña de ladrillo, que se repetiría n
veces a lo largo de toda la superficie de la pared. En cambio, el
cartel solo se dibuja una sola vez. La textura de ladrillo
tendríamos que preparar para que se repitiese, y la del
cartel no. Realmente lo que hace OpenGL realmente es, al dibujar
la textura y aplicar el filtro lineal, es, en los bordes,
interpolar con los texels del borde opuesto, para dar un aspecto
mas realista.
Si queremos activar esta opción,
haríamos:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
GL_REPEAT);
Si no nos interesa este efecto:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
GL_CLAMP);
Por GL_TEXTURE_WRAP_S y GL_TEXTURE_WRAP_T nos referimos
al
filtro para las filas y las columnas,
respectivamente.
1.4. Renderizando
con texturas
Ahora que ya tenemos las texturas cargadas y ajustadas a
nuestro gusto, veamos ahora cómo podemos dibujar polígonos con texturas aplicadas.
Supongamos que queremos dibujar un simple cuadrado, con la
textura que hemos cargado anteriormente.
Si lo dibujamos sin textura seria:
glBegin (GL_QUADS);
glVertex3i (-100, -100, 5);
glVertex3i (-100, 100, 5);
glVertex3i (100, 100, 5);
glVertex3i (100, -100, 5);
glEnd ();
Veamos ahora como lo hariamos aplicando una
textura:
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D,
idTextura);
glBegin (GL_QUADS);
glTexCoord2f(0.0f, 0.0f);
glVertex3i (-100, -100, 5);
glTexCoord2f(1.0f, 0.0f);
glVertex3i (-100, 100, 5);
glTexCoord2f(1.0f, 1.0f);
glVertex3i (100, 100, 5);
glTexCoord2f(0.0f, 1.0f);
glVertex3i (100, -100, 5);
glEnd ();
glDisable(GL_TEXTURE_2D);
Analicemos el código
paso por paso:
glEnable(GL_TEXTURE_2D);
La función glEnable de OpenGL nos permite activar
y desactivar propiedades que se aplican a la hora de renderizar.
En este caso, le estamos diciendo que active la
texturización
glBindTexture(GL_TEXTURE_2D,
idTextura);
Como ya habiamos visto antes, la función
glBindTexture se encarga de activar la textura que deseemos,
referenciada por el identificador que habiamos guardado
previamente en idTextura.
glBegin (GL_QUADS);
glTexCoord2f(0.0f, 0.0f);
glVertex3i (-100, -100, 5);
Aquí vemos algo que no estaba antes, nos
referimos a glTexCoord2f(). Esta funcion nos introduce un
concepto
nuevo: las coordenadas de textura, y nos referiremos a ellas
generalmente como ‘s’ y ‘t’, siendo
‘s’ el eje horizontal y ‘t’ el vertical.
Se usan para referirnos a una posición de la textura.
Generalmente se mueven en el intervalo [0,1].
La coordenada (0, 0) se refiere a la esquina inferior
izquierda, y la (1, 1) a la superior derecha. Por lo tanto, si
nos fijamos en el código, a cada vértice le
asignamos una coordenada de textura, siempre en orden
contrario a las agujas del reloj, sino la textura se
vería al revés.
Hemos comentado que las coordenadas de textura se mueven
en el intérvalo [0,1], pero OpenGL permite otros valores.
Podríamos considerar que la textura se repite
infinitamente en todas las direcciones, de manera que la
coordenada (1, 0) seria el mismo texel que (2, 0), (3, 0), etc.
No obstante, si dibujamos un cuadrado y le asignamos a un extremo
(2, 2) en lugar de (1, 1), dibujara la textura repetida 2 veces
en cada dirección, en total, 4 veces. Podemos jugar
y asignar valores de (-1, -1) a (1, 1),etc.
Una vez hayamos dibujado la geometría que queramos, es importante
desactivar la texturización, si no lo hacemos, si mas
adelante queremos dibujar algún objeto sin texturizar,
OpenGL intentará hacerlo, aunque no le pasemos coordenadas
de textura, produciendo efectos extraños. Así
pues:
glDisable(GL_TEXTURE_2D);
1.5. Colores,
luces y texturas
Hemos estado
hablando de texturizar una superficie, pero, que pasa con el
color? Realmente podemos seguir usándolos, es mas, OpenGL
texturizará y coloreará a la vez. Podemos activar
el color rojo, y todas las texturas que se dibujen a partir de
entonces, saldrán tintadas de ese mismo color.
Del mismo modo con las luces. No hay ningún
problema en combinar colores, luces y texturas a la vez. Si no
queremos iluminación alguna, basta con no activar
las iluminación, y si no queremos que las texturas salgan
tintadas de algún color, basta con definir el color activo
como el blanco.
#include <GL/glut.h>
#include "bitmap.h"
BITMAPINFO *TexInfo; /*
Texture bitmap information */
GLubyte *TexBits; /*
Texture bitmap pixel bits */
void display(void) {
glClearColor (1.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT |
GL_DEPTH_BUFFER_BIT);
TexBits = LoadDIBitmap("escudo.bmp",
&TexInfo);
glTexImage2D(GL_TEXTURE_2D, 0, 3, TexInfo->
bmiHeader.biWidth, TexInfo-> bmiHeader.biHeight, 0,
GL_BGR_EXT,GL_UNSIGNED_BYTE, TexBits);
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);
glColor3f(1.0, 1.0, 1.0);
//se activa el mapeado
detexturas
glEnable(GL_TEXTURE_2D);
glBegin(GL_POLYGON);
glTexCoord2f(0.0, 0.0);
glVertex2f(-1.0, -1.0);
glTexCoord2f(1.0, 0.0);
glVertex2f(1.0, -1.0);
glTexCoord2f(1.0, 1.0);
glVertex2f(1.0, 1.0);
glTexCoord2f(0.0, 1.0);
glVertex2f(-1.0, 1.0);
glEnd();
glDisable(GL_TEXTURE_2D);
glutSwapBuffers() ;
}
void reshape(int width, int height) {
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60.0, (GLfloat)height / (GLfloat)width,
1.0, 128.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(0.0, 1.0, 3.0, 0.0, 0.0, 0.0, 0.0, 1.0,
0.0);
}
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH |
GLUT_DOUBLE);
glutInitWindowSize(400, 400);
glutInitWindowPosition(100, 100);
glutCreateWindow("tecnunLogo");
glutReshapeFunc(reshape);
glutDisplayFunc(display);
glutMainLoop();
return 0;
}
Cargar textura
bool CTextura::CargarBMP(char
*szNombreFichero)
{
FILE *hFichero;
AUX_RGBImageRec *Imagen;
bool bResultado=false;
if (szNombreFichero)
// Comprobamos que el nombre de fichero
sea correcto
{
hFichero=fopen(szNombreFichero,"r");
// Comprobamos si el fichero existe (Si
podemos abrirlo)
if (hFichero) //
¿Existe el fichero?
{
fclose(hFichero); //
Cerramos el handle
Crear(szNombreFichero);
Imagen=auxDIBImageLoad(szNombreFichero);
// Cargamos el BMP
m_nAncho=Imagen->sizeX;
m_nAlto=Imagen->sizeY;
glTexImage2D(GL_TEXTURE_2D, 0, 3, m_nAncho, m_nAlto,
0, GL_RGB, GL_UNSIGNED_BYTE, Imagen->data);
bResultado=true;
}
}
delete Imagen->data;
delete Imagen;
return bResultado;
}
El mapeado de texturas puede resumirse en cuatro
pasos:
1. Se carga una imagen y se define como
textura.
2. Se indica cómo va a aplicarse la textura a
cada píxel de la superficie.
3. Se activa el mapeado de texturas.
4. Se dibuja la escena, indicando la correspondencia
entre las coordenadas
de la textura y los vértices de la
superficie.
Normalmente en una escena suelen haber muchas
texturas diferentes. Si dibujamos toda la geometría
desordenadamente, es posible que cada pocos triángulos tengamos que cambiar de textura.
El proceso de cambio de textura tiene un cierto coste, por eso es
recomendable organizar nuestra geometría, haciendo
grupos de
superficies que tengan la misma textura, de manera que hagamos
los mínimos cambios posibles. Asimismo, podemos hacer lo
mismo con los materiales en
el caso que hagamos uso de ellos.
1. [IPG04]
Introducción a la Programación Gráfica con
OpenGL
(Oscar García y Alex Guevara) La Salle
2004
2.[Map01]
Tutorial Mapeado de textura
(Fernando Jose Serrano) 25/04/01
3. [Ham99]
Hammersley, T., "Texture Shading", GameDev.Net Website
at http://www.gamedev.net,
October, 1999.
Autor:
Yuri A. Rodrgíuez Cruz