"Ningún juego dura tanto hasta que te lo terminas como el que te programas tú mismo,
ninguno te absorbe tanto y ninguno te produce tanta satisfacción cuando lo has acabado"
Programacion de videojuegos
Lunes 05 de Junio del 2023

Menu principal
Colaborar (con PayPal)

Para continuar con el trabajo de esta Web y poder pagar el hosting, viene bien la ayuda que sea. Gracias a todos.

Importe: 

Ultimas descargas
19.Jan

Clase que permite dibujar texto en OpenGL con mucha facilidad.Usa FreeType2.Para ver que hace y c...


20. Billboarding. Imprimir Correo electrónico
Videojuegos - Curso de Programación de juegos
Escrito por Vicengetorix   
Este capítulo habla del billboarding con OpenGL y ya de paso se verán asuntos que serán muy útiles sobre el espacio tridimensional. Además los billboards son muy usados, desde para simular árboles o hierba, a los sistemas de partículas, pasando por otros usos varios. Veremos 4 tipos de billboards.

Lo primero es lo primero, el concepto de billboard. La palabra significa "cartelera".

Un billboard es un gráfico en dos dimensiones situado en un mundo de tres dimensiones y orientado de manera que mire hacia la cámara y siempre se vea de frente.

Para lograr esto tendremos, claro está, que girar el cartel cuando giremos o movamos la camara, o cuando movamos el propio cartel.

¿Para qué se hace esto? Pues para ahorrar tiempo de proceso gráfico usando un sucedaneo de lo que se debiera usar. Es una ñapa para que, renderizando un bosque por ejemplo, no se quede tieso el PC. ¿Que es la programación gráfica si no el arte de engañar al ojo para que las cosas parezcan algo que no son?
También se usa para dibujar las partículas de un sistema de partículas o carteles de texto para marcar algo en objetos 3D.
Ya hacíamos algo parecido al pintar el cartel del logo de UnSitioWeb de los capítulos anteriores (y este). El cartel siempre se pinta de forma que lo veamos de frente. La diferencia con un billboard es que con este último no cambiaremos la perspectiva y lo pintaremos en su posición del espacio 3D.

Una vez claro el concepto veremos tipos de billboard.
La primera clasificación es entre los que se orientan hacia la cámara y los que se orientan hacia el plano perpendicular a la dirección en que mira la cámara, y en el que se encuentra la cámara.
Uffff, me ha salido un poco complicado.
Imaginemos que la cámara está en un muro. Los carteles se orientan hacia el muro, no hacia la cámara. Si la cámara gira, girará el muro entero de forma que la cámara siempre mira de forma perpendicular al muro y los carteles tendrán que reorientarse al girar el muro.
Gráficamente se verán mejor las dos opciones:

Orientados a la cámara:



Orientados al plano de la cámara:



¿Mejor así?.
Realmente lo que los diferencia es que el billboard orientado a cámara gira cuando la cámara se mueve y el orientado al plano de la cámara gira cuando la camara gira.

Otra clasificación es si giran en todos los ejes o si mantienen la verticalidad siempre.
En el caso de un cartel que indica el nombre de un objeto 3D en una aplicación de CAD, nos interesará que siempre de la cara a la cámara sin ningún tipo de inclinación.
En el caso de simular un bosque con billboarding, querremos que los arboles siempre miren a la cámara pero manteniendo la verticalidad, sin girar alrededor del eje X. Aunque lo miremos desde una posición elevada.
Si el cartel gira el todos los ejes para estar siempre perpendicular a la dirección de la vista será de tipo "esférico". Si gira en el eje Y pero no en el eje X (mantiene la verticalidad) se llama de tipo "cilíndrico". Las razones del nombre son bastante intuitivas ¿no?.

Para determinar la dirección de un cartel manejaremos 3 tipos de vectores.
Primero el que indica la dirección donde está la cámara respecto a la posición del cartel, el de dirección.
El que indica la dirección hacia arriba del cartel. Si el cartel está vertical coincidirá con el vector que marca el eje Y (0,1,0), si no está vertical será distinto.
Y por último el vector que indica la dirección de la derecha del cartel y que será perpendicular al plano donde están los otros dos. Todos estos vectores normalizados, claro (longitud 1).



Y en base a la dirección que indica hacia arriba, el billboard sera de tipo esférico:



o cilíndrico:


Observad que solo cambia el vector que marca hacia arriba.

Jugando con estos 3 vectores podremos orientar el cartel. Lo haremos gracias a como OpenGL trata las matrices. Veamos la composición de la matriz de modelo.



Conociendo esto podemos modificar la matriz de modelo para que nuestros objetos 3D se orienten hacia donde queramos.

Tras la teoría pasaremos a programarlos. Pondremos 2 billboards en nuestra escena. En el primero calcularemos nosotros los 3 vectores. Será de tipo orientado a la cámara (esférico o cilíndrico)
El gráfico que usaremos como billboard (para los dos que pondremos) es un PNG transparente:



En el segundo extraeremos los 3 vectores necesarios de la matriz  de proyección. Será de tipo orientado al plano de la cámara (esférico o cilíndrico).
En esta versión del código elimino lo referente al texto grande que poniamos en el capítulo anterior.

Empezamos por las definiciones globales.

Un nuevo identificador de textura para el billboard.

1
2
// Añado tex4 para el billboard.
GLuint tex1, tex2, tex3, tex4; // Para los id's de texturas.

Añado una variable para mover la cámara a lo largo del eje X con las teclas de flecha izquierda y derecha.

1
2
3
4
// Variable para la coordenada X de la camara.
// La modificaremos segun las teclas de flecha
// izquierda o derecha.
GLfloat lat=0;

Añado una función llamada MulVecMat(...) que sirve para multiplicar un vector por una matriz. Esto sirve para aplicar las transformaciones que incluye esa matriz al vector (rotarlo, girarlo y escalarlo). La usaremos luego para averiguar la posición real del billboard.

1
2
3
4
5
6
7
8
9
10
11
12
// Funcion para multiplicar un vector por una matriz, matriz 
// tal cual la racuperamos con glGetFloatv(GL_MODELVIEW_MATRIX,xx)
// por ejemplo. Modifica el vector que le pasamos.
void MulVecMat(GLfloat v[3], GLfloat m[16])
{
GLfloat x[3];
 
x[0]=(m[0]*v[0])+(m[4]*v[1])+(m[8]*v[2])+m[12];
x[1]=(m[1]*v[0])+(m[5]*v[1])+(m[9]*v[2])+m[13];
x[2]=(m[2]*v[0])+(m[6]*v[1])+(m[10]*v[2])+m[14];
v[0]=x[0]; v[1]=x[1]; v[2]=x[2];
}

En la función IniciaGL().

Cambiamos el color negro del fondo por azúl. De esta forma, pintando el cuadrado entero del billboard para apreciar mejor como actua, lo veremos mejor.

1
2
// Cambio el color del fondo.
glClearColor(0,0,0.6f,1.0f);

Cargo la textura del billboard.

1
2
// Cargo textura para el billboard.
CargaTextura(&tex4,"dibus//Imagen1.png");

Ajusto las posiciones de las luces.

1
2
// Ajusto posicion de luz.
GLfloat posicion[] = { 2.0, 2.0, 0.0, 1.0 };

1
2
// Ajusto las posiciones de la luz.
GLfloat posicion1[] = { 9.0, 3.0, 0.0, 1.0 };


En la función ProcesaMensajes(...).

Borro la textura nueva antes de terminar.

1
2
// Borro textura nueva.
glDeleteTextures( 1, &tex4 );

En la función Pinta() está el meollo del asunto.

Primero he cambiado un parametro de la perspectiva.

1
2
3
// El primer parametro es ahora 60 en vez de 50. No muy es importante
// el cambio.
gluPerspective(60.0f, (float)rect.right/(float)rect.bottom, 0.5f, 50.0f);

He cambiado también el orden de los giros de la cámara. Creo que así es mejor.

1
2
3
4
5
6
// He cambiado el orden de los giros porque
// me parecio que queda mejor.
// Giro arriba o abajo
glRotatef(ang_camaraY,1,0,0);
// Giro a izquierda o derecha
glRotatef(ang_camaraX,0,1,0);

En el movimiento de la cámara incluyo la variable "lat" para el movimiento lateral, en el eje X, y elevo el punto de vista (subo la posición de la cámara).

1
2
3
4
5
// Incluyo la variable lat para modificar
// la posicion X de la camara.
// Elevo, tambien, el punto de vista.
// Me alejo o acerco de la escena.
glTranslatef(lat,-10,-3-dist);

Elimino el giro que dábamos a los objetos para que se vieran mejor. Con esto se quedan todos en el suelo (Y=0).

1
2
3
// Giro el objeto 30 grados en el eje x, luego otros
// 30 en el eje y. Es para que quede bonito.
//glRotatef(30,1,0,0);

Modifico la variable "lat" en función de la pulsación de teclas.

1
2
3
4
5
6
7
8
9
10
11
12
13
// Con las teclas de flecha izquierda y 
// flecha derecha, puedo mover la camara
// en el eje X (la variable "lat").
if(Teclas[VK_RIGHT])
{
lat+=0.2;
if(lat>20) lat=20;
}
if(Teclas[VK_LEFT])
{
lat-=0.2;
if(lat<-20) lat=-20;
}

Llegamos al pintado del primer cartel o billboard. Será orientado a la cámara.

Guardo la matriz de modelo ya que voy a cambiarla y quiero usarla luego para colocar el segundo billboard.
Coloco el cartel donde quiero que salga.
Defino variables que voy a usar para guardar vectores y la matriz de modelo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Pintado del primer billboard.
// Este lo haremos calculando los vectores
// que marcan la direccion a la camara.
 
// Guardo la matriz de modelo
// antes de cambiarla.
glPushMatrix();
// Subo 1 en el eje y para pintar
// el billboard encima del ultimo
// cubo que pinte.
glTranslatef(0,1,0);
 
// Para el vector que marca la
// direccion de arriba del billboard.
GLfloat arr[3];
// Para el que marca la derecha.
GLfloat der[3];
// Para el que marca la direccion hacia la
// camara.
GLfloat dir[3];
// Uno auxiliar.
GLfloat aux[3];
// Espacio para guardar la matriz de modelo
GLfloat mv[16];

Extraigo la matriz de modelo y obtengo la posición real que marca la matriz multiplicando esta matriz por un vector  nulo (0,0,0). Con esto aplico al vector 0,0,0 las transformaciones de la matriz.

1
2
3
4
5
6
7
8
9
// Obtengo la matriz de modelo actual.
glGetFloatv(GL_MODELVIEW_MATRIX, mv);
// El vector auxiliar lo pongo en el eje de
// coordenadas, a 0.
aux[0]=0; aux[1]=0; aux[2]=0;
// Multiplico el vector a 0 por la matriz
// de modelo para obener la posicion actual, despues de
// haber rotado y movido. La posicion del billboard.
MulVecMat(aux, mv);

Una vez conocida la posicion del billboard, puedo hallar el vector de dirección del billboard a la cámara, ya que también conozco la posición de la cámara, restando una de otra.
Normalizo el vector.
Con el vector de dirección y el vector generico que marca la dirección hacia arriba (0,1,0) hallo el vector que marca la dirección derecha (con el producto vectorial) .
Normalizo el vector hallado.
Con el vector de dirección y el de la derecha, puedo hallar el vector real que marca arriba (antes usamos el que marca el eje Y -0,1,0-), usando el producto vectorial.
Lo normalizo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Me monto el vector de direccion restando de la 
// posicion de la camara, la del billboard.
dir[0]= -lat-aux[0] ;
dir[1]= 10 -aux[1] ;
dir[2]= (3+dist)-aux[2] ;
// Normalizo el vector.
Normaliza(dir);
// Pongo un vector de direccion arriba generico,
// el que marca la direccion arriba del espacio.
aux[0]=0; aux[1]=1; aux[2]=0;
// Hago el producto vectorial de el vector hacia arriba (aux)
// y el vercor de direccion a la camara(dir), con lo que
// obtengo el vector que marca a la derecha del billboard.
ProdVectorial(aux,dir,der);
// Lo normalizo.
Normaliza(der);
// Hago el producto vectorial de el vector de direccion a la
// camara(dir) con el que marca la derecha(der) y obtengo
// el vector que marca la direccion arriba real del billboard.
ProdVectorial(dir,der,arr);
// Lo normalizo.
Normaliza(arr);

Incluyo los vectores obtenidos en la matriz de modelo. Si quiero que el billboard sea cilíndrico, incluyo el vector que marca el eje Y (0,1,0) en vez del vector de dirección de arriba obtenido antes. Si quiero que sea esférico incluyo el hallado antes.
Las posiciones donde se cargan los vectores se pueden ver en la figura de la matriz que se vio en la explicación. al principio del capítulo.
Luego cargo la matriz obtenida como matriz de modelo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Guardo el vector que marca la derecha que hemos obtenido
// en la posicion correspondiente de la matriz de modelo.
mv[0]=der[0];
mv[1]=der[1];
mv[2]=der[2];
// Hago lo mismo con el vector que marca la direccion arriba.
// Haciendo esto, el billboard es de tipo esferico.
// Si en vez de el vector obtenido antes pongo el vector
// hacia arriba generico, el que marca el eje Y (0,1,0), el
// billboard sera de tipo cilindrico.
mv[4]= 0;//arr[0];
mv[5]= 1;//arr[1];
mv[6]= 0;//arr[2];
// Por ultimo guardo el vector de direccion a la camara
// en la posicion correspondiente de la matriz de modelo.
mv[8]=dir[0];
mv[9]=dir[1];
mv[10]=dir[2];
// Y con la matriz modificada en la parte de giro, la cargo en
// en OpenGL.
glLoadMatrixf(mv);

Finalmente pinto el cartel usando coordenadas X e Y, con la textura que quiera, usando un PNG con transparencia. Gracias a la matriz que hemos cargado, el cartel se colocará en su sitio pero siempre de cara a la cámara.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Ahora solo queda dibujar el billboard con la textura 
// que hayamos cargado
// Habilito texturas
glEnable(GL_TEXTURE_2D);
// Hago bind de la textura que voy a usar
glBindTexture(GL_TEXTURE_2D, tex4);
// Indico como aplicar la textura
glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
// Habilito blending para que las partes transparentes de
// de la textura no se pinten, y la funcion adecuada.
// Si comentamos esta linea, veremos el cuadrado entero para
// poder apreciar como se comporta el billboard.
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// Pinto el cuadrado, siempre usando solo coordenadas X e Y.
// La coordenada Z sera siempre 0.
// Aqui tendremos que tener cuidado de ajustar las coordenadas al
// tamaño que queremos del billboard y donde esta el centro sobre
// el que rotara al mover la camara.
glBegin(GL_QUADS);
glTexCoord2f(0.0,1.0); glVertex3f( -1, 0, 0);
glTexCoord2f(1.0,1.0); glVertex3f( 1, 0, 0);
glTexCoord2f(1.0,0.0); glVertex3f( 1, 2, 0);
glTexCoord2f(0.0,0.0); glVertex3f( -1, 2, 0);
glEnd();
// Dejamos las cosas como queremos.
glDisable(GL_BLEND);
glDisable(GL_TEXTURE_2D);
// Restablezco la matriz de modelo anterior a modificarla
// para el billboard.
glPopMatrix();


Pintaremos ahora el segundo billboard. Este será orientado al plano de la cámara

Coloco la posición donde quiero que se pinte.
Defino un buffer para guardar la matriz de proyección (el de la matriz de modelo lo creamos antes).
Obtengo la matriz de modelo y la de proyección.

1
2
3
4
5
6
7
8
9
10
11
12
// Pintamos el segundo billboard.
// Este lo haremos extrayendo datos de
// la matriz de preyeccion pasandoselos a
// la matriz de modelo.
 
// Ponemos el billboard encima de uno de los cubos.
glTranslatef(-2,1, 4.0f);
//Espacio para guardar la matriz de proyeccion.
GLfloat py[16];
// Obtengo las matrices de modelo y proyeccion.
glGetFloatv(GL_MODELVIEW_MATRIX, mv);
glGetFloatv(GL_PROJECTION_MATRIX, py);

Defino ahora dos variables que harán de factor para corregir el cambio de tamaño provocado por pasar los datos de la matriz de proyección a la de modelo sin normalizar.
Otra opción sería normalizar los datos antes de cargarlos en la matriz de modelo. Cualquiera de los dos métodos es válido pero en uno debes hallar 3 raices cuadradas y en otro no.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Variables para los factores de correccion del
// tamaño. Al usar los datos de la matriz de
// proyeccion, la escala se modifica en base a la
// perspectiva que hemos puesto.
// Luego multiplicaremos estos factores por las coordenadas
// al pintar el billboard. Si no el tamaño saldra mal al
// estar no normalizados los vectores que extraemos de la
// matriz de proyeccion.
GLfloat z1,z2;
// Dada esta perspectiva:
// gluPerspective(60.0f, (float)rect.right/(float)rect.bottom, 0.5f, 50.0f);
// El primer factor, si el primer parametro de la perspectiva es
// 60. sera 0.60 (un poco menos queda mejor).
// El segundo factor sera el segundo parametro de la perspectiva
// por el primer factor.
z2=1;//0.58f; // Ponemos 1 en caso de ser cilindrico.
z1=((GLfloat)rect.right/(GLfloat)rect.bottom)*0.58f;

Copio los datos que necesito de la matriz de proyección a la de modelo transponiendo la submatriz de rotación de la matriz de proyección. (ver figura de matriz).
Si es cilíndrico en vez de el vector arriba extraido de la matriz de proyección, pongo 0,1,0.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Cargo en la matriz de modelo los datos desde la matriz de
// proyeccion (la parte del giro -y el escalado-) transponiendolos.
// Las posiciones 4,5 y 6 de la matriz de modelo (igual que el caso
// enterior) son para el vector de direccion hacia arriba.
// Si uso el vector que indica el eje Y (0,1,0) sera de tipo cilindrico,
// si uso los datos tomados de la matriz de proyeccion sera de tipo
// esferico.
// Vector derecha
mv[0]=py[0];
mv[1]=py[4];
mv[2]=py[8];
// Vector arriba
mv[4]=0;//py[1]; // Cilindrico
mv[5]=1;//py[5]; // Cilindrico
mv[6]=0;//py[9]; // Cilindrico
// Vector direccion a la camara (al plano)
mv[8]=py[2];
mv[9]=py[6];
mv[10]=py[10];

Si nos decidieramos a normalizar los vectores en vez de usar las variables "z1" y "z2", para mantener correcto el tamaño del cartel, lo hariamos aquí.

1
2
3
4
5
6
// En lugar de usar los factores z1 y z2 para recuperar el 
// tamaño normal del cartel, se puede normalizar los
// vectores que extraemos de la matriz de proyeccion.
//Normaliza(mv);
//Normaliza(mv+4);
//Normaliza(mv+8);

Cargamos la matriz de modelo hallada y pintamos como hicimos con el otro billboard.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Cargo la matriz de modelo con los nuevos datos de giro.
glLoadMatrixf(mv);
// Y pinto el cuadrado del billboard (podria no ser un cuadrado)
// Habilito textura
glEnable(GL_TEXTURE_2D);
// Hago bind de la que quiero usar
glBindTexture(GL_TEXTURE_2D, tex4);
// Indico como se aplicara
glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
// Habilito blending si quiero.
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// Y pinto el billboard.
// Aqui aplico los factores de correccion del tamaño que
// habiamos calculado antes sobre las coordenadas ya que
// si no, se vera mas grande de lo que lo estamos pintando,
// a no ser que hayamos normalizado los vectores, en cuyo
// caso no haria falta.
glBegin(GL_QUADS);
glTexCoord2f(0.0,1.0); glVertex3f( -1, 0, 0);
glTexCoord2f(1.0,1.0); glVertex3f( 1*z1, 0, 0);
glTexCoord2f(1.0,0.0); glVertex3f( 1*z1, 2*z2, 0);
glTexCoord2f(0.0,0.0); glVertex3f( -1, 2*z2, 0);
glEnd();
 
// Dejo las cosas como debo.
glDisable(GL_BLEND);
glDisable(GL_TEXTURE_2D);

Una vez pintados los dos billboards solo quedan flecos.

Añado una entrada en la leyenda.

1
2
3
// Nuevas teclas en la leyenda
Escribe(rect.right-300,rect.bottom-20,
"Izquierda Derecha - Mover cámara en el eje X");

Y ya hemos terminado con el código.

En pantalla se vería más o menos así.



Y el código completo sería este: usw20.cpp .

Nota: Las manipulaciones matematicas de las matrices y vectores que hemos visto en este capítulo, nos servirán para otras cosas como por ejemplo orientar un objeto, como un avión, en base a un vector de dirección.




¡Sólo los usuarios registrados pueden escribir comentarios!
+/- Comentarios
Buscar
Carlos  - Problemas   |189.130.27.xxx |02-07-2013 23:21:19
Oye esta muy chido pero no me funciona
no se dedonde obtibiste las librerias
que mencionas en el codigo final, soy nuevo en esto y me gustaria aprender
Vicengetorix   |89.130.162.xxx |14-07-2013 17:19:17
En los capitulos 7 y 19 indica las librerias de graficos y de texto.
Conviene
leer el curso en orden para no perderse estas cosas.
En la zona de descargas del
sitio hay una cuantas tambien.

3.26 Copyright (C) 2008 Compojoom.com / Copyright (C) 2007 Alain Georgette / Copyright (C) 2006 Frantisek Hliva. All rights reserved."

 


Banner
Spanish Chinese (Simplified) English French German Japanese Portuguese Russian