"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
Inicio Curso de programación de juegos 18. Vertex Buffer Objects en OpenGL
Domingo 22 de Octubre del 2017

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...


18. Vertex Buffer Objects en OpenGL Imprimir Correo electrónico
Videojuegos - Curso de Programación de juegos
Escrito por Vicengetorix   
Seguimos avanzando con OpenGL y nos adentramos en la version 1.5 con una de las caracteristicas mas necesarias para el rendimiento cuando programamos graficos, los Vertex Buffer Objects o VBOs que nos permiten hacer mejor uso de la aceleracion grafica de nuestra tarjeta. En este capitulo no habra cambios visibles en el resultado de nuestra pequeña escena pero, internamente el cambio sera muy grande; tanto es asi que a partir de la version 3.0 de OpenGL lo anterior sera declarado "deprecated" (obsoleto) y en la version 3.1 de OpenGL sera la unica forma de pintar.

Los Vertex Buffer Objects se incorporaron al nucleo de OpenGL a partir de la version 1.5. Con la version 1.4, y si lo soporta la tarjeta grafica , se pueden usar solo a traves de la extension ARB_vertex_buffer_object .

Veamos el concepto. Si lo entendemos no nos costara su uso.
La idea es parecida a los Vertex Arrays. Se agrupan los datos de los vertices (coordenadas, normales, coortenadas de textura, color de los vertices, o cualquier otro atributo que pueda aplicarse a un vertice) igual que se hace con Vertex Array pero se pasan esos datos a un objeto de OpenGL llamado Vertex Buffer Object que OpenGL gestionara de forma que si es posible guardara en memoria de video (la de la tarjeta grafica) o memoria AGP o lo que OpenGL crea conveniente. Luego la forma de renderizar (pintar en pantalla) el modelo cuyos vertices hemos guardado en un VBO sera igual que con Vertex Array.

El resultado en pantalla no cambia frente a Vertex Arrays pero el rendimiento no tiene nada que ver, con VBOs aprovechamos la potencia de la tarjeta grafica. Vaya, que nuestro programa ira mas rapido.

La forma de trabajar sera:
  • Crear primero el VBO.
  • Luego se hace bind del VBO (se indica a OpenGL que estamos trabajando con el, lo marcamos como el activo).
  • Luego se le dan las caracteristicas, entre ellas el puntero donde tienes los datos de los vertices (se le puede no pasar al definirlo y cargar los datos despues). Cuando le pasas el puntero copiara los datos desde donde los tienes tu al VBO.
  • Luego, se debe indicar a OpenGL que cuando pinte vertices coja los datos del VBO con las funciones que ya vimos de "glVertexPointer", "glNormalPointer", ... pero pasando como ultimo parametro 0 o NULL en vez de el puntero a los datos.
  • Luego se pinta igual que haciamos con Vertex Arrays.

Para aclarar mas el tema, cuando defines las caracteristicas del VBO (ya veremos cuales) y le pasas un puntero a una matriz con los datos, estos datos se copian al VBO desde la memoria donde los tienes, de forma que los datos quedaran duplicados. Si no lo haces entonces (le pasas 0 o NULL en vez de el puntero) los datos se copiaran mas tarde, con otra funcion que esta para eso. En todo caso los datos quedan copiados dentro del VBO. Si no pretendes usar de nuevo el array original lo puedes borrar y pintar usando el VBO.

Veamos las nuevas funciones.

void glGenBuffers(sizei n, uint *buffers)
Esta funcion crea uno o mas VBO.
El primer parametro es el numero de VBOs a crear.
El segundo, la direccion de los GLuint's donde dejar los identificativos de los VBO.

void BindBuffer(enum target, uint buffer)
Esta funcion es la que hace bind de un VBO. Hacer bind es convertir ese VBO como el activo, sobre el que que porteriores funciones van a actuar.
El primer parametro indica el tipo de datos del VBO; acepta dos opciones, GL_ARRAY_BUFFER para coordenadas, normales, coordenadas de textura, colores, ... . GL_ELEMENT_ARRAY_BUFFER para indices. Este parametro ayuda a OpenGL a decidir en que memoria es mejor guardarlo.
El segundo parametro es el identificativo del VBO.

void BufferData(enum target, sizeiptr size, const void *data, enum usage)
Esta funcion define como sera el VBO y puede cargar los datos en el.
El primer parametro es el tipo de VBO, como en la funcion anterior.
El segundo es el tamaño del VBO en bytes.
El tercero es el puntero a los datos a copiar, sean coordenadas o indices u otra cosa. Si este parametro fuera NULL o 0, no se cargarian los datos y se tendrian que cargar mas tarde con BufferSubData. En caso de ser NULL o 0, y tener datos, descartaria esos datos.
El cuarto indica a OpenGL la frecuencia con que se va a acceder al contenido del VBO en el programa. Esto determina tambien la memoria donde sera almacenado. Las opciones son

  • GL_STATIC_DRAW
  • GL_STATIC_READ
  • GL_STATIC_COPY
  • GL_DYNAMIC_DRAW
  • GL_DYNAMIC_READ
  • GL_DYNAMIC_COPY
  • GL_STREAM_DRAW
  • GL_STREAM_READ
  • GL_STREAM_COPY

STATIC es que no se va a acceder casi, DYNAMIC que se accedera de vez en cuando y STREAM que se accedera cada frame.
DRAW es que se escribira en al VBO, READ que se leera del VBO y COPY que se leera y escribira.

void DeleteBuffers(sizei n, const uint *buffers)
Borra VBOs.
El primer parametro el numero de buffers (VBOs).
El segundo parametro la direccion donde estan los identificadores de los VBOs.

void BufferSubData(enum target, intptr offset, sizeiptr size, const void *data)
Carga datos en un VBO ya creado antes o en parte de el.
El primer parametro es el tipo de VBO, como en la funcion BindBuffer.
El segundo parametro es el offset, la posicion dentro del buffer donde empezara a copiar.
El tercero el tamaño en bytes de los datos a copiar.
El cuarto el puntero donde se encuentran los datos a copiar.

void *MapBuffer(enum target, enum access)
Retorna un puntero al VBO. Si la GPU (la tarjeta grafica) esta usandolo, el programa espera a que termine con el. Cuidado, puede ralentizar. Para evitar esto se puede llamar glBufferData con puntero NULL, lo que eliminaria los datos de VBO pero retronaria sin espera.
El primer parametro es el tipo de VBO, como en la funcion BindBuffer.
El segundo es el tipo de uso que se va a dar al puntero de retorno. Puede ser:

  • GL_READ_ONLY
  • GL_WRITE_ONLY
  • GL_READ_WRITE

El significado es obvio ¿no?.

boolean UnmapBuffer(enum target)
Tras modificar los datos de un VBO con MapBuffer se debe decir a OpenGL que se ha terminado.
Retorna true si todo ha ido bien y false si no.
El parametro es el tipo de VBO, como en la funcion BindBuffer.

En resumen, para usar un VBO seguiremos estos pasos.
  • Tener los datos de los vertices en memoria (cargados de un fichero o como sea).
  • Crear los VBOs necesarios.
  • Cargar los datos de vertices desde memoria, donde los tenemos, a los VBOs.
  • Indicar con las funciones "glVertexPointer", "glNormalPointer", ... que vamos a usar VBOs pasandolas NULL o 0 en vez de el puntero a los datos.
  • Por ultimo pintar normalmente como hariamos con Vertex Arrays.

Importante es hacer bind del VBO correspondiente antes de hacer nada con el.

Un tema interesante es como organizaremos los datos. Este tema no se vio cuando nos ocupamos de los Vertex Arrays, nos limitamos entonces a hacerlo de una manera, pero se puede hacer de varias y con VBOs tambien. Me explico.
Nosotros usamos espacios de memoria distintos para cada dato, coordenadas, normales y coordenadas de textura se guardaban por separado. Tambien se ordenaban para que estuvieran en el orden correcto y no tener que usar indices.
Se podria haber guardado los datos de cada vertice juntos, habiendo definido una estructura para cada vertice como esta:

1
2
3
4
5
6
7
8
9
10
11
struct vertice
{
float x, y, z; //Vertice
float nx, ny, nz; //Normal
float s0, t0; //C. Textura 0
float s1, t1; //C. Textura 1
float s2, t2; //C. Textura 2
float relleno[4]; //ATI indica que es mejor que estas
//estructuras sean multiplos de 32
//por tema de rendimiento del VBO.
};

y habiendo creado un buffer con esta estructura en memoria, se pasan los datos a un unico VBO y luego, a las funciones "glVertexPointer", "glNormalPointer", ... se les asignaria el mismo VBO a todas pero cambiando el parametro offset (el tercero) a 64 (el tamaño de la estructura de cada vertice) y el parametro (el cuarto) de puntero, en vez de puntero, otro offset de donde empieza cada tipo de dato (en nuestra estructura, 0 para el vertice, 12 para normales, ... -asumiendo 4 bytes por float-).
Luego se pintaria con glDrawArrays.
Tambien se podria usar un VBO de indices (IBO) o guardar los datos en el mismo VBO pero con otra organizacion, hay muchas opciones.
Para completar las posibilidades de los Vertex Arrays sugiero la lectura del RedBook de OpenGL (en ingles).

Nosotros continuaremos manteniendo buffers separados para cada tipo de datos, como hicimos en capitulos anteriores, y los mismos VBOs.

Tras la explicacion teorica, nada mejor que ponerlo en practica modificando el codigo de nuestra escena. Partiremos del codigo del capitulo anterior, el 17.

Lo primero sera limpiar el codigo quitando toda referencia a la libreria glaux. A partir de aqui no tiene sentido usarla, ya que estamos mejorando el rendimiento y glaux, por su edad, no hace uso de caracteristicas modernas de OpenGL. Ademas, sabiendo cargar modelos no hace falta para poner una esfera en pantalla.
En las definiciones globales borraremos (o comentaremos) estas lineas:

1
2
3
4
5
6
7
8
// Comentamos todas las referencias a glaux
// o las borramos. no usaremos esta libreria mas.
//#pragma comment( lib, "glaux.lib" )
 
...
 
// Otra referencia glaux fuera.
//#include <GL/glaux.h>

En la funcion Pinta() quitaremos este bloque:

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
// Elimino la parte de pintar la bola con glaux.
 
// y dibujo una esfera solida de 1 de radio
// usando la libreria glaux, que para eso la
// hemos cargado.
// Realizamos con la esfera lo mismo
// que con los cubos pero con otro
// identificador (el 5).
//if(Seleccion)
// glReadPixels(Raton.x,rect.bottom-Raton.y, 1,1,
// GL_DEPTH_COMPONENT,GL_FLOAT,&z1);
//auxSolidSphere(1.0);
//if(Seleccion)
// glReadPixels(Raton.x,rect.bottom-Raton.y, 1,1,
// GL_DEPTH_COMPONENT,GL_FLOAT,&z2);
 
//if(ObjetoSel==5)
//{ glDisable(GL_LIGHTING);
// glPolygonMode(GL_FRONT, GL_LINE);
// auxSolidSphere(1);
// glPolygonMode(GL_FRONT, GL_FILL);
// glEnable(GL_LIGHTING);
//}
//if(z2<z1 && Seleccion) ObjetoSel=5;
 

Una vez limpio nuestro codigo, comenzamos a implementar los VBO para el modelo que cargamos de disco.

Antes de empezar a usar VBO's comprobaremos si nuestra tarjeta soporta el asunto.
Se puede hacer de dos formas, comprobando la version 1.5 de OpenGL o comprobando si soporta la extension (en caso de ser una version menor de 1.5). En nuestro caso comprobaremos la extension en la funcion IniciaGL(), luego usaremos la extension (mi tarjeta no es muy nueva).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Como cualquier extension, chequeo si mi tarjeta la
// soporta. En el caso de VBOs si la version de OpenGL
// fuera 1.5 o mas estaria incluido en el nucleo de OpenGL
// y no haria falta la extension ni poner detras de las
// funciones ARB o detras de las definiciones _ARB.
if (!GLEE_ARB_vertex_buffer_object)
{
// Si no se soporta mandamos un mansaje...
MessageBoxA(NULL, "ARB_vertex_buffer_object NO soportado",
"USW", MB_OK);
// ... salimos del programa y no hacemos mas.
PostMessage(IdVentana, WM_CLOSE, 0, 0);
return;
}

A partir de aqui podemos usar las funciones y definiciones de VBO como extension (terminadas en ARB o _ARB).

Comenzamos con las definiciones globales.

Lo primero es declarar unas variables GLuint para guardar los identificativos de los VBO's que vamos a usar, lo mismo que cuando usamos texturas y guardamos los identificadores.

1
2
3
4
5
// Definimos tres variables para guardar el identificativo
// de los VBOs que vamos a usar.
GLuint vboVertices;
GLuint vboNormales;
GLuint vboTextura;

Declaramos 3, ya que, como antes hablamos, guardaremos cada tipo de informacion de los vertices en un VBO distinto (se podria hacer de otra manera).

En la funcion CargaModelo() vamos a crear los 3 VBO's.
Definiremos el tipo de VBO, el tamaño, el puntero del buffer donde tenemos los datos y, en nuestro caso, indicaremos que no se van a modificar durante el programa (no quiere decir que no se pueda pero OpenGL se fia de lo que le decimos para decidir donde guarda el VBO para que sea mas eficiente).

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
// Vamos a crear los 3 VBOs y los vamos a cargar con los datos
// que antes teniamos simplemente en un array.
// A partir de aqui los datos estaran en mamoria de la tarjeta de video
// o memoria AGP, o donde el driver OpenGL de nuestra tarjeta crea que
// va a rendir mejor
 
// Generamos 3 VBOs para los vertices, normales y coordenadas
// de textura
glGenBuffersARB(1, &vboVertices);
glGenBuffersARB(1, &vboNormales);
glGenBuffersARB(1, &vboTextura);
// Indico a OpenGL que voy a hacer algo con el VBO que usare
// para los vertices, hago bind del VBO.
glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboVertices);
// Paso al VBO los datos de vertices, indico que los datos pasados son
// coordenadas de vertices, normales o textura (no indices), el tamaño
// del buffer, el puntero donde estan los datos a traspasar y por ultimo
// la frecuencia con que se van a modificar los datos del VBO
// (en nuestro caso nunca)
glBufferDataARB(GL_ARRAY_BUFFER_ARB, sizeof(GLfloat)*9*NCaras,
Puntos, GL_STATIC_DRAW_ARB);
// Hago bind del VBO para normales.
glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboNormales);
// Indico el tamaño y le paso los datos de las normales
glBufferDataARB(GL_ARRAY_BUFFER_ARB, sizeof(GLfloat)*9*NCaras,
Normales, GL_STATIC_DRAW_ARB);
// Hago bind del VBO para coordenadas de textura.
glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboTextura);
// Le paso los datos de coordenadas de textura.
glBufferDataARB(GL_ARRAY_BUFFER_ARB, sizeof(GLfloat)*6*NCaras,
uvTex, GL_STATIC_DRAW_ARB);

Observad que antes de operar con un VBO se debe hacer "bind" de el, lo mismo que cuando hacemos "bind" de una textura, para que OpenGL sepa sobre cual estamos trabajando en ese momento.
En el momento en que pasamos el puntero con los datos, OpenGL copiara esos datos al VBO (si lo creemos oportuno podriamos borrar los datos originales).

En la funcion PintaModelo() cambiaremos la forma en que indicabamos los punteros de los Vertex Arrays. Ahora en vez de pasar los punteros con los datos a la funcion glVertexPointer, glNormalPointer o glTexCoordPointer, haremos "bind" del VBO correspondiente que contiene los datos y luego llamaremos a una de esas 3 funciones pasandole un 0 en vez de el puntero correspondiente (es una forma de indicar que no coja los datos de un buffer de memoria si no de un VBO).

Para los vertices seria asi:

1
2
3
4
5
6
7
8
// Hago bind del VBO para vertices, indico a 
// OpenGL que lo use a partir de ahora.
glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboVertices);
 
// Como uso un VBO, le paso 0 en vez de un puntero.
// Eso quiere decir que use el VBO, que ya tiene los
// datos de vertices.
glVertexPointer(3, GL_FLOAT, sizeof(GLfloat)*3, 0);

Para las normales lo mismo:

1
2
3
4
5
6
7
// Hago bind del VBO para normales
glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboNormales);
 
// Le paso 0 en vez de un puntero para indicar que
// use los datos de normales del VBO que esta activo y
// donde ya estan los datos de las normales.
glNormalPointer(GL_FLOAT, sizeof(GLfloat)*3, 0);

Cuando indico donde estan las coordenadas de textura, lo mismo:

1
2
3
4
5
6
7
8
// Hago bind del VBO que tiene las coordenadas 
// de textura
glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboTextura);
 
// Le indico que las coordenadas de textura estan
// en el VBO activo (el ultimo con el que hicimos bind)
// pasando 0 o NULL en vez de un puntero.
glTexCoordPointer(2, GL_FLOAT,sizeof(GLfloat)*2, 0);

Al terminar de pintar indico a OpenGL que no use ningun VBO y para eso hago "bind" con 0:

1
2
3
4
5
// Al hacer bind de un VBO pasando 0 como 
// parametro en vez de el id de un VBO le
// estoy diciendo que deje de usar VBOs,
// que use vertex array normales.
glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);

Luego, en la funcion BorraModelo(), eliminamos los VBO's que hemos creado para no dejar basura, como de costumbre:

1
2
3
4
5
// Como siempre, borro lo que creo para no dejar basura
// (memoria asignada sin usar)
glDeleteBuffersARB(1, &vboVertices);
glDeleteBuffersARB(1, &vboNormales);
glDeleteBuffersARB(1, &vboTextura);

Ya estaria terminado el tema si no hubieramos puesto otra capa de textura al modelo en el capitulo 17 sobre multitextura, asi que en la funcion Pinta() cambiaremos algo.

Antes de pintar el modelo, al asignar las coordenadas de textura para esta capa, indicamos que coja las coordenadas del VBO, lo mismo que hicimos con la primera capa de textura.

1
2
3
4
5
6
7
8
// Hago bind del VBO con las coordenadas de textura
// para usar las mismas que el modelo pero en la
// TEXTURE1
 
glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboTextura);
// E indico a OpenGL que los datos estan el un VBO pasando
// 0 en vez de un puntero.
glTexCoordPointer(2, GL_FLOAT, 0, 0);

Tras pintar el modelo no cambiamos nada, no hace falta hacer "bind" del VBO "0" como hicimos al terminar la funcion PintaModelo() precisamente porque esta funcion ya lo hace.

Con esto hemos terminado de implementar VBO's en nuestra escena. El resultado visual sera exactamente el mismo que en el capitulo 17, el anterior, pero el rendimiento sera mejor; aunque a lo mejor no se note mucho aplicado solamente a un modelo con pocos vertices como nuestro rinoceronte.
Para un pequeño juego con pocos objetos no se notara apenas. En un juego grande con muchos muñecos, cualquier optimizacion sera caida del cielo para que nuestro programa se mueva con fluidez.

El codigo completo seria: usw18.cpp




¡Sólo los usuarios registrados pueden escribir comentarios!
+/- Comentarios
Buscar

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