| IntroducciónEste es el primer artículo de una serie sobre OpenGL, un estándar 
    de la industria para gráficos 2D/3D (ver también 
    Qué es OpenGL).
    Supondremos que el lector está familiarizado con su plataforma de desarrollo
    en C, y que tiene ciertos conocimientos con la librería GLUT (sinó, 
    puedes seguir
    la serie de artículos "Programando GLUT" en este magazine). Bajo Linux, 
    recomendamos el uso de la librería Mesa, que es una maravillosa 
    implementación freeware de OpenGL. Actualmente existe incluso soporte hardware
    para Mesa (ver Tarjetas gráficas 3Dfx). 
     La presenación de cada nuevo comando de OpenGL vendrá acompañada de 
    un ejemplo que trata de utilizar su funcionalidad, ¡por lo menos lo intentaremos!.
    Hacia el final de nuestra serie, os mostraremos el código fuente de un juego
    de simulación escrito completamente en OpenGL. 
     Antes de empezar, me gustaría mencionar que, como científico, la
    mayor parte de mi experiencia con OpenGL proviene de utilizarlo como herramienta
    para escribir simulaciones de sistemas clásicos y reales. Así pues,
    todos mis ejemplos van sobre eso ;-). Espero que los lectores encuentren estos 
    ejemplos accesibles o, al menos, divertidos. Si te gustaría ver otro tipo
    de ejemplos, házmelo saber. 
     OpenGL se asocia a menudo con gráficos 3D, fantásticos efectos 
    especiales, modelos complejos con modelado realístico de luces, etc. Sin
    embargo, también es una máquina de trazado de gráficos 2D.
    Esto es importante porque hay muchas cosas que puedes aprender a hacer en 2D
    antes de aprender las complejidades de las perspectivas 3D, el trazado de modelos,
    luces, posiciones de cámaras, etc. Un gran número de aplicaciones
    de ingeniería y ciencias se pueden trazar en 2D. Así pues, aprendamos
    primero cómo hacer sencillas animaciones en 2D. Dibujando PuntosOpenGL tiene únicamente unas pocas primitivas geométricas: 
    puntos, líneas, polígonos. Todas ellas se describen en 
    términos de sus respectivos vértices. Un vértice está 
    caracterizado por 2 o 3 números en como flotante, las coordenadas 
    cartesianas del vértice,  (x, y) en 2D y (x, y, z) en 3D. Aunque
    las coordenadas cartesianas son las más comunes, en gráficos
    por ordenador también existe el sistema coordenado homogéneo
    en el que cada punto se describe con 4 números en coma flotante (x, y, z, w).
    Volveremos a él después de ver algunas nociones elementales de 
    trazado en 3D. Como en OpenGL todos los objetos geométricos son finalmente descritos
    como un conjunto ordenado de vértices, existe una familia de rutinas para
    declarar un vértice en OpenGL, su sintaxis es:void glVertex{234}{sifd}[v](TYPE coords); Familiarízate con esta notación. Las llaves indican parte del 
    nombre de la rutina, las rutinas pueden tomar 2, 3 o 4 parámetros de tipo
    short, long, float o double. Opcionalmente, estos parámetros se pueden
    proporcionar en forma de vector, en este caso deberemos usar la rutinas del tipo
    v. Aquí hay algunos ejemplos:void glVertex2s(1, 3); void glVertex2i(23L, 43L);
 void glVertex3f(1.0F, 1.0F, 5.87220972F);
 
 float vector[3];
 void glVertex3fv(vector);
 
 Para simplificar nos referiremos a estas rutinas como glVertex*. OpenGL interpreta cualquier secuencia de vértices segú:n su
    contexto. El contexto se declara mediante el par de rutinas 
    glBegin(GLenum mode) y glEnd(),
    toda sentencia glVertex* ejecutada entre estas dos se interpreta 
    según el valor de mode, por ejemplo:glBegin(GL_POINTS); glVertex2f(0.0, 0.0);
 glVertex2f(1.0, 0.0);
 glVertex2f(0.0, 1.0);
 glVertex2f(1.0, 1.0);
 glVertex2f(0.5, 0.5);
 glEnd();
 
 dibuja 5 puntos en 2D con las coordenadas especificadas. GL_POINTS es una de las
    etiquetas definidas en el fichero cabecera de OpenGL <GL/gl.h>, 
    existen muchos otros modos disponibles, pero los veremos cuando sea necesario.
 Cada punto se dibuja con el color actualmente guardado en la variable de estado
    de OpenGL asociada con el buffer de color. Para cambiar el color actual, usaremos
    la familia de rutinas glColor*; hay mucho que decir sobre la 
    selección y manipulación de colores, habrá un artículo solo
    para esto. De momento podemos utilizar tres números en coma flotante entre
    0.0 y 1.0. Es el codificado RGB (rojo-verde-azul)glColor3f(1.0, 1.0, 1.0);      /* Blanco  */ glColor3f(1.0, 0.0, 0.0);      /* Rojo    */
 glColor3f(1.0, 1.0, 0.0);      /* Magenta */
 etc...
    
    Descarga:
 ../../common/January1998/Makefile
 ../../common/January1998/../../common/January1998/example1.c
 ../../common/January1998/../../common/January1998/example2.c
 
 Ya tenemos suficiente material para escribir nuestros dos primeros 
    ejemplos de código. El primer ejemplo es un simple programa en OpenGL que
    dibuja un número de órbitas de una transformación
    caótica (la transformación
    estandar). Si el lector no está familiarizado con transformaciones
    y con la transformación estandar en particular, no importa. Dicho 
    sencillamente, la transformación toma un punto y genera uno nuevo usando 
    una fórmula definida como:
 yn+1 = yn + K sin(xn)
 xn+1 = xn + yn+1
 
 en el caso de la transformación estandar, representa un modelo de la traza
    dejada por una partícula cargada que gira alrededor del toro de un acelerador
    de partículas y cruza una sección plana del acelerador. Estudiar las 
    propiedades de esta y otras transformaciones es importante en física porque
    nos ayuda a entender la estabilidad de las partículas cargadas confinadas en 
    el ciclotrón. La transformación estandar está muy bien porque, 
    para algunos valores de su parámetro K, muestra claramente una mezcla de 
    movimiento caótico y movimiento orbital. Incluso aquellos que no están
    interesados en la física pero quieren desarrollar código para 
    gráficos deberán prestar atención a las transformaciones
    y sus propiedades, muchos de los algortimos para generar texturas, 
    llamas de fuegos, árboles, tierra, etc... se basan en transformaciones 
    fractales.
  Este es el código de ../../common/January1998/../../common/January1998/example1.c: 
    
#include <GL/glut.h>
#include <math.h>  
const  double pi2 = 6.28318530718;
void NonlinearMap(double *x, double *y){
    static double K = 1.04295;
    *y += K * sin(*x);
    *x += *y;
    *x = fmod(*x, pi2);
    if (*x < 0.0) *x += pi2;
};
void winInit(){
    /* Poner sistema de coordenadas */
    gluOrtho2D(0.0, pi2, 0.0, pi2);
};
void display(void){
    const int    NumberSteps = 1000;
    const int    NumberOrbits = 100;
    const double Delta_x = pi2/(NumberOrbits-1);
    int step, orbit;
    glColor3f(0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT);
    glColor3f(1.0, 1.0, 1.0);
    for (orbit = 0; orbit < NumberOrbits; orbit++){
      double x, y;
      y = 3.1415;
      x = Delta_x * orbit;
      glBegin(GL_POINTS);
      for (step = 0; step < NumberSteps; step++){
        NonlinearMap(&x, &y);
        glVertex2f(x, y);
      };
      glEnd();
    };
    for (orbit = 0; orbit < NumberOrbits; orbit++){
      double x, y;
      x = 3.1415;
	y = Delta_x * orbit;
	glBegin(GL_POINTS);
	for (step = 0; step < NumberSteps; step++){
	  NonlinearMap(&x, &y);
	  glVertex2f(x, y);
	};
	glEnd();
     };
};
int main(int argc, char **argv) {
  glutInit(&argc, argv);  
  glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);  
  glutInitWindowPosition(5,5);  
  glutInitWindowSize(300,300);  
  glutCreateWindow("Standard Map");  
  
  winInit();
  glutDisplayFunc(display);  
  glutMainLoop();  
  
  return 0;  
}  
  
Léete el artículo Programando GLUT
    para entender las rutinas glut*, la mayor parte de este código 
    viene de ahí. La ventana gráfica se abre en modo buffer 
    simple y RGB. Entonces una función callback llamada 
    display() dibuja la transformación:
    primero seleccionamos el color negro para el fondo y 
    glClear(GL_COLOR_BUFFER_BIT) pone el buffer de color al color actual (negro),
    a continuación, después de seleccionar el color blanco con
    glColor, ejecutamos unas cuantas veces la función 
    NonlinearMap() y dibujamos los puntos con 
    glVertex* en modo GL_POINTS. Realmente simple. Fíjate con en la rutina de inicialización de la ventana
    winInit() hay una única instrucción
    del toolkit de utilidades de OpenGL, gluOrtho2D(). Esta rutina pone 
    un sistema de coordenadas 2D ortogonal. Los parámetros que recibe son 
    "mínimo x, máximo x, mínimo y, máximo y". He elgido una ventana en modo simple y un gran número de puntos
    para que puedas ver la imagen mientras es dibujada. Esto es habitual en modo
    simple con imágenes grandes y que tarden en calcularse, los dibujos
    aparecen en pantalla tal y como van siendo generados por las rutinas de OpenGL. Después de ejecutar ../../common/January1998/example1 deberías ver esta imagen:  
 Vayamos al segundo programa,
    ../../common/January1998/../../common/January1998/example2.c: 
    
#include <GL/glut.h> 
#include <math.h>
const  double  pi2 = 6.28318530718; 
const  double  K_max = 3.5;
const  double  K_min = 0.1;
static double  Delta_K = 0.01;
static double  K = 0.1;          
void NonlinearMap(double *x, double *y){
    /* Transformación estandar */
    *y += K * sin(*x);
    *x += *y;
    /* El ángulo x es módulo 2Pi */
    *x = fmod(*x, pi2);
    if (*x < 0.0) *x += pi2;
};
/* Función callback: 
   Qué hacer en ausencia de entradas */
void  idle(void){
    /* Incrementar el parámetro estocástico */
    K += Delta_K;
    if(K > K_max) K = K_min;
    /* Redibujar el display */
    glutPostRedisplay();
};
/* Inicialización de la ventana gráfica */
void winInit(void){
    gluOrtho2D(0.0, pi2, 0.0, pi2);
};
/* Función callback:
    Qué hacer cuando el display se ha de redibujar */
void display(void){
    const int    NumberSteps = 1000;
    const int    NumberOrbits = 50;
    const double Delta_x = pi2/(NumberOrbits-1);
    int step, orbit;
    glColor3f(0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT);
    glColor3f(1.0, 1.0, 1.0);
		
    for (orbit = 0; orbit < NumberOrbits; orbit++){
	double x, y;
	y = 3.1415;
	x = Delta_x * orbit;
	glBegin(GL_POINTS);
	for (step = 0; step < NumberSteps; step++){
	  NonlinearMap(&x, &y);
   	  glVertex2f(x, y);
	};
	glEnd();
     };
     for (orbit = 0; orbit < NumberOrbits; orbit++){
	double x, y;
	x = 3.1415;
	y = Delta_x * orbit;
        glBegin(GL_POINTS);
	for (step = 0; step < NumberSteps; step++){
	  NonlinearMap(&x, &y);
	  glVertex2f(x, y);
	};
	glEnd();
     };
     glutSwapBuffers();
};
int main(int argc, char **argv)  {  
  /* Inicializaciones de GLUT */
  glutInit(&argc, argv);  
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);  
  glutInitWindowPosition(5,5);  
  glutInitWindowSize(300,300);
  
  /* Abrir ventana*/
  glutCreateWindow("Order to Chaos");  
  
  /* Inicialización de la ventana */
  winInit();
  /* Registrar funciones callback */
  glutDisplayFunc(display);  
  glutIdleFunc(idle);
  /* Iniciar el proceso de eventos */
  glutMainLoop();  
  
  return 0;  
}  
  
    
    Este programa se basa en ../../common/January1998/../../common/January1998/example1.c, 
    la principal diferencia es que la ventana se abre en modo de doble buffer, 
    y el parámetro K de la transformación es una variable que 
    cambia durante la ejecución del programa. Hay una nueva función 
    callback idle() registrada para el procesador 
    de eventos de GLUT por glutIdleFunc().Esta función tiene un 
    significado especial, es ejecutada por el procesador de eventos en todo momento
    que no hay entrada del usuario. La función callback idle() es 
    ideal para animaciones de programas. En ../../common/January1998/example2, se utiliza para cambiar 
    ligeramente el valor del parámetro de la transformación.
    Al final de idle() hay otra función de GLUT útil, 
    glutPostResDisplay() que redibuja la ventana conservando la 
    inicializaciones anteriores, en general es más eficiente que llamar
    a display() de nuevo. Otra diferencia a señalar es el uso de glutSwapBuffers() al
    final de display(). La ventana se inició en modo doble buffer
    por lo que todas las directivas de trazado se aplican al buffer oculto, el
    usuario no ve cómo se dibuja la imagen en este caso. Cuando se ha 
    finalizado la imagen completa (frame), entonces se hace visible intercambiando
    los buffers visible e invisible con glutSwapBuffers(). Sin esta técnica
    la animación no iría suave.
     Estas son algunas imágenes que se ven durante la animación:  
  
  
 IMPORTANTE: LA función callback display() siempre
    se invoca al menos una vez antes que idle(). Recuerda esto cuando escribas tus 
    animaciones y decidas qué va en display() y qué va en idle(). Descarga:
 ../../common/January1998/../../common/January1998/example3.c
 
 Dibujando Líneas y Polígonos
 
 Como hemos dicho antes, glBegin(GLenum mode)
    acepta varios modos, y la secuencia de vértices 
    v0, v1,v2,
    v3,v4,... vn-1 
    declarados a continuación se interpreta acordemente.
    Los valores posibles para mode y su significado son:  
    GL_POINTS    Dibuja un punto en cada uno de los n vértices.
    GL_LINES     Dibuja una serie de líneas no conectadas. Los 
          segmentos se dibujan entre v0 y v1, 
          v2 y v3,...etc. Si n is impar 
          vn-1 se ignora.
    GL_POLYGON    Dibuja un polígono usando  v0,
          v1,..,vn-1 como vértices. n debe ser al menos 
          3 o entonces no se dibuja nada, además el polígono no se 
          puede cortar a sí mismo y debe ser convexo (debido a limitaciones 
          en los algoritmos de hardware).
    GL_TRIANGLES    Dibuja una serie de triángulos usando los 
         vértices v0,  v1 y  v2, luego 
         v3,  v4 y  v5 etc. Si n no es un  
         multiplo de 3 los puntos sobrantes se ignoran.
    GL_LINE_STRIP    Dibuja una línea desde v0 
         hasta v1, luego otra desde v1 hasta v2 
         y así sucesivamente. La última va desde vn-2 
         hasta vn-1, siendo un total de n-1 segmentos de línea.
         No hay restricciones en los vértices que describen una tira de 
         líneas, las líneas pueden intersecarse arbitrariamente.
    GL_LINE_LOOP    Lo mismo que GL_LINE_STRIP excepto que al final
         se dibuja un segmento de línea desde vn-1 hasta
         v0, cerrando el lazo.
    GL_QUADS    Dibuja una serie de cuadriláteros usando los 
         vértices v0,  v1,  v2, v3  
         y  v4,  v5,  v6, v7 y así
         sucesivamente.
    GL_QUAD_STRIP    Dibuja una serie de cuadriláteros usando 
         los vértices v0,  v1,  v3, 
         v2 y luego v2,  v3,  v5, 
         v4 y así sucesivamente.
    GL_TRIANGLE_STRIP    Dibuja una serie de triángulos usando
         los vértices en el orden siguiente: v0,  v1,  
         v2, luego v2,  v1,  v3, luego
         v2,  v3,  v4, etc. El orden es para asegurar 
         que los triángulos tienen la orientación correcta y la tira se
         puede usar para formar parte de una superficie.
    GL_TRIANGLE_FAN      Similar a GL_TRIANGLE_STRIP excepto que los
         triángulos son v0,  v1,  v2, luego
         v0,  v2,  v3, luego v0,  
         v3,  v4, etc. Todos los triángulos tienen
         v0 como vértice común.
     En nuestro tercer ejemplo, otra animación, hacemos uso de GL_LINES
    y GL_POLYGON. Compila el programa, mírate el código fuente 
    y observa cómo funciona. Es básicamente muy similar a ../../common/January1998/../../common/January1998/example2.c,
    ahora la imagen dibujada es un péndulo muy simple. La animación 
    simula el movimiento de un péndulo ideal. Esto es una fotografía
    de la animación:  
 Como antes, hay una función callback idle() cuya misión
    aquí es mantener el reloj funcionando (actualizando la variable 
    time). La función display() dibuja dos objetos, la 
    cuerda del péndulo y su peso (en blanco y rojo respectivamente). El
    movimiento de las coordenadas del péndulo está implícito en
    las fórmulas de xcenter y ycenter: 
    
void display(void){
  static double radius = 0.05;
  const double delta_theta = pi2/20;
  double xcenter , ycenter;  
  double x, y;
  double theta = 0.0;
  double current_angle = cos(omega * time);
  glColor3f(0.0, 0.0, 0.0);
  glClear(GL_COLOR_BUFFER_BIT);
  glColor3f(1.0, 1.0, 1.0);
  /* Dibujar la cuerda del péndulo */  
  glColor3f(1.0, 1.0, 1.0);
  glBegin(GL_LINES);
  glVertex2f(0.0, 0.0);
  xcenter = -cord_length * sin(current_angle);
  ycenter = -cord_length * cos(current_angle);
  glVertex2f(xcenter, ycenter);
  glEnd();
  /* Dibujar el disco del péndulo */
  glColor3f(1.0, 0.0, 0.0);
  glBegin(GL_POLYGON);
  while (theta <= pi2) {
    x = xcenter + radius * sin(theta);
    y = ycenter + radius * cos(theta);
    glVertex2f(x, y);
    theta += delta_theta;
  };
  glEnd();
  
  glutSwapBuffers();
};
    
    EjerciciosAquí te damos algunas sugerencias para que practiques lo que has
    aprendido hasta ahora: 
    En ../../common/January1998/../../common/January1998/example1.c  prueba otras transformaciones.
    Vete a la biblioteca y coge cualquier libro sobre Caos y Fractales, allí 
    encontrarás muchos ejemplos. Experimenta cambiando los parámetros, 
    sistema de coordenadas, aplicando varias transformaciones consecutivamente antes
    de dibujar los puntos. Diviértete con ello.
    En ../../common/January1998/../../common/January1998/example2.c  puedes añadir colores a cada
    punto. Por ejemplo, una código de color muy interesante sería
    asignar a cada punto un color según la estabilidad local de la 
    órbita (Physics Review Letters Vol 63, (1989) 1226),
    cuando la trayectoria va hacia una región caótica, se vuelve
    más roja, por ejemplo, mientras que islas casi estables son más 
    azules. Si haces este efecto, se verá más clara la naturaleza 
    fractal de la transformación de nuestro ejemplo. Es un poco avanzado 
    para aquellos de vosotros que no hayais hecho nada de ecuaciones diferenciales,
    pero es interesante aprenderlo si quereis aprender cómo utilizar 
    transformaciones y fractales en vuestros gráficos por ordenador.En ../../common/January1998/../../common/January1998/example3.c , prueba a cambiar el tipo 
    de línea usado para dibujar el disco. Usa GL_LINES, GL_TRIANGLES, etc.
    Mira lo que ocurre. Prueba a optimizar la generación del disco, no es
    necesario evaluar tantos senos y cosenos para dibujar el mismo disco en cada
    imagen, puedes guardarlo en una matriz. Usando polígonos, prueba a 
    poner cajas, diamantes, o cualquier cosa al final del péndulo. Dibuja dos
    péndulos por imagen, moviéndoso independientemente o incluse
    chocando entre ellos.
     Proximamente....Esto es todo de momento. Hay todavía muchas cosas a discutir sobre 
    polígonos. En el próximo número (Marzo 1998) continuaremos
    explorando los polígonos, modelado y estudiaremos más detalles sobre
    algunos de los comandos que ya nos son familiares.  |