Ir al Hogar Ver Índice Buscar Enlaces Acerca de nosotros
[LinuxFocus Image]
[Navegation Bar]
  Noticias   Archivos   Compañías   Consejos 

Programaci�n OpenGL: Escenas 3D

por Miguel Angel Sep�lveda


Despu�s de un largo camino por otros temas, finalmente ha llegado el momento de hablar de gr�ficos en 3D bajo OpenGL. No voy a mentir diciendo que es un tema f�cil porque no lo es. Cualquier buen programador de aplicaciones 3D con OpenGL, y en particular de animaciones, debe tener suficientes conocimientos de �lgebra lineal, geometr�a anal�tica, f�sica (mec�nica) y por supuesto dominar el an�lisis num�rico.

Intentar� hacer el resto de esta serie lo mas accesible posible para todo el mundo. Desafortunadamente, no hay forma de evitar la necesidad de tener conocimientos sobre matrices, c�mo los planos y l�neas se representan matem�ticamente en el espacio 3D, vectores, aproximaciones polinomiales a curvas, por mencionar solo unos pocos.

Durante la �ltima semana he estado pensando como presentar este material m�s complejo a una audiencia amplia. Los libros cl�sicos utilizan una metodolog�a incremental paso a paso, m�s o menos el m�todo que he seguido en los dos art�culos anteriores. He decidido no seguir con esta metodolog�a pues se necesitar�a demasiado tiempo (meses) para conseguir llevar al lector al punto de poder escribir su propio c�digo. En su lugar voy a aventurarme a utilizar otro m�todo, que he decidido llamarlo "tratamiento de choque". Esta vez voy a incluir una demostraci�n de una de mis simulaciones 3D y luego explicare bit por bit qu� hace el c�digo. Finalmente, intentar� explicar con m�s detalle todas las cuestiones que normalmente se tratan en los libros est�ndar de OpenGL. Creo que saltando directamente al final y dando al lector un ejemplo de c�digo con algunas cosas interesantes incitar� a los lectores a experimentar y a probar cosas, incluso aunque yo no haya dicho aun como funcionan todas las cosas exactamente. Espero que este m�todo funcione y la gente lo encuentre r�pido y m�s directo.

As� que pongamos manos a la obra. Durante los 6 meses anteriores he estado trabajado en la universidad de Pittsburgh en una herramienta OO (Object Oriented) para el desarrollo de simulaciones de pol�meros y geles. El proyecto est� bastante avanzado, la f�sica es muy interesante, incluso para inform�ticos, porque un gel es b�sicamente una red neuronal de pol�meros y muchas de las t�cnicas desarrolladas para redes neuronales se pueden aplicar a la construcciones del gel. He elegido unos cuantos objetos de esta herramienta y los he empaquetado en esta sencilla demo: ../../common/May1998/example2.tar.gz. Se puede compilar bajo Linux, cualquier UNIX o sobre Windows 95/NT (suponiendo que ya tienes instalado GLUT). La demo muestra un pol�mero simple (una cadena lineal de mon�meros enlazados) movi�ndose en suspensi�n en una soluci�n a una determinada temperatura. La din�mica es tentadora, se asemeja a una serpiente excitada. La animaci�n es muy viva debido a las colisiones de las mol�culas del solvente. No se puede ver el solvente, pero influye en el movimiento del pol�mero a trav�s de las ecuaciones del movimiento.

[model of polymer]

El modelo utilizado para dibujar el pol�mero es bastante sencillo; example2 controla las coordenadas (x, y, z) de cada nodo (mon�mero) a lo largo de la cadena pol�mero. En cada imagen (frame) de la animaci�n dibujamos una esfera en las coordenadas del mon�mero y luego los unimos utilizando cilindros que van de mon�mero a mon�mero. Por tanto tenemos dos primitivas elementales 3D: una esfera y un cilindro. Como en cualquier mol�cula, la distancia entre mon�meros cambia en el tiempo, con lo que no podemos utilizar "un cilindro" para dibujar todos los enlaces, se ha de re-escalar de acuerdo a la distancia entre cada par de mon�meros.

Primera pregunta: Has dicho que tienes dos objetos en 3D, una esfera y un cilindro unidad. digamos que ambos objetos est�n centrados en el origen de coordenadas. Si todo lo que sabemos sobre el pol�mero es la secuencia de (x, y, z) de los nodos, �c�mo podemos escalar, rotar y trasladar las replicas de los cilindros para crear los enlaces de los pol�meros?

Por alguna raz�n que no logro comprender, los cient�ficos inform�ticos han decidido cambiar el significado cl�sico de las coordenadas cartesianas: x es horizontal, y es vertical y z va en direcci�n al observador. Ten cuidado con esto porque si vienes de una formaci�n matem�tica te puede confundir bastante.

En la parte superior de la ventana de la animaci�n se muestra informaci�n sobre el estado de la animaci�n que te permitir� saber en todo momento el tiempo, la temperatura del pol�mero, temperatura media del pol�mero, temperatura de la soluci�n, la fricci�n del solvente y el �ngulo de rotaci�n de la c�mara exterior. Para tener una visi�n m�s amplia del pol�mero desde todos los lados, la c�mara (tu punto de vista) gira lentamente al rededor del centro de gravedad del pol�mero.

De echo la longitud del pol�mero que he elegido para la demo es tan corta que la rotaci�n de la c�mara no es realmente necesaria, con un poco de tiempo el pol�mero llega a girar �l solo. Sigue adelante y edita el fichero example2.cxx y modifica la definici�n de POLYMERLENGTH a un valor entre 2 y 100. La c�mara gira porque quiero que el lector se d� cuenta de un aparente problema: cambia del sistema de coordenadas. El sistema de coordenadas de los nodos es utilizadas por las ecuaciones del movimiento y por tato est�n expresadas en coordenadas del mundo, independientes del punto de vista concreto desde el que el usuario observa la escena. Estas coordenadas deben proyectarse a las coordenadas 2D x-y de la pantalla del ordenador. Cada vez que cambias el punto de vista, cambian las formulas que transforman las coordenadas internas del pol�mero en coordenadas 2D de la ventana.

Segunda pregunta. �C�mo solucionas este problema? cambiando las ecuaciones del movimiento del mundo real a coordenadas 2D del punto de vista no es una soluci�n, pues requiere demasiada �lgebra, es muy complicado de implementar y es dif�cil no cometer errores.

La respuesta a la segunda pregunta es sencilla. S�lo hay una opci�n: realizar toda la din�mica y la representaci�n del modelo 3D (pol�mero) en las coordenadas del mundo y luego cambiar las coordenadas del mundo a las coordenadas 2D del punto de vista de la c�mara en el momento de dibujar (render) la imagen. OpenGL es bastante eficiente realizando estas transformaciones, incluso se pueden realizar por hardware (para aquellos que posean una tarjeta gr�fica con soporte OpenGL ver�n la diferencia). Pero antes de entrar a describir c�mo OpenGL resuelve este problema, consideremos primero cu�ntas transformaciones de coordenadas hay desde el mundo real en 3D a las coordenadas finales 2D de la ventana.

Primero viene la transformaci�n de coordenadas del modelo (Modelview), para proyectar las coordenadas originales del mundo a las coordenadas de la vista (eye coordinates), estas son las coordenadas 3D relativas a la posici�n del ojo del que mira la escena (o sea las coordenadas de la c�mara). Se llama transformaci�n Modelview porque ----------------- It is called Modelview transformation because it really involves many similar though distinct operations. ----------------- Modelando y viendo proyecciones, lo �ltimo es an�logo a posicionar una c�mara de fotos en un estudio enfocando hacia la escena que se ha de fotografiar; el modelado de la proyecci�n es entonces como posicionar el objeto a fotografiar en frente de la c�mara.

Siguiendo la secuencia de transformaciones, las coordenadas de la vista se pasan a las coordenadas de transformaci�n de la proyecci�n. El prop�sito de estas transformaciones puede parecer un poco esot�rico a estas alturas. Despu�s de posicionar la c�mara en la direcci�n correcta y de posicionar los objetos en el campo de la escena, OpenGL quiere saber qu� cantidad (volumen) de campo debe ser proyectado sobre la ventana 2D de la pantalla. Por ejemplo, la c�mara puede estar dirigida hacia una monta�a muy distante, el campo que vemos define un volumen de espacio muy grande. Los ordenadores s�lo pueden trabajar con cosas finitas, por ello hay que especificar qu� cantidad, de toda la escena, ha de ser recortada. Esta transformaci�n tambi�n se encarga de eliminar las superficies que no se pueden ver. Las coordenadas finales obtenidas son las Clip coordinates, recuerda siempre que no es suficientes que tus objetos 3D est�n en frente de la c�mara, sino que deben estar situados dentro de los planos de recorte definidos por la transformaci�n de proyecci�n. Las distintas perspectivas 3D (como c�nica u ortogonal) se definen en este nivel.

Por el momento no entraremos en qu� es una perspective division, ni cu�l es la diferencia entre las coordenadas de recorte y las coordenadas normalizadas de dispositivo. No es necesario saberlo aun.

La �ltima transformaci�n de coordenadas importante es la transformaci�n del Viewport. Aqu� las coordenadas 3D que han pasado por todo tipo de transformaciones 3D son finalmente proyectadas en el �rea 2D de la ventana de tu ordenador.

Las transformaciones de coordenadas se representan por matrices (matrices de dos dimensiones). Para cada uno de los anteriores tipos de transformaciones hay una matriz asociada. �stas pueden ser especificadas en cualquier momento del programa antes de dibujar la imagen. OpenGL mantiene una pila de matrices de transformaci�n que se han de aplicar sobre cada punto de la escena. Esta es una t�cnica muy eficiente y �til que exploraremos en futuros art�culos. Por el momento vayamos al c�digo fuente, donde se definen algunas de estas transformaciones. En el fichero example2.cxx encontramos las ya familiares funciones reshape:

   
void mainReshape(int w, int h){  
  
  // VIEWPORT TRANSFORMATION
   glViewport(0, 0, w, h);  

  // PROJECTION TRANSFORMATION
  glMatrixMode(GL_PROJECTION);   
  glLoadIdentity();     
  glFrustum(wnLEFT, wnRIGHT, wnBOT, wnTOP, wnNEAR, wnFAR);   

  // MODELVIEW TRANSFORMATION
  glMatrixMode(GL_MODELVIEW);   
  
  ....

La directiva glViewport(x, y, width, height) especifica la transformaci�n del Viewport: x, y son las coordenadas de la esquina inferior izquierda del rect�ngulo de la ventana de dibujo y width y height las dimensiones del viewport. Todos los n�meros se expresan en pixels.

Entonces la funci�n glMatrixMode(), utilizado para seleccionar la matriz actual, es invocada con los par�metros GL_PROJECTION para comenzar la especificaci�n de la transformaci�n de proyecci�n. Antes de especificar cualquier transformaci�n de matrices es recomendable cargar la matriz unidad (que no hace nada sobre los v�rtices de coordenadas), esto se hace con glLoadIdentity(), asigna la matriz unidad a la matriz actual. Luego viene la declaraci�n de la perspectiva 3D; la sentencia es glFrustum(left, right, bottom, top, near, far) declara los planos de recorte en las posiciones izquierda, derecha, abajo, arriba, cerca y lejos. Estos n�meros est�n especificados en coordenadas del punto de vista (eye) y su magnitud determina la forma (la perspectiva) del volumen del espacio que se va a proyectar en el viewport (pantalla del ordenador). Quiz�s parezca complicado, a m� me llevo un tiempo acostumbrarme. La mejor forma de entenderlo es experimentando con varios n�meros, recuerda siempre que has de elegir n�meros de forma que el objeto modelado-visto caiga dentro de los planos de recorte o no ver�s nada en la pantalla. Hay otras formas de especificar la transformada de proyecci�n. Con el tiempo llegaremos a verlas.

Finalmente cambiamos la matriz actual a la matriz del modelview, otra vez con la funci�n glMatrixMode() y utilizando GL_MODELVIEW como par�metro. La funci�n mainReshape() continua con otras cosas que no tienen nada que ver y acaba. Lo que importa es que despu�s de que la ventana principal ha sido re-dimensionada, esta funci�n ha especificado el viewport y la transformada de proyecci�n y finalmente establece como matriz actual, la matriz modelview.

Lo que sucede luego es que la funci�n mainDisplay() termina la especificaci�n del modelview y finalmente dibuja el pol�mero con scene():

 
void mainDisplay(){  
  glutSetWindow(winIdMain); 


  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  
      // Limpiar los buffers de color y profundidad
      // Esto es como limpiar la pizarra.
        
  // Continuar con la transformaci�n MODELVIEW: 
  // posicionar y orientar la c�mara.
  glLoadIdentity();                // Cargar matriz unidad 
  glTranslatef(0.0, 0.0, -4.0);    // Mover la c�mara 4 unidades atr�s
      // dejamos la c�mara apuntando en la direcci�n "-z". Realmente 
      // esto funciona moviendo la siguiente escena 4 pasos en el eje "-z".
      // Solo los elementos de la escena que queden dentro de el volumen
      // de representaci�n (ver transformaci�n de proyecci�n m�s adelante)
      // se ver�n en la pantalla.
  // Dibujar el pol�mero
  glScalef(0.5, 0.5, 0.5); 
  glRotatef(Angle, 0, 1, 0); 
  scene();  
 
  glutSwapBuffers(); 
}; 
 

Espero no haber confundido mucho al lector por haber utilizado dos sub-ventanas. No estoy explicando cuestiones relativas a las sub-ventanas porque ya las expliqu� en el art�culo anterior ( Gesti�n de ventanas). Si tienes alguna duda, puedes ir a este art�culo para refrescar la memoria.

Esta funci�n es muy simple. Primero glClear borra los buffers de color y profundidad. El buffer de profundidad es importante ahora en 3D porque la coordenada z de cada v�rtice ha de ser comparada para determinar las superficies ocultas y eliminarlas. Despu�s cargamos la matriz unidad en la matriz actual modelview e invocamos las tres transformaciones de modelado:

  • glTranslatef(xt, yt, zt) , esto traslada el sistema de coordenadas actuales por el vector (xt, yt, zt). En nuestro caso, tiene el efecto de mover la c�mara 4 unidades hacia atr�s a lo largo de z, esto es, alejarse del modelo. Si no hacemos esto, la c�mara se quedar�a en el origen, en medio de la escena, con lo que dif�cilmente podr�amos ver nada.
  • glScalef(xs, ys, zs) , como su nombre indica, su misi�n es la de escalar el sistema de coordenadas por los factores xs, ys, zs a lo largo de los ejes x, y, z respectivamente. Este escalado es necesario para que la escena expresada en coordenadas del mundo (pol�mero) quepa en las coordenadas del volumen visible.
  • glRotatef(angle, vx, vy, vz) , gira el sistema actual de coordenadas a lo largo del vector normalizado (vx, vy, vz) el �ngulo angle. Este es el truco que utilizamos para producir la ilusi�n de que la c�mara gira al rededor de la escena. De hecho es la escena la que est� girando. Hay muchas otras formas de mover la c�mara, pero por el momento est� es la m�s sencilla.

Unas palabras de advertencia: el orden en el que se aplican las transformaciones de modelado es muy importante. Es necesario comprender qu� es lo que sucede con la matriz de Modelview cada vez que invocas a una transformaci�n de coordenadas. Cada transformaci�n Ti se representa matem�ticamente por una matriz Mi. La superposici�n de una secuencia de transformadas Tn Tn-1... T1 (por ejemplo: translaci�n + escalado + rotaci�n ) es representado matem�ticamente por una �nica matriz M = Mn Mn-1 .... M1. El orden es crucial porque cuando la transformaci�n compuesta M act�a sobre un v�rtice v, las transformaciones son realmente aplicadas en el orden inverso:

M v = Mn Mn-1 .... M1 v

Primero M1, luego M2, etc.. y finalmente Mn. En nuestro c�digo ejemplo, he declarado la transformaci�n en el siguiente orden: translaci�n -> escalado -> rotaci�n. Por tanto, cada punto del modelo en las coordenadas del mundo va a ser rotado -> escalado -> trasladado antes de ser proyectado sobre la pantalla gr�fica.

Siempre has de tener este orden inverso de transformaciones en la cabeza cuando escribas c�digo, en caso contrario puedes obtener resultados no deseados muy sorprendentes.

La funci�n scene() sencillamente ejecuta el dibujado 3D (render) del objeto pol�mero. Para entender como se construye el modelo 3D, tenemos que ir al fichero Gd_opengl.cxx y echar un vistazo a la funci�n miembro draw(GdPolymer &p). Hay un bucle principal que pasa por cada mon�mero de la cadena del pol�mero, obtiene sus coordenadas x,y,z dibuja una esfera en esa posici�n, y luego dibuja los cilindros a lo largo de los enlaces que conectan cada mon�mero �Recuerdas la primera pregunta? Aqu� tenemos una posible soluci�n... Si encuentras otra m�s r�pida d�melo.

Hay un cosa m�s que el lector debe saber para entender completamente la rutina de dibujado del pol�mero �Para qu� sirven lasfunciones: glPushMatrix() y glPopMatrix()?

Hay s�lo dos primitivas geom�tricas en el modelo del pol�mero, una esfera de radio 0.40 centrada en el origen y un cilindro superior derecho de altura 1.0 y radio 0.4. El pol�mero se construye utilizando dos primitivas y una serie de transformaciones para situar las esferas y cilindros en las posicies correctas. Cada vez que se ejecutan las sentencias glCallList(MONOMER) o glCallList(CYLINDER) se dibuja una nueva esfera y cilindro en el origen. Para mover las esferas a las coordenadas x,y,z necesitamos una translaci�n (ver glTranslatef(x, y, z)); para dibujar y posicionar un cilindro es m�s complicado porque tenemos que orientarlo en la direcci�n adecuada (en mi algoritmo utilizo una transformaci�n de escalado->rotaci�n).

Pero cualquiera que sea el m�todo que utilices para construir modelos complejos 3D, no hay duda que necesitar�s varias transformaciones de translaci�n, rotaci�n y escalado. Cuando se invoca la funci�n scene(), la matriz actual de la m�quina de estados OpenGL es la matriz MODELVIEW, como he mencionado anteriormente, �sta es la matriz que representa la proyecci�n del modelo de coordenadas del mundo a coordenadas de recorte. �ste es un problema serio, mientras la matriz MODELVIEW sea todav�a la matriz activa, cualquier nueva transformaci�n aplicada para construir el modelo 3D se a�adir� a la matriz actual, con la consecuencia indeseable de destruir la transformaci�n MODELVIEW. De forma similar, algunas veces queremos aplicar determinadas transformaciones 3D a una parte del modelo pero no a otras (por ejemplo, escalar un cilindro pero no la esfera). OpenGL resuelve este problema utilizando una pila interna de matrices. Hay dos operaciones b�sicas sobre esta pila implementadas mediante glPushMatrix() (meter en pila) y glPopMatrix() (sacar de pila). Examina una vez m�s el c�digo fuente de scene() y observar�s que antes de dibujar la esfera de cada nom�mero llamamos una vez a "push", para mover la matriz MODELVIEW a la pila, y al final del bucle llamamos a "pop" para restaurar la matriz MODELVIEW. El bucle interno que dibuja los enlaces del pol�mero tiene sus propios "push" y "pop" para aislar las transformaciones de escalado y rotaci�n de las translaciones que afectan a ambos, esfera y cilindro.

Hay mucho m�s que decir sobre transformaciones 3D y pilas de matrices. En este art�culo s�lo hemos ara�ado la superficie de estas cuestiones. Por el momento lo dejaremos as� y dejaremos que el lector interesado explore el c�digo fuente de la demo e intente construir sus propios modelos 3D. El c�digo example2 tambi�n utiliza unas cuantas caracter�stica aun no estudiadas: materiales e iluminaci�n. Dejamos la presentaci�n de estos temas para art�culos futuros. La pr�xima vez, continuaremos explorando con mayor profundidad las transformaciones 3D y las pilas de matrices, tambi�n mostraremos como utilizar ambas caracter�sticas de OpenGL para implementar un robot m�vil. Hasta entonces, pasatelo bien con OpenGL.


Traducido por Ismael Ripoll

M�s infromaci�n:
© 1998 Miguel Angel Sep�lveda
P�ginas web mantenidas por Miguel A Sep�lveda.