miércoles, 26 de septiembre de 2012

Matrices


Matrices

Las matemáticas que hay tras las transformaciones se simplifican gracias a las matrices. Cada una de las transformaciones puede conseguirse multiplicando una matriz que contenga los vértices por una matriz que describa la transformación. Por tanto todas las transformaciones ejecutables con ogl pueden describirse como la multiplicación de dos o más matrices.

El canal de transformaciones

Para poder llevar a cabo todas las transformaciones de, deben modificarse dos matrices: la matriz del Modelador y la matriz de Proyección. OpenGL proporciona muchas funciones de alto nivel que hacen muy sencillo la construcción de matrices para transformaciones. Éstas se aplican sobre la matriz que este activa en ese instante. Para activar una de las dos matrices utilizamos la función glMatrixMode. Hay dos parámetros posibles:

glMatrixMode(GL_PROJECTION);
Activa la matriz de proyección, y
glMatrixMode(GL_MODELVIEW);

Activa la del modelador. Es necesario especificar con que matriz se trabaja, para poder aplicar las transformaciones necesarias en función de lo que se desee hacer.

El camino que va desde los datos “en bruto” de los vértices hasta las coordenadas en pantalla sigue el siguiente camino: Primero, el vértice se convierte en una matriz 1x4 en la que los tres primeros valores son las coordenadas x, y, z. El cuarto número (llamado parámetro w) es un factor de escala, que no se va a usar de momento, usando el valor 1.0. Entonces se multiplica el vértice por la matriz del modelador, para obtener las coordenadas oculares. Éstas se multiplican por la matriz de proyección para conseguir las coordenadas de trabajo. Con esto se eliminan todos los datos que están fuera del volumen de proyección. Estas coordenadas de trabajo se dividen por el parámetro w del vértice, para hacer el escalado relativo. Finalmente, las coordenadas resultantes se mapean en un plano 2D mediante la transformación de la vista.

La matriz del modelador

La matriz del modelador es una matriz 4x4 que representa el sistema de coordenadas transformado que se está usando para colocar y orientar los objetos. Si se multiplica la matriz del vértice (de tamaño 1x4) por ésta se obtiene otra matriz 1x4 con los vértices transformados sobre ese sistema de coordenadas.

OpenGL proporciona funciones de alto nivel para conseguir matrices de translación, rotación y escalado, y además la multiplican por la matriz activa en ese instante, de manera que no hay que preocuparse por ello en absoluto.

Traslación

Imaginemos que se quiere dibujar un cubo con la función de la librería GLUT glutSolidCube, que lleva como parámetro el lado del cubo. Si se escribe el siguiente código:

glutSolidCube(5);

Se obtiene un cubo centrado en el origen (0,0,0) y con el lado de la arista 5. Ahora se quiere mover 10 unidades hacia la derecha (es decir, 10 unidades en el sentido positivo del eje de las x). Para ello se tendría que construir una matriz de transformación y multiplicarla por la matriz del modelador. Ogl nos ofrece la función glTranslate, que crea la matriz de transformación y la multiplica por la matriz que está activa en ese instante (en este caso debería ser la del modelador, GL_MODELVIEW). Entonces el código quedaría de la siguiente manera:

glTranslatef(10.0f, 0.0f, 0.0f);
glutSolidCube(5);

La “f” añadida a la función indica que se usarán flotantes. Los parámetros de glTranslate son las unidades a desplazar en el eje x, y y z, respectivamente. Pueden ser valores negativos, para trasladar en el sentido contrario.

Rotación

Para rotar, tenemos también una función de alto nivel que construye la matriz de transformación y la multiplica por la matriz activa, glRotate. Lleva como parámetros el ángulo a rotar (en grados, sentido horario), y después x, y y z del vector sobre el cual se quiere rotar el objeto. Una rotación simple, sobre el eje y, de 10° sería:
glRotatef(10, 0.0f, 1.0f, 0.0f);

Escalado

Una transformación de escala incrementa el tamaño de nuestro objeto expandiendo todos los vértices a lo largo de los tres ejes por los factores especificados. La función glScale lleva como parámetros la escala en x, y y z, respectivamente. El valor 1.0f es la referencia de la escala, de tal forma que:

glScalef(1.0f, 1.0f, 1.0f);

No modificaría el objeto en absoluto. Un valor de 2.0f sería el doble, y 0.5f sería la mitad. Por ejemplo, para ensanchar un objeto a lo largo de su eje z, de tal forma que quedase cuatro veces más “alargado” en este eje, sería:
glScalef(1.0f, 1.0f, 4.0f);

La matriz identidad

El “problema” del uso de estas funciones surge cuando se tiene más de un objeto en la escena. Estas funciones tienen efectos acumulativos. Es decir, si se quiere preparar una escena como la de la ilustración:

Con una esfera (de radio 3) centrada en (0, 10, 0) y otra centrada en (10, 0, 0), y se escribe el siguiente código (que es incorrecto):

glTranslatef(0.0f, 10.0f, 0.0f);
glutSolidSphere(3.0f, 8, 8);
glTranslate(10.0f, 0.0f, 0.0f);
glutSolidsphere(3.0f, 8, 8);

En este código, se dibuja primero una esfera en (0, 10, 0) como se quiere. Pero después, se está multiplicando la matriz del modelador (que ya estaba transformada para dibujar la primera esfera) por otra matriz de transformación que desplaza 10 unidades hacia la derecha. Por ello la segunda matriz se dibujaría, en (10, 10 ,0), y no en (10, 0, 0), como se pretendía.

Para solventar este problema se debe reiniciar la matriz del modelador a un estado conocido, en este caso, centrada en el origen de nuestro sistema de coordenadas oculares. Para ello se carga en la matriz del modelador la matriz identidad (una matriz 4x4 llena de ceros excepto en la diagonal, que contiene unos). Esto se consigue gracias a la función glLoadIdentity, que no lleva parámetros. Simplemente carga la matriz identidad en la matriz activa en ese instante. El código correcto para el ejemplo anterior quedaría de la siguiente manera:

glTranslatef(0.0f, 10.0f, 0.0f);
glutSolidSphere(3.0f, 8, 8);
glLoadIdentity();
glTranslate(10.0f, 0.0f, 0.0f);
glutSolidsphere(3.0f, 8, 8);  


La matriz de proyección

La matriz de proyección especifica el tamaño y la forma del volumen de visualización. El volumen de visualización es aquel cuyo contenido es el que se representa en pantalla. Está delimitado por una serie de planos de trabajo. De estos planos, los más importantes son los planos de corte, que son los que nos acotan el volumen de visualización por delante y por detrás. En el plano más cercano a la cámara (znear) es donde se proyecta la escena para luego pasarla a la pantalla. Todo lo que está más adelante del plano de corte más alejado de la cámara (zfar) no se representa.

Proyecciones ortográficas

Una proyección ortográfica es cuadrada en todas sus caras. Esto produce una proyección paralela, útil para aplicaciones de tipo CAD o dibujos arquitectónicos, o también para tomar medidas, ya que las dimensiones de lo que representan no se ven alteradas por la proyección.

Una aproximación menos técnica pero más comprensible de esta proyección es imaginar que se tiene un objeto fabricado con un material deformable, y se aplasta literalmente como una pared. Se obtendría el mismo objeto, pero plano, liso. Pues eso es lo que se vería por pantalla.

Para definir la matriz de proyección ortográfica y multiplicarla por la matriz activa (que debería ser en ese momento la de proyección, GL_PROJECTION), se utiliza la función glOrtho, que se define de la siguiente forma:

glOrtho(limiteIzquierdo, limiteDerecho, limiteAbajo, limiteArriba, znear, zfar)

Siendo todos flotantes. Los valores de znear y zfar no son las posiciones de esos planos en el espacio 3D. Representan la distancia desde el centro de proyección, con valor positivo hacia delante y negativo hacia atrás. Con esto simplemente se acota  lo que será¡ el volumen de visualización (un cubo).

Por ejemplo, la ilustración es un render de un coche con proyección ortográfica, visto desde delante.

El código utilizado para esta proyección ha sido:

glOrtho(-0.5f, 0.5f, -0.5f, 0.5f, 0.01f, 20.0f);

Proyecciones perspectivas

Una proyección en perspectiva reduce y estira los objetos más alejados del observador. Es importante saber que las medidas de la proyección de un objeto no tienen por qué coincidir con las del objeto real, ya que han sido deformadas.

El volumen de visualización creado por una perspectiva se llama frustum. Un frustum es una sección piramidal, vista desde la parte afilada hasta la base.

Se puede definir esta proyección utilizando la función glFrustum. Pero existe otra función de la librería GLU llamada gluPerspective que hace el proceso más sencillo. Se define de la siguiente forma:

Void gluPerspective(angulo, aspecto, znear, zfar);

Los parámetros de gluPerspective son flotantes y definen las características mostradas en la ilustración, el ángulo para el campo de visión en sentido vertical, el aspecto que es la relación entre la altura (h) y la anchura (w) y las distancias znear y zfar de los planos que acotan el fustrum al observador. Los valores de znear y zfar no son las posiciones de esos planos en el espacio 3D, representan la distancia desde el centro de proyección, con valor positivo hacia delante y negativo hacia atrás.
La ilustración muestra la escena del coche de la sección anterior, esta vez con una proyección en perspectiva:

El código utilizado para definir la proyección ha sido:

gluPerspective(45.0f,(GLfloat)(width/height),0.01f,100.0f);

Se usan 45° de ángulo, la relación entre el ancho y alto de la pantalla (width y height son el ancho y alto actual de la ventana) y las distancias a los planos de corte znear y zfar son 0.01 y 100 respectivamente.

No hay comentarios:

Publicar un comentario