18. Vertex Buffer Objects en OpenGL |
![]() |
![]() |
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:
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
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:
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.
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:
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:
En la funcion Pinta() quitaremos este bloque:
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).
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.
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).
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:
Para las normales lo mismo:
Cuando indico donde estan las coordenadas de textura, lo mismo:
Al terminar de pintar indico a OpenGL que no use ningun VBO y para eso hago "bind" con 0:
Luego, en la funcion BorraModelo(), eliminamos los VBO's que hemos creado para no dejar basura, como de costumbre:
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.
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!
3.26 Copyright (C) 2008 Compojoom.com / Copyright (C) 2007 Alain Georgette / Copyright (C) 2006 Frantisek Hliva. All rights reserved." |