Introducción
Por razones de diseño, la especificación de OpenGL se aisló de
cualquier dependencia de un sistema de ventanas concreto. El interface resultante
es una librería de trazado en 2D y 3D portable y eficiente. Es trabajo del
sistema de ventanas nativo el abrir y trazar ventanas. La librería de OpenGL
se comunica con el sistema nativo a través de librerías adicionales
auxiliares. Por ejemplo, la librería auxiliar GLX describe la
interacción entre OpenGL y el sistema X Window System.
El kit de utilidades de OpenGL (OpenGL Utility Toolkit - GLUT) es un interface
de programación con bindings para ANSI C y FORTRAN para escribir programas
OpenGL independientes del sistema de ventanas. Lo ha escrito Mark J. Kilgard y cubre
un gran agujero dejado por la especificación de OpenGL. Gracias a GLUT, los
desarrolladores pueden usar un inteface común para el sistema de ventanas
independientemente de la plataforma empleada. Las aplicaciones de OpenGL que usan
GLUT se pueden portar fácilmente entre plataformas sin tener que introducir
muchos cambios en el código fuente. En fin, GLUT simplifica la producción
de código OpenGL y complementa la librería de OpenGL.
GLUT es relativamente pequeño y fácil de aprender. Está bien
diseñado y su autor ha escrito una buena documentación. Por eso, parece
redundante empezar una serie de artículos aquí en
LinuxFocus. Recomendamos a cualquier
desarrollador serio que lea la documentación de Mark. Nuestro propósito
escribiendo estas columnas de GLUT es introducir la librería GLUT y su uso
paso por paso con ejemplos, como complemento a la lectura de la serie de OpenGL de
este magazine. Esperamos hacer una contribución útil y como mínimo
motivar a alguno de vosostros a subiros al vagón de Linux y OpenGL. De cualquier
modo, obtener vuestra copia de la documentación de Mark como referencia.
La API de GLUT es una máquina de estados, como OpenGL. Esto significa que
GLUT tiene una serie de variables de estado que duran toda la ejecución de
la aplicación. El estado inicial de la máquina de GLUT se ha elegido
razonablemente para ajustarse a la mayor parte de aplicaciones. El programa puede
modificar los valores de las varibles de estado para ajustarlas a su gusto. Cuando
se llama a una función de GLUT, su acción se modifica de acuerdo a los
valores de las variables de estado. Las funciones de GLUT son simples, tienen pocos
parámetros. Nunca devuelven punteros, y los únicos punteros pasados
a las funciones de GLUT son punteros a cadenas de caracteres y manejadores de fuentes.
Las funciones de GLUT se pueden clasificar en varias subAPIs según su
funcionalidad:
- Inicialización
- Inicio del procesado de eventos
- Control de ventanas
- Control de overlay
- Control de menús
- Registro de funciones Callback
- Control del mapa de colores
- Obtención del estado
- Trazado de fuentes
- Trazado de formas geométricas
Es este artículo exploraremos algunas de las funciones de
inicialización, procesado de eventos y control de ventanas necesarias
para empezar un sencillo programa en OpenGL.
Initializations
Todo programa de OpenGL que utilice GLUT debe empezar inicializando el
estado de la máquina de estados de GLUT. Las funciones de inicialización
de GLUT tienen el prefijo glutInit-. La rutina principal de
inicialización es glutInit:
Uso:
glutInit(int **argcp,
char **argv);
argcp
es un puntero a la variable argc de la función main (sin modificar).
Al acabar la función, el valor apuntado por argcp se actualiza, ya que glutInit
extrae todas las opciones de la línea de comandos relevantes para la
librería GLUT. Por ejemplo: bajo X Window System toda opción relevante
para la ventana X asociada a la ventana GLUT..
argv
es la variable argv de la función main (sin modificar).
glutInit se encarga de modificar las variables de estado de GLUT y
negociar una sesión con el sistema de ventanas. Hay muy pocas funciones
que pueden aparecer antes de glutInit; solo aquellas precedidas por
glutInit-. Estas rutinas se pueden usar para poner los estados de
inicialización por defecto, por ejemplo:
Uso:
glutInitWindowPosition(int x,
int **y);
glutInitWindowSize(int width,
int **height);
x , y
posición en la pantalla en píxels de la ventana
(esquina superior izquierda)
width, height
ancho y alto en píxels de la ventana.
Existe otra rutina de inicialización ominpresente en toda aplicación
en OpenGL, glutInitDisplayMode():
Uso:
glutInitDisplayMode(unsigned int mode);
mode
es el modo de display, un OR de los posibles valores de display, que
son:
GLUT_RGBA |
Selecciona una ventana en modo RGBA. Es el valor por defecto
si no se indican ni GLUT_RGBA ni GLUT_INDEX.
|
GLUT_RGB |
Lo mismo que GLUT_RGBA.
|
GLUT_INDEX |
Seleciona una ventana en modo de índice de colores.
Se impone sobre GLUT_RGBA.
|
GLUT_SINGLE |
Selecciona una ventana en modo buffer simple. Es el valor por
defecto.
|
GLUT_DOUBLE |
Selecciona una ventana en modo buffer doble. Se impone sobre
GLUT_SINGLE.
|
GLUT_ACCUM |
Selecciona una ventana con un buffer acumulativo.
|
GLUT_ALPHA |
Selecciona una ventana con una componente alpha del buffer de
color.
|
GLUT_DEPTH |
Selecciona una ventana con un buffer de profundidad.
|
GLUT_STENCIL |
Selecciona una ventana con un buffer de estarcido.
|
GLUT_MULTISAMPLE |
Selecciona una ventana con soporte multimuestra.
|
GLUT_STEREO |
Selecciona una ventana estéreo.
|
GLUT_LUMINANCE |
Selecciona una ventana con un modelo de color de "luminancia".
|
Si alguna de estas propiedades no te es familiar, no te preocupes, más tarde
o más pronto escribiremos sobre ellas. Veamos un par de ejemplos, primero una
inicialización simple para una aplicación de trazado en un paso:
#include <GL/glut.h>
void main(int argcp, char **argv){
/* Poner el tamaño y posición de la ventana */
glutInitWindowSize(640, 480);
glutInitWindowPosition(0, 0);
/* Seleccionar el tipo de modo de display:
Buffer simple y color RGBA */
glutInitDisplayMode(GLUT_RGBA | GLUT_SINGLE);
/* Inicializar el estado de GLUT */
glutInit(&argcp, argv);
.....más código
};
Ahora un ejemplo de un programa de animación:
#include <GL/glut.h>
void main(int argcp, char **argv){
/* Poner el tamaño y posición de la ventana */
glutInitWindowSize(640, 480);
glutInitWindowPosition(0, 0);
/* Seleccionar el tipo de modo de display:
Buffer doble y color RGBA */
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
/* Inicializar el estado de GLUT */
glutInit(&argcp, argv);
.....más código
};
Volveremos a estos dos ejemplos a medida que aprendamos más sobre GLUT.
La principal diferencia es que en el segundo caso el display se inicializa en modo
de doble buffer, ideal para animaciones porque elimina el parpadeo cuando cambia
la imagen en la secuencia de animación.
Procesado de Eventos
Como hemos dicho antes, GLUT es una máquina de estados. Ahora veremos
que también está diseñada como un motor dirigido por eventos. Esto
significa que hay un "timer" o bucle continuo que comienza después de la
inicialización correspondiente y que procesa uno por uno todos los eventos
declarados a GLUT durante la inicialización. Los eventos son: un botón
del ratón que se ha pulsado, una ventana que se cierra, una ventana que se
redimensiona, un cursor que se mueve, unas teclas del teclado que se han pulsado,
un curioso evento "idle", esto es, no pasa nada. Cada uno de los posibles eventos
se debe registrar en una de las variables de estado de GLUT para que el "timer"
o bucle de proceso de eventos de GLUT mire periódicamente si este evento ha
sido activado por el usuario.
Por ejemplo, podemos registrar "pulsar botón del ratón" como
un evento para GLUT. Los eventos se registran mediante rutinas de registro
callback. Todas tienen la sintaxis glut[algunEvento]Func, en el caso
del click del ratón será glutMouseFunc. Un registro de callback
le dice a la máquina de GLUT que función definida por el usuario se debe
llamar si el correspondiente evento es activado. Así pues, si escribo mi rutina
MyMouse que especifica qué hacer cuando se pulsa el botón
izquierdo del ratón, o el botón derecho, etc.. entonces registraré
mi función callback después de glutInit() en main() usando
la sentencia "glutMouseFunc(MyMouse);" .
Dejemos para más tarde qué funciones callback y qué eventos
están permitidos en GLUT. Lo importante ahora es señalar que después de
registrar todos los eventos importantes de nuestra aplicación debemos invocar
la rutina de procesado de eventos de GLUT, que es glutMainLoop(). Esta
función nunca vuelve, nuestro programa básicamente comienza un bucle
infinito. Irá llamando, cuando sea necesario, las funciones callback que hayan
sido previamente registradas. Toda función main() de una aplicación OpenGL
debe pues terminar en una sentencia glutMainLoop(). Así pues, en el caso
de nuestra plantilla de una animación:
#include <GL/glut.h>
void main(int argcp, char **argv){
/* Inicializar el estado de GLUT */
glutInit(&argcp, argv);
glutInitWindowSize(640, 480);
glutInitWindowPosition(0, 0);
/* Abrir una ventana */
glutCreateWindow("My OpenGL Application");
/* Seleccionar el tipo de modo de display:
Buffer doble y color RGBA */
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
/* Registrar funciones Callback */
.....
/* Iniciar el procesado de eventos */
glutMainLoop();
};
Notad que he añadido un código que no he mencionado antes. Es una de las
funciones de control de ventanas de GLUT, glutCreateWindow(char **name).
Esto es lo que me gusta de la filosofía de diseño de OpenGL y GLUT, está
muy claro lo que hace la rutina solo mirando el nombre!. Sí, se encarga de
pasar la orden al sistema de ventanas subyacente de abrir una ventana para nuestra
aplicación de OpenGL. La ventana tendrá el nombre "name", pasado como
una cadena de caracteres. En el entorno de X Window este nombre se escribe en la esquina
superior izquierda de la ventana. La sección de control de ventanas de GLUT
tiene muchas otras funciones que iremos estudiando poco a poco. Por hoy, esta es
suficiente. También he reordenado las funciones de inicialización para
mostrar que se pueden poner después de glutInit()
Volvamos a los eventos... En el presente artículo quiero introducir
dos funciones de registro de callback que son fundamentales en cualquier programa
de animación. La función glutDisplayFunc, que registra la
función de display para la ventana actual y la función
glutIdleFunc, que registra la función "idle". Ambas rutinas
de registro esperan una función del tipo void *(void). Supongamos
que escribimos dos funciones callback adicionales en nuestra plantilla de
animación, void MyDisplay(void) que se encarga de invocar las
instrucciones OpenGL que dibujan nuestra escena en la ventana, y
void MyIdle(void) que es una función que es llamada cuando no
hay entradas del usuario, esto es, cada vez que el procesador de eventos de GLUT
da una vuelta al bucle infinito (glutMainLoop()) y no encuentra ningún
nuevo evento activado, procesa MyIdle. ¿Por qué necesito registrar
una función callback "idle" en un programa de animación? Porque si
quiero modificar cada una de las imágenes mostradas durante la animación
independientemente de la entrada del usuario, debe existir una función (la
función callback "idle") que se llame muy a menudo durante la ejecución
del programa y cambie las imágenes antes de que se dibujen en pantalla por
Mydisplay().
Animation Example
Finalmente aquí tenemos una simple plantilla para un programa de
animación:
#include <GL/glut.h>
void MyIdle(void){
/* Código para modificar las variables que definen la próxima imagen */
....
};
void MyDisplay(void){
/* Código OpenGL que dibuja una imagen */
....
/* Después de dibujar la imagen, intercambiar los buffers */
glutSwapBuffers();
};
void main(int argcp, char **argv){
/* Inicializar el estado de GLUT */
glutInit(&argcp, argv);
glutInitWindowSize(640, 480);
glutInitWindowPosition(0, 0);
/* Abrir una ventana */
glutCreateWindow("My OpenGL Application");
/* Seleccionar el tipo de modo de display:
Buffer doble y color RGBA */
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
/* Registrar funciones Callback */
glutDisplayFunc(MyDisplay)
glutIdleFunc(MyIdle)
/* Iniciar el procesado de eventos */
glutMainLoop();
};
Nota que al final de MyDisplay he añadido una nueva rutina
GLUT, glutSwapBuffers(). Es muy útil en animaciones.
Estamos usando una ventana en modo de DOBLE buffer, uno que se muestra y otro
oculto. Las instrucciones de dibujo de OpenGL en este caso siempre trazan en el
buffer oculto. La llamada a glutSwapBuffers intercambia los buffers,
mostrando en la ventana de golpe todo lo que hemos dibujado. Esta técnica
es común en animaciones por ordenador porque previene que el ojo humano vea
como se construye la imagen línea por línea.
Ya tenemos suficiente material para empezar a escribir aplicaciones OpenGL,
lo único que falta son las instrucciones OpenGL en MyDisplay
que realizan el dibujo... pero esto es otra historia ;-).
En el próximo artículo de programación en GLUT
exploraremos más profundamente las funcionalidades disponibles en la
sección de control de ventanas de GLUT como abrir multiples escenas
dentro de la misma ventana. También aprenderemos cómo usar menús
y sus pros y contras en cuanto a portabilidad.
|