Capítulo 2: "Hello world"
OPEN GL
2.1 La OpenGL Utility Toolkit (GLUT)
la librería glut está diseñada para no tener preocupaciones con respecto al sistema de ventanas, incluyendo funciones del tipo abre_ventana(), que nos ocultan la complejidad de librerías a más bajo nivel como la GLX.
GLUT nos ofrece toda una dinámica de programación de aplicaciones ogl, gracias a la definición de funciones callback.
Programa
#include <GL/glut.h>
void reshape(int width, int height)
{
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-1, 1, -1, 1, -1, 1);
glMatrixMode(GL_MODELVIEW);
}
void display()
{
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(1,1,1);
glLoadIdentity();
glBegin(GL_TRIANGLES);
glVertex3f(-1,-1,0);
glVertex3f(1,-1,0);
glVertex3f(0,1,0);
glEnd();
glFlush();
}
void init()
{
glClearColor(0,0,0,0);
}
int main(int argc, char **argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glutInitWindowPosition(50, 50);
glutInitWindowSize(500, 500);
glutCreateWindow("Hello OpenGL");
init();
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutMainLoop();
return 0;
}
Resultado 2.1
2.3 Refresco de imagen
Es muy importante dentro de los gráficos en computación la capacidad de conseguir movimiento a partir de una secuencia de fotogramas o “frames”. La animación es fundamental para un simulador de vuelo, una aplicación de mecánica industrial o un juego.
En el momento en que el ojo humano percibe mas de 24 frames en un segundo, el cerebro lo interpreta como movimiento real. En este momento, cualquier proyector convencional es capaz de alcanzar frecuencias de 60 frames por segundo (fps) o más. Evidentemente, a 60 fps el movimiento será mas suave y, por tanto, mas real, que a 30 fps.
La clave para que la animación funcione es que cada frame esté completo (haya terminado de renderizar) cuando tenga que ser mostrado por pantalla. Supongamos que nuestro algoritmo (en pseudocódigo) para la animación es el siguiente:
abre_ventana();
for (i=0; i< ultimo_frame; I++) {
borra_ventana();
dibuja_frame(i);
espera_hasta_la_1/24_parte_de_segundo();
}
Si el borrado de pantalla y el dibujado del frame tardan más de 1/24 de segundo, antes de que se pueda dibujar el siguiente, el borrado ya se habrá producido. Esto causaría un parpadeo en la pantalla puesto que no hay una sincronización en los tiempos.
Cuando acaba el rederizado del segundo, se dibuja en pantalla y se vuelve a pintar en el primero. Así nunca veremos una imagen cortada, solventando el problema del parpadeo. El algoritmo quedaría:
abre_ventana();
for (i=0; i< ultimo_frame; I++) {
borra_ventana();
dibuja_frame(i);
swap_buffers();
}
2.3.1 Inclusión de doble buffer
En el ejemplo anterior, prueba a modificar el tamaño de la ventana pinchando en el borde y arrastrando. Como puedes apreciar el triángulo parpadea.
En ogl se pueden utilizar dos buffers como se acaba de comentar. Para ello, modifica primero la función glutInitDisplayMode como sigue:
int main(int argc, char **argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
Capítulo 3: Dibujando en 3D
El dibujo 3D en OpenGL se basa en la composición de pequeños elementos, con los que se va construyendo la escena deseada.
La Ilustración 3.1 muestra un eje de coordenadas inmerso en un volumen de visualización sencillo, que se utilizará para definir y explicar el espacio en el que se va a trabajar.
Los vértices (puntos 3D) son el denominador común en cualquiera de las primitivas de OpenGL.
Una primitiva es simplemente la interpretación de un conjunto de vértices dibujados de una manera específica en pantalla.
3.3.1 Dibujo de puntos (GL_POINTS)
Es la más simple de las primitivas de ogl. Para comenzar, veamos el siguiente código:
glBegin(GL_POINTS);
glVertex3f(0.0f, 0.0f, 0.0f);
glVertex3f(10.0f, 10.0f, 10.0f);
glEnd();
El parámetro pasado a glBegin es GL_POINTS, con lo cual interpreta los vértices contenidos en el bloque glBeing-glEnd como puntos.
glBegin(GL_POINTS);
glVertex3f(0.0f, 0.0f, 0.0f);
glEnd();
glBegin(GL_POINTS);
glVertex3f(10.0f, 10.0f, 10.0f);
glEnd();
3.3.2 Dibujo de líneas (GL_LINES)
En el dibujado de puntos, la sintaxis era muy cómoda: cada vértice es un punto. En las líneas, los vértices se cuentan por parejas, denotando punto inicial y punto final de la línea. Si se especifica un número impar de vértices, el último de ellos se ignora.
El siguiente código dibuja una serie de líneas radiales:
GLfloat angulo; int i; glBegin(GL_LINES); for (i=0; i<360; i+=3) { angulo = (GLfloat)i*3.14159f/180.0f; // grados a radianes glVertex3f(0.0f, 0.0f, 0.0f); glVertex3f(cos(angulo), sin(angulo), 0.0f); } glEnd();
3.3.3 Dibujo de polígonos
En la creación de objetos sólidos, el uso de puntos y líneas es insuficiente.
-las primitivas GL_TRIANGLES y GL_QUADS.
3.3.3.1 Triángulos (GL_TRIANGLES)
El polígono más simple, es el triángulo, con sólo tres lados. En esta primitiva, los vértices van de tres en tres.
glBegin(GL_TRIANGLES);
glVertex3f(0.0f, 0.0f, 0.0f); // V0
glVertex3f(1.0f, 1.0f, 0.0f); // V1
glVertex3f(2.0f, 0.0f, 0.0f); // V2
glVertex3f(-1.0f, 0.0f, 0.0f); // V3
glVertex3f(-3.0f, 2.0f, 0.0f); // V4
glVertex3f(-2.0f, 0.0f, 0.0f); // V5
glEnd();
3.3.3.2 Cuadrados (GL_QUADS)
Esta primitiva funciona exactamente igual que GL_TRIANGLES, pero dibujando cuadrados. También tiene la variación de GL_QUAD_STRIP, para dibujar “tiras” de cuadrados.
Componer un objeto sólido a partir de polígonos implica algo más que ensamblar vértices en un espacio coordenado 3D.
3.4.1 Color de relleno
Para elegir el color de los polígonos, basta con hacer una llamada a glColor entre la definición de cada polígono.
3.4.2 Modelo de sombreado
Es el método que utiliza OpenGL para rellenar de color los polígonos. Se especifica con la función glShadeModel.
Y A COLORES
3.4.3 Eliminación de las caras ocultas
Cuando se tiene un objeto sólido, o quizá varios objetos, algunos de ellos estarán más próximos a nosotros que otros.
glColor3f(1.0f, 1.0f, 1.0f); // activamos el color blanco glBegin(GL_TRIANGLES); glVertex3f(-1.0f, -1.0f, -1.0f); glVertex3f(1.0f, -1.0f, -1.0f); glVertex3f(0.0f, 1.0f, -1.0f); glEnd();
glColor3f(1.0f, 0.0f, 0.0f); // activamos el color rojo
glBegin(GL_POINTS);
glVertex3f(0.0f, 0.0f, -2.0f);
glVertex3f(2.0f, 1.0f, -2.0f);
glEnd();
Proyección: Trasforman una escena 3d “abstracta”, en una imagen plana que puede ser visualizada en una pantalla.
Viewport: Ajustan el producto de la proyección a las dimensiones de un rectángulo contenedor (ventana).
De vista: Que definen y afectan la posición desde la cual se visualizan las escenas tridimensionales.
Modelado: Sirven para manipular los objetos en la escena, permitiendo trasladarlos, rotarlos y deformarlos (escalarlos).
Modelo-Vista: Son la combinación de las dos transformaciones anteriores, que desde un punto de vista práctico son semejantes.
Void glMatrixMode( enum mode ); Permite seleccionar la matriz sobre la cual se realizaran las operaciones, los posibles valores de mode son TEXTURE, MODELVIEW, COLOR o PROJECTION .
Void glLoadMatrix{fd} (T m[16]); Recibe una matriz de 4×4 que reemplaza la actual seleccionada. El arreglo es ordenado en forma de una matriz que tiene orden Y, a diferencia de las matrices convencionales que tienen orden X.
Void glMultMatrix{fd}( T m[16] ); Multiplica la matriz actual por la matriz m[16] y reemplaza la matriz actual con el resultado de la operación. La operación resultante sería algo así como A’ = AM, donde A es la matriz actual, M es la matriz suministrada y A’ es la nueva matriz que resulta de la operación y que reemplaza a A.
Void glLoadTransposeMatrix{fd}( T m[16] ); Realiza una función similar a LoadMatrix(), con la diferencia que trabaja sobre una matriz en orden X.
Void glMultTransposeMatrix{fd}( T m[16] ); Misma funcionalidad que MultMatrix() , solo que actúa en base al la matriz en orden X, o sea la transpuesta.
Void glLoadIdentity( void ); Remplaza la matriz actual por la matriz identidad de tamaño 4×4.
Void glGetFloatv(enum value, float *data); Permite obtener una copia de aquello
Ortográfica: Para ajustar la proyección ortográfica se utiliza el siguiente grupo de funciones:
glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far);
Esta función permite controlar los parámetros del volumen de vista izquierdo, derecho, abajo, arriba, cerca y lejos.
Perspectiva: Existen dos manera de manejar la proyección en perspectiva, a través de una función gl o mediante la librería glu (una tercera puede ser realizar los cálculos de la matriz “manualmente”. En el primer caso:
glFrustrum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far)
Transformaciones ModelView
Una tarea muy común en la creación de gráficos 2d, 3d y videojuegos es la de mover objetos par crear cierta animación. La primera idea que se nos viene a la cabeza en el caso de OpenGL es que todo modelo está formado por primitivas, toda primitiva por puntos y finalmente todo punto por una tripleta de coordenadas XYZ, así que si se cambian las coordenadas todo de ahí hacia arriba se mueve.
void glRotate[fd](GLfloat angle, GLfloat x, GLfloat y, GLfloat z);
Realiza una rotación del espacio coordenado por una medida de (angle) tomados en grados a lo largo del vector determinado por (x,y,z). Es recomendable que (x,y,z) representen un vector normalizado (o sea magnitud(x,y,z) = 1), debido a que si no es así OpenGL intentará normalizarlo. La rotación se lleva siguiendo la regla de la mano derecha, teniendo en cuenta que el vector (x,y,z) apunta con el “pulgar” hacia adentro (hacia uno).
void glTranslate[fd](GLfloat x, GLfloat y, GLfloat z);
Esta función traslada el espacio coordenado en x, y, z unidades sobre sus respectivos ejes coordenados X, Y, Z.
void glScalef(GLfloat x, GLfloat y, GLfloat z);
Cambia la proporción de los objetos respecto a los ejes coordenados, lo que es equivalente a decir que los estira o encoge una cantidad determinada por los parámetros de la función x,y,z.
Coordenadas oculares:
Las coordenadas oculares se sitúan en el punto de vista del observador, sin importar las transformaciones que tengan lugar. Por tanto, estas coordenadas representan un sistema virtual de coordenadas fijo usado como marco de referencia común.
Transformaciones:
Las transformaciones son las que hacen posible la proyección de coordenadas 3D sobre superficies 2D. También son las encargadas de mover, rotar y escalar objetos.
Transformaciones del observador
Es la primera que se aplica a la escena, y se usa para determinar el punto más ventajoso de la escena. Por defecto, el punto de vista está en el origen (0,0,0) mirando en dirección negativa del eje z. La transformación del observador permite colocar y apuntar la cámara donde y hacia donde se quiera. Todas las transformaciones posteriores tienen lugar basadas en el nuevo sistema de coordenadas modificado.
Transformaciones del modelo
Estas transformaciones se usan para situar, rotar y escalar los objetos de la escena. La apariencia final de los objetos depende en gran medida del orden con el que se hayan aplicado las transformaciones.
Transformaciones de proyección
La transformación de proyección se aplica a la orientación final del modelador. Esta proyección define el volumen de visualización y establece los planos de trabajo. A efectos prácticos, esta translación específica cómo se traslada una escena finalizada a la imagen final de la pantalla.
Tranformaciones de vista
En el momento en que se ha terminado todo el proceso de transformaciones, solo queda un último paso: proyectar lo que hemos dibujado en 3D al 2D de la pantalla, en la ventana en la que estamos trabajando.
Matrices
Las matemáticas que hay tras estas transformaciones se simplifican gracias a las matrices. Cada una de las transformaciones de las que se acaba de hablar puede conseguirse multiplicando una matriz que contenga los vértices por una matriz que describa la transformación.
El canal de transformaciones
Para poder llevar a cabo todas las transformaciones de las que se acaban de hablar, 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.
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.
Translació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.
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.
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.
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.
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.
Proyecciones perspectivas
Una proyección en perspectiva reduce y estirar 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.
Viewport: Ajustan el producto de la proyección a las dimensiones de un rectángulo contenedor (ventana).
De vista: Que definen y afectan la posición desde la cual se visualizan las escenas tridimensionales.
Modelado: Sirven para manipular los objetos en la escena, permitiendo trasladarlos, rotarlos y deformarlos (escalarlos).
Modelo-Vista: Son la combinación de las dos transformaciones anteriores, que desde un punto de vista práctico son semejantes.
Void glMatrixMode( enum mode ); Permite seleccionar la matriz sobre la cual se realizaran las operaciones, los posibles valores de mode son TEXTURE, MODELVIEW, COLOR o PROJECTION .
Void glLoadMatrix{fd} (T m[16]); Recibe una matriz de 4×4 que reemplaza la actual seleccionada. El arreglo es ordenado en forma de una matriz que tiene orden Y, a diferencia de las matrices convencionales que tienen orden X.
Void glMultMatrix{fd}( T m[16] ); Multiplica la matriz actual por la matriz m[16] y reemplaza la matriz actual con el resultado de la operación. La operación resultante sería algo así como A’ = AM, donde A es la matriz actual, M es la matriz suministrada y A’ es la nueva matriz que resulta de la operación y que reemplaza a A.
Void glLoadTransposeMatrix{fd}( T m[16] ); Realiza una función similar a LoadMatrix(), con la diferencia que trabaja sobre una matriz en orden X.
Void glMultTransposeMatrix{fd}( T m[16] ); Misma funcionalidad que MultMatrix() , solo que actúa en base al la matriz en orden X, o sea la transpuesta.
Void glLoadIdentity( void ); Remplaza la matriz actual por la matriz identidad de tamaño 4×4.
Void glGetFloatv(enum value, float *data); Permite obtener una copia de aquello
Ortográfica: Para ajustar la proyección ortográfica se utiliza el siguiente grupo de funciones:
glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far);
Esta función permite controlar los parámetros del volumen de vista izquierdo, derecho, abajo, arriba, cerca y lejos.
Perspectiva: Existen dos manera de manejar la proyección en perspectiva, a través de una función gl o mediante la librería glu (una tercera puede ser realizar los cálculos de la matriz “manualmente”. En el primer caso:
glFrustrum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far)
Transformaciones ModelView
Una tarea muy común en la creación de gráficos 2d, 3d y videojuegos es la de mover objetos par crear cierta animación. La primera idea que se nos viene a la cabeza en el caso de OpenGL es que todo modelo está formado por primitivas, toda primitiva por puntos y finalmente todo punto por una tripleta de coordenadas XYZ, así que si se cambian las coordenadas todo de ahí hacia arriba se mueve.
void glRotate[fd](GLfloat angle, GLfloat x, GLfloat y, GLfloat z);
Realiza una rotación del espacio coordenado por una medida de (angle) tomados en grados a lo largo del vector determinado por (x,y,z). Es recomendable que (x,y,z) representen un vector normalizado (o sea magnitud(x,y,z) = 1), debido a que si no es así OpenGL intentará normalizarlo. La rotación se lleva siguiendo la regla de la mano derecha, teniendo en cuenta que el vector (x,y,z) apunta con el “pulgar” hacia adentro (hacia uno).
void glTranslate[fd](GLfloat x, GLfloat y, GLfloat z);
Esta función traslada el espacio coordenado en x, y, z unidades sobre sus respectivos ejes coordenados X, Y, Z.
void glScalef(GLfloat x, GLfloat y, GLfloat z);
Cambia la proporción de los objetos respecto a los ejes coordenados, lo que es equivalente a decir que los estira o encoge una cantidad determinada por los parámetros de la función x,y,z.
Coordenadas oculares:
Las coordenadas oculares se sitúan en el punto de vista del observador, sin importar las transformaciones que tengan lugar. Por tanto, estas coordenadas representan un sistema virtual de coordenadas fijo usado como marco de referencia común.
Transformaciones:
Las transformaciones son las que hacen posible la proyección de coordenadas 3D sobre superficies 2D. También son las encargadas de mover, rotar y escalar objetos.
Transformaciones del observador
Es la primera que se aplica a la escena, y se usa para determinar el punto más ventajoso de la escena. Por defecto, el punto de vista está en el origen (0,0,0) mirando en dirección negativa del eje z. La transformación del observador permite colocar y apuntar la cámara donde y hacia donde se quiera. Todas las transformaciones posteriores tienen lugar basadas en el nuevo sistema de coordenadas modificado.
Transformaciones del modelo
Estas transformaciones se usan para situar, rotar y escalar los objetos de la escena. La apariencia final de los objetos depende en gran medida del orden con el que se hayan aplicado las transformaciones.
Transformaciones de proyección
La transformación de proyección se aplica a la orientación final del modelador. Esta proyección define el volumen de visualización y establece los planos de trabajo. A efectos prácticos, esta translación específica cómo se traslada una escena finalizada a la imagen final de la pantalla.
Tranformaciones de vista
En el momento en que se ha terminado todo el proceso de transformaciones, solo queda un último paso: proyectar lo que hemos dibujado en 3D al 2D de la pantalla, en la ventana en la que estamos trabajando.
Matrices
Las matemáticas que hay tras estas transformaciones se simplifican gracias a las matrices. Cada una de las transformaciones de las que se acaba de hablar puede conseguirse multiplicando una matriz que contenga los vértices por una matriz que describa la transformación.
El canal de transformaciones
Para poder llevar a cabo todas las transformaciones de las que se acaban de hablar, 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.
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.
Translació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.
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.
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.
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.
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.
Proyecciones perspectivas
Una proyección en perspectiva reduce y estirar 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.