Transformaciones Geométricas en OpenGL
Una vez que hemos aprendido a utilizar las primitivas de OpenGL el siguiente paso será aprender a utilizar las transformaciones geométricas que nos permitirán modificar la posición y orientación de los obejtos.
No hace falta ser un genio de las matemáticas para poder hacer uso de las funciones que OpenGL nos ofrece para realizar transformaciones geométricas, del mismo modo que podemos conducir un coche sin conocer todos los mecanismos internos. Lo que si necesitamos, es conocer los aspectos básicos que nos permitiran conocer qué cosas se pueden hacer y qué herramientas serán las mejores para conseguir nuestros propósitos.
Cuando hablamos de gráficos 3D realmente no estamos hablando de objetos en 3D, sino que estamos tratando conceptos 3D para describir cómo un objeto 3D puede representarse en un monitor 2D. A este proceso se le denomina “proyección“.
Por tanto los objetos 3D se proyectan en el plano, pero hay varias formas de “ver” esta proyeccion, la proyección en perspectiva y ortogonal. En la primera los objetos más lejanos se verían mas pequeños, que es lo que en realidad pasa, mientras que en la segunda todos los objetos se ven a la misma distancia y tienen el mismo tamaño.
La proyección es una de las transformaciones que nos permite utilizar OpenGL pero existen muchas más. Las transformaciones nos permitirán por ejemplo rotar objetos, desplazarlos o escalarlos.
Ejemplo de rotaciones de objetos sobre los ejes de coordenadas X,Y y Z.
Ejemplo de la traslación de un objeto.
Es muy importante hacer incapié en que las transformaciones geométricas se aplican antes de que un objeto sea visualizado. Veamos algunos códigos de ejemplo OpenGL.
Traslación:
Imaginemos que queremos pintar un cubo de 10 unidades y que queremos desplazarlo 10 unidades sobre el eje X. El código sería:
// Nos trasladamos 10 unidades sobre el eje X glTranslatef(10.0f, 0.0f, 0.0f); //Pintamos el cubo glutWireCube(10.0f); |
Rotación:
Para rotar un objeto sobre uno de los 3 ejes de coordenadas, o sobre cualquier otro vector definido V (x,y,z), OpenGL nos permite utilizar la función:
glRotatef (GLfloat angulo, GLfloat x, GLfloat y, GLfloat z); |
El ángulo de rotación es siempre un ángulo en sentido en contra de las agujas del reloj y medido en grados. Si por ejemplo quisieramos rotar 45 grados nuestro cubo sobre el eje x el código sería el siguiente:
// Realizamos la rotación glRotatef(45.0f, 1.0f, 0.0f, 0.0f); //Pintamos el cubo glutWireCube(10.0f); |
Escalado:
El escalado es una transformación que permite cambiar el tamaño de un objeto expandiendo o contrayendo todos sus vértices. La función que nos permite realizar el escalado en OpenGL es la siguiente:
glScalef (GLfloat x, GLfloat y, GLfloat z); |
El escalado no tiene porqué ser uniforme, y podemos expandir por tanto un objeto más en anchura que en altura. Un ejemplo que ensancha el tamaño del cubo al doble en el eje X:
// Realizamos el escalado glScalef( 2.0f, 1.0f, 1.0f ); //Pintamos el cubo glutWireCube(10.0f); |
Cuando se trata de aplicar una sola transformación no hay ningún problema con las funciones anteriormente comentadas, pero en el caso de que queramos realizar varias transformaciones a un objeto tendremos que entender mejor cómo las gestiona y lleva a cabo OpenGL. La idea principal es que OpenGL utiliza una pila (LIFO) para almacenar las transformaciones.
En la imagen anterior vemos la diferencia entre ejecutar una traslación primeramente y un escalado en segundo lugar, y ejecutar primero un escalado y luego una traslación:
//primer ejemplo glScalef( 2.0f, 2.0f, 2.0f); glTranslatef( 10.0f, 0.0f, 0.0f); glutWireCube(4.0f); |
//segundo ejemplo glTranslatef( 10.0f, 0.0f, 0.0f); glScalef( 2.0f, 2.0f, 2.0f); glutWireCube(4.0f); |
En el primer caso, la primera transformación que se ejecuta es la Traslación y posteriormente el Escalado, aunque en el código lo veamos al revés. ¿Cuál es la razón? La razón está en el comportamiento de la pila. Al ejecutar secuencialmente el código la primera transformación es el Escalado, que es la primera en entrar en la pila, mientras que la Traslación entra después. La traslación está encima del Escalado y por eso se ejecutará antes.
OpenGL tiene una pila para las transformaciones geométricas y de la cámara llamada GL_MODELVIEW, y otra para las proyecciones denominada GL_PROYECTION. Para indicar sobre qué pila estamos trabajando se utiliza la función glMatrixMode(Nombre_Pila).
Si quisieramos realizar diversas transformes a diferentes objetos la situación se nos complicaría ya que las transformaciones se irían acumulando en la pila. Si se quieren aplicar distintas transformaciones a distintos objetos sería necesario poder modificar el contenido de la pila. OpenGL nos ofrece 3 funciones para manejar las pilas: glLoadIdentity(), glPushMatrix() y glPopMatrix().
La función glLoadIdentity sustituye el contenido de la pila por la matriz de identidad.
La función glPushMatrix() realiza una copia de la matriz superior y la pone encima de la pila, de tal forma que las dos matrices superiores son iguales. De esta forma al llamara a la función glPushMatrix() se duplica la matriz superior y por tanto las siguientes transformaciones que se realizan se aplicarán sólo a la matriz superior de la pila, quedando la anterior con los valores que tenía en el momento de llamar a la función glPushMatrix().
La función glPopMatrix() elimina la matriz superior, quedando en la parte superior de la pila la matriz que estaba en el momento de llamar a la función glPushMatrix().