Mini Manual de OpenGL Por Daniel Barrero (dbarrero@cable.net.co) - 11/6/2002 Introducción OpenGL es una interface de software para el hardware grafico, esta interface consiste de una larga serie de comandos para manipulacion de objetos y operaciones sobre estos los cuales permiten controlar la implentacion realizada en la forma de una maquina de estados finitos, donde cada una de las variables que determinan el estado se aplican a partir de ese punto hasta que se indique explicitmanete el cambio, asi las variables de estado de OpenGL que vamos a utilizar mas comunmente son: • Color (de dibujo y de borrado). • Matrices de Transformacion (GL_MODELVIEW, GL_PROYECTION). • Patrones (de lineas y de relleno). • Modo de dibujo de poligonos. • Buffers de dibujo. • Buffer de Stencil. • Buffer de profundidad (z-Buffer). • Buffer de acumulacion. Funcionamiento de OpenGL: Para poder trabajar con OpenGL, primero se debe crear un contexto de trabajo, este contexto contiene el estado actual de maquina finita, asi como las referencias a los diferentes buffers de trabajo, estos buffers se pueden ver como zonas de memoria correspondiendo a la pantalla en las cuales OpenGL va a dibujar, en general a parte del buffer de color (GL_COLOR_BUFFER) que es el buffer en el cual se van a dibujar las primitivas, existen otro tipo de buffers mas especializados. La configuracion en memoria de estos buffers (cuantos bits representan un pixel, etc) depende de la manera como fue creado el contexto OpenGL y de las limitaciones del hardware, por esto no se puede acceder directamente sino solo a traves de las primitivas OpenGL. OpenGL puede funcionar adicionalmente de dos maneras, de modo directo o indirecto: • Modo directo: las primitivas se van dibujando a medida que se van definiendo. Instruccion -> Buffer de Color = Pantalla • Modo indirecto: las primitivas se guardan en una lista y solo se dibujan cuando el usuario decida o la lista este llena, esto permite optimizar la fase de dibujo. Instruccion-> Pila de instrucciones-> flush -> Buffer de Color = Pantalla En este modo cuando se desea que OpenGL pinte lo que esta en la lista se utiliza la instruccion glFlush(): esta instruccion obliga a pintar y no espera a que el hardawre termine para continuar con el programa, analogamente la glFinish() obliga a pintar pero espera a que el hw termine antes de continuar con el programa. En el modo indirecto, OpenGL permite definir dos buffers de colores (doublebuffer), asi un buffer corresponde a lo que se ve en pantalla y otro a el buffer donde se esta pintando, de esta manera una vez que se ha pintado todo lo deseado y se quiere que esto aparezca en pantalla se intercambian los buffers, esta instruccion depende del sistema operativo para esto se utilizara la instruccion de la libreria portable glut: glutSwapBuffers() (esta ejecuta implicitamente glFlush o glFinish), en este modo glFlush y glFinish obligan a pintar en el buffer de dibujo pero esto NO sera visible hasta intercambiar buffers. Primitivas de dibujo : En OpenGL solo se pueden dibujar primitivas muy simples, tales como puntos lineas, cuadrados, triangulos y polygonos, a partir de estas primitivas es posible construir primitivas mas complejas como arcos y circulos aproximandolos por poligonos. Toda primitiva de dibujo se construye con un par: glBegin(tipo_de_primitiva); glVertex2f(); ... glEnd(); donde tipo_de_primitiva puede ser cualquiera de las siguientes: GL_POINTS: Cada vertice es un punto GL_LINES: Cada par de vertices sucesivos es una linea GL_LINE_STRIP: lineas conectadas. GL_LINE_LOOP: lineas conectadas donde el ultimo y el primer vertice indican una linea cerrando el poligono. GL_POLYGON: poligono (relleno o no) donde los vertices sucesivos componiendolo se dan el sentido contrario de las manecillas del reloj. GL_QUADS: cuadrilateros separados, cada 4 vertices hacen un quad. GL_QUAD_STRIP: tira de cuadrados unidos, cada par de vertices sucesivos forman un cuadrado con el par anterior. GL_TRIANGLES: Triangulos separados, cada 3 vertices hacen un triangulo. GL_TRIANGLE_STRIP: tira de triangulos unidos (similara quad_strip). GL_TRIANGLE_FAN: Grupo de triangulos con un unico vertice comun a todos. Dentro del par glBegin, glEnd solo pueden ir instrucciones OpenGL para definir objetos tales como vertices, y colores (existen otras mas complejas como normales y materiales) y no transformaciones ni cambios de estado (diferentes a los especificados), adicionalmente dentro del par pueden ir instrucciones de programacion del lenguaje tales que ciclos, condicionales, llamados a funciones, etc. Siempre y cuando no usen alguna de las funciones OpenGL no permitidas, i.e.: glBegin(GL_POLYGON)’; glColor3f(1.0, 0.0, 0.0); // rojo for(int i=0; i<10; i++){ glVertex3f(1.0/i, i*i, 0.0); } glColor3f(0.0, 1.0, 0.0); // verde glVertex3f(1.0, 0.0, 0.0); glColor3f(0.0, 0.0, 1.0); // azul glVertex3f(1.0, 1.0, 0.0); glEnd(); muchas primitivas basicas tiene sufijos indicando el tipo del valor o variable que se le pasan como parametro, asi como el numero de parametros, asi una instruccion como p.ej. glVertex puede tener 2,3 o 4 parametros, asi comor recibir tipos enteros, flotantes, etc. O un vector conteniendo esos parametros Entonces en los manuales se escribe como: glVertex[234][sifd][v] quiere decir que se le pueden pasar 2,3 o 4 parametros mas el tipo o un vector, como minimo debe tener el numero de parametros y el tipo i.e.: glVertex2f(1.0,0.0). o si se usa un vector v Glfloat v[3]={1.0,0.5} entonces seria glVertex2fv(v); los tipos de datos tambien se pueden usar para definir variables, estos son: b entero 8 bits GLbyte s entero 16 bits GLshort i entero 32 bits Glint f float 32 bits GLfloat d flotante 64 bits GLdouble ub entero sin signo 8 bits GLubyte us entero sin signo 16 bits GLushort ui entero sin signo 32 bits GLuint Las primitivas mas basicas de dibujo son entonces: glRect[sid][v]: dibuja un rectangulo, NO puede estar en bloque glBegin/glEnd glColor3[f][v]: para cambiar el color actual de dibujo, puede estar en bloque glBegin/glEnd glVertex[234][sifd][v]: vertice de un punto,linea o poligono, SOLO puede estar entre glBegin/glEnd Variables de estado que afectan las primitivas anteriores (NO puede estar en bloque glBegin/glEnd): glPointSize(size): size= flotante indicando el tamaño de dibujo de un punto > 0.0, 1.0 por defecto. glLineWidth(size): size= flotante indicando el ancho de dibujo de una linea > 0.0, 1.0 por defecto. glLineStipple(factor,patron): factor = entero indicando un factor de escalamiento, patron= short sin signo conteniendo el patron de bits par pintar la linea (punteado,etc..).. glPolygonMode(cara,modo): cara corresponde al lado del poligono, si el que esta hacia el usuario (pantalla) (GL_FRONT) o hacia el otro lado (GL_BACK) o los dos (GL_FRONT_AND_BACK), el modo puede ser: relleno (GL_FILL), solo los vertices (GL_POINT), o solo el borde (GL_LINE). Otras funciones y variables de estado: glClearColor(r,g,b): r,g,b son flotantes y definen el color de borrado de la pantalla (fondo). glClear(mask): mask = GL_COLOR_BUFFER_BIT borra el buffer de dibujo con el color de fondo definido. Transformaciones: Las tranformaciones se realizan multiplicando por las matrices y se aplican en sentido inverso al que se escriben, esto es si quiero rotar y luego trasladar un objeto en el codigo, primero traslado, luego roto y luego si pinto mi objeto (es tambien en sentido inverso al que se lee el codigo), OpenGL trabaja con dos matrices diferentes: • GL_MODELVIEW: es la matriz donde se trabajan con las transformaciones. • GL_PROJECTION: es donde se define la relacion mundo viewport. Para cambiar de matriz a utilizar se usa la instruccion: glMatrixMode(modo): es uno de los dos modos de matriz. Adicionalmente, la matriz en uso se puede guardar y eliminar de una pila: glPushMatrix(): coloca la matriz en la pila. glPopMatrix(): quita la matriz de la pila. La pila de la matriz de projeccion tiene un limite maximo de 2 en la mayoria de las implementaciones. Para trabajar con la matriz directamente se tiene: glLoadIdentity(): inicializa la matriz actual con una matriz identidad. glMultMatrix(matriz): matriz = vector de 16 flotantes con el que multiplica la matriz actual. glLoadMatrix(matriz): matriz = vector de 16 flotantes con el que reemplaza la matriz actual. glGetFloatv(modo,m): modo = uno de los modos de matriz. matriz = vector de 16 flotantes en el que se recupera la matriz actual de OpenGL. Para no tener que generar sus propias matrices, se proveen instrucciones de tranformacion: glRotate[fd](a,x,y,z): rota un angulo a alrededor del vector x,y,z glTranslate[fd](x,y,z): traslada una cantidad x,y,z. glScale[fd](sx,sy,sz): multiplica cada coordenada por el valor de escalamiento correspondiente. Estructura de un programa OpenGL Para la creacion de programas OpenGL se va a utilizar la libreria GLUT, esta libreria permite olvidarse de los problemas especificos de cada plataforma, La estructura general de un programa utilizando la libreria GLUT es la siguiente (en C o C++ es las funciones de opengl son las mismas): #include <GL/gl.h> #include <GL/glut.h> void init(void) { glClearColor(0,0,0,0); // iniciar estado global (color de borrado } void reshape ( int w, int h) { glViewport(0,0,w,h); // coloque el viewport al tamano de la ventana glMatrixMode(GL_PROJECTION); // modo matriz de proyeccion glLoadIdentity(); // inicialize la matriz glOrtho(0, w, 0, h, -1, 1); // tranformacion directa a pantalla glScalef(1, -1, 1); // invierta Y para que vaya para abajo glTranslatef(0, -h, 0); // corra el origen a la parte superior izq. glMatrixMode(GL_MODELVIEW); // modo matriz de transformacion glLoadIdentity(); // inicialize la matriz } void display(void) // pinte.... { glClear(GL_COLOR_BUFFER_BIT); // borre la pantalla glBegin(GL_TRIANGLES); // pinte glColor3f(0.0, 0.0, 1.0); glVertex3f(0, 1,0); glVertex3f(0, 0,0); glVertex3f(0, -1,0); glEnd(); glutSwapBuffers(); // cambie buffers... } void keyboard(unsigned char c, int x, int y) { switch(c) { // aqui adicionar los casos para las diferentes teclas. case 27: exit(0); break; } glutPostRedisplay(); //pinte si se cambiaron variables que afectan el dibujo } void idle(void) { // modifique variables glutPostRedisplay(); //pinte } void main( int argc, char **argv) { glutInit(&argc,argv); // inicie la libreria GLUT glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE); // inicie el modo de video glutInitWindowSize(w,h); // inicie tamano de la ventana glutInitWindowPos(x,y); // inicie posicion de la ventana glutCreateWindow("nombre ventana "); init(); // iniciar variables y estado global glutDisplayFunc(display); // instale la funcion de display glutReshapeFunc(reshape); // instale la funcion de tamaño. glutKeyboardFunc(keyboard); // instale la funcion de teclado glutIdleFunc(idle); // funcion de animacion. glutMainLoop(); // ciclo infinito } Manejo de Buffers Para utilizar los diferentes tipos de buffers de OpenGL estos se deben crear al crear el contexto opengl, en el caso de la utilizacion de la libreria glut, al llamar la funcion: glutInitDisplayMode (buffers), buffers es una combinacion de valores indicando que buffers se deben crear i.e. para crear un contexto con doble buffer rgba y z-buffer buffers seria = GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH, los valores para crear otro tipos de buffers son: GLUT_STENCIL = buffer de stencil. GLUT_ACCUM = buffer de acumulacion. Todos los buffers deben ser activados o desactivados dependiendo de la utilizacion con el comando glEnable(param) o glDisable(param), donde param corresponde al tipo de test que se activa, i.e param= GL_STENCIL_TEST para el buffer de stencil, o param=GL_DEPTH_TEST para el buffer de profundidad (activado por defecto). Los buffers de stencil, acumulacion y profundidad funcionan aplicando una serie de operaciones y funciones sobre los valores que se van a escribir en el buffer, asi para manejar la funcion del buffer de profundidad se utiliza la funcion glDepthFunc(funcion), donde funcion indica cuando pasa el test de profundidad (GL_EQUAL, GL_LESS, GL_LEQUAL (por defecto), GL_NOTEQUAL, GL_GREATER, GL_GEQUAL). En el caso de la funcion de test, se define glStencilFunc(funcion, mascara), donde funcion son las mismas que para la profundidad, y mascara indica que bits del stencil se utilizan. Adicionalmente a la funcion de stencil, se utiliza la operacion de stencil glStencilOp(fallo,zfallo,zpaso) que especifica como se modifica el buffer de stencil dependiendo de si la funcion del buffer stencil y z fallaron o pasaron, pueden ser GL_KEEP,GL_ZERO, GL_REPLACE, GL_INCR, GL_DECR, GL_INVERT. Manejo de Animacion y Timers Opengl no maneja animacion ni timers directamente ya que esto depende del sistema operativo, OpenGL solo se preocupa de colocar poligonos en la pantalla, y solo seria cuestion de ordenarle de manera ciclica el repintar la pantalla con paramentros nuevos, el problema se encuentra cuando se desea que la animacion se no bloqueante, la libreria GLUT nos facilita el manejo de la animacion de dos maners independientemente del Sistema operativo de manera no bloqueante: 1. Funcion de animacion Idle, GLUT nos permite manejar la animacion definiendo una funcion de animacion con la funcion glutIdleFunc(funcname), donde funcname es el nombre de la funcion que se va a llamar para realizar la animacion, esta funcion no recibe ni retorna parametro alguno, esta funcion se debe encargar de calcular los parametros de la animacion y luego llamar la funcion de dibujo, esta no se debe llamar directamente sino con la funcion glutPostRedisplay() que quiere decir que solo se llama una vez se acabe de calcular la funcion idle, esta funcion idle no se ejecuta de manera regular, solo se ejecuta una vez se hallan procesado todos los eventos que recibe la aplicacion tales como teclado, repintar, etc. Por lo tanto la velocidad de la animacion resultante puede no ser constante. 2. Funciones de Timers, para evitar el problema de la velocidad de animacion irregular y para tener mas control sobre el tiempo de ejecucion de la funcion de animacion, glut nos permite definir timers de a los cuales le asignamos una funcion que se debe ejecutar cada cierto tiempo definido por el timer, para definir estos timers se utiliza la funcion: glutTimerFunc(tiempo,funciontimer,valor), donde tiempo es el tiempo en milisegundos del timer, funciontimer es la funcion a llamar la cual no devuelve nada pero recibe un parametro entero (valor), valor es el parametro que se le va a pasar a la funcion. Esta funcion se ejecuta dentro del tiempo determinado y termina, si se desea que se vuelva a llamar, al final de la funcion se debe instalar de nuevo el timer. El valor sirve para seleccionar diferentes acciones la proxima vez que se llame el timer, como estas funciones no reciben parametros, cualquier resultado debe ser devuelto por una variable global. Compilacion de aplicaciones OpenGL en Windows Para compilar un programa OpenGL suponiendo que se esta en un sistema operativo MS-Windows, con el compilador VisualC++, se debe recuperar la libreria glut para windows (glut.h glut32.lib y glut32.dll), los archivs .h y . lib se deben colocar en el directorio respectivo para includes y librerias del VisualC, el archivo glut32.dll se debe colocar en el directorio system32 de windows idealmente o en su defecto en el directorio donde va a quedar el ejecutable. En el VisualC se debe crear un proyecto nuevo de tipo win32 o win32 consola (si se va a utilizar GLUT NO! se debe crear proyecto de tipo MFC) En las opciones de compilacion del proyecto, en el tabulador de encadenamiento, se debe adicionar las siguientes librerias en este orden especifico: glut32.lib glu32.lib opengl32.lib. Compilacion de aplicaciones OpenGL en Linux Para compilar un programa OpenGL suponiendo que se esta en sistema operativo UNIX o Linux con las librerias instaladas en lugares estandar se debe dar el siguiente comando suponiendo que todo el programa esta en un solo archivo: Programa esta en C: gcc -o nombre_ejecutable nombre_programa.c –lglut –lGLU –lGL lm Programa esta en C++: g++ -o nombre_ejecutable nombre_programa.c –lglut –lGLU –lGL lm Si el programa esta compuesto por varios modulos C o C++ se debe realizar un archivo llamado: Makefile, este archivo debe ser de la manera siguiente: CC = gcc CXX = g++ INCDIR = -I./ -I/usr/local/include # XLIBS = -lX11 -lXt -lXext -lXm -lXmu -lXi GL_LIBS= -lglut -lGLU -lGL -lm LIBDIR = -L/usr/X11/lib -L/usr/X11R6/lib -L/usr/local/lib CFLAGS = -O DOCDIR = doc OBJECTS= modulo1.o modulo2.o PROGS = nombreprograma .SUFFIXES: .c .cc .cpp .c: $(CC) -o $@ $(CFLAGS) $(INCDIR) $< $(LIBDIR) $(GL_LIBS) $(XLIBS) .cc: $(CXX) -o $@ $(CFLAGS) $(INCDIR) $< $(LIBDIR) $(GL_LIBS) $(XLIBS) .cpp: $(CXX) -o $@ $(CFLAGS) $(INCDIR) $< $(LIBDIR) $(GL_LIBS) $(XLIBS) all: $(OBJECTS) $(PROGS) nombreprograma: modulo1.c modulo2.cpp $(CC) -o $@ $(CFLAGS) $(INCDIR) $(OBJECTS) $(LIBDIR) $(GL_LIBS) $(XLIBS) Ojo: Las identaciones en el archivo son tabulador no espacios en blanco!. Solo se debe cambiar nombreprograma por el nombre del archivo que contiene la funcion main, y modulo1, modulo2, etc por los nombres de los otros archivos conteniendo otras funciones del programa, para compilar solo basta dar en la linea de comandos el comando: make Para correr el programa ya sea que se haya compilado con la ayuda de make o directamente a mano con gcc/g++ solo basta dar el comando: ./nombredeprograma Para mas informacion sobre el comando make dar el comando: man make ,o en caso que este falle: info make Para mas informacion sobre opciones gcc/g++ dar el comando: man gcc ,o: man g++ Informacion Adicional Para mas informacion sobre instalar OpenGL y glut en caso que estos no esten instalados ir a: http://www.opengl.org y http://www.mesa3d.org Tutoriales basicos y avanzados de opengl se pueden encontrar en los siguientes sitios: • http://nehe.gamedev.net/ • http://romka.demonews.com/index_eng.htm