Dibujando Líneas
En el último número hablamos sobre los
elementos básicos para la construcción de
polígonos bajo OpenGL. Éste sólo soporta unas pocas
primitivas de objetos grométricos: puntos, líneas,
polígonos y superfícies descritas por vectores de pequeños
trianguos o cuadrilateros.
La ídea principal tras la simplicidad de OpenGL
es que es responsabilidad del desarrollador implementar
modelos geométricos más complejos a partir
de estos objetos simples. OpenGL contiene una serie de
comandos para controlar los detalles de los puntos, las
líneas y los polígonos.
Por ejemplo el tamaño de los puntos se puede especificar
en píxels empleando la primitiva
glPointSize:
void glPointSize(GLfloat tamaño)
Por defecto el tamaño de los puntos es de 1.0 pixels y
tamaño ha de ser siempre mayor que cero. El tamaño
del punto se especifica con un número en coma floatante;
están permitidos tamaños fraccionarios de puntos y
líneas. OpenGL interpreta las fraccionarines de
píxel según el contexto de trazado. Si el modo
anti-aliasing está activado, entonces OpenGL modifica
los píxels del entorno de la línea en
cuestión para dar la sensación de que tiene
anchura fraccional. El anti-aliasing es una técnica
que se usa también para eliminar las escaleras que
tienen las líneas inclinadas en las pantallas de baja
resolución. Si el modo anti-aliasing no está
activado entonces glPointSize redonderá el valor de
tamaño al entero más próximo.
El tamaño físico de un píxel depende
realmente del dispositivo. Por ejemplo, en resoluciones de
monitor bajas, el píxel parece más ancho. Del
mismo modo, en dispositivos con resoluciones muy altas, como
un plotter, el ancho de línea por defecto (1
píxel) puede aparecer casi invisible. Para estimar el
ancho real de tus líneas debes conocer las
dimensiones físicas de los píxels en el
dispositivo de salida.
El ancho de las líneas se específica con la
función glLineWidth, que se debe invocar
antes del par de funciones glBegin() - glEnd() que
dibujan la línea. Ésta es la sintaxis completa del
comando:
void glLineWidth(GLfloat ancho)
Las implementaciones de OpenGL pueden limitar el ancho de
las líneas sin anti-aliasing al ancho máximo
de las líneas con anti-aliasing, redondeado al valor
entero más próximo. Ten en cuenta también que
el ancho de las líneas no se mide perpendicularmente
a la línea, sino en la dirección del eje de
las y si el valor absoluto de la pendiente de la curva es
menor que 1; o en la dirección del eje de las x si es
mayor que 1.
Este mes hemos preparado otra animación 2D, simple
pero esperemos que útil, que muestra cómo usar
varios tipos de anchos de línea en las aplicaciones
OpenGL (../../common/March1998/example2.c, ../../common/March1998/Makefile). He elegido un ejemplo de
Física Cuántica: una partícula
cuántica atrapada en una pozo doble de
potencial. ¿Por qué? Humm... pues lo he olvidado. De
cualquier modo, imagino que será útil para que
estudiantes de física e ingeniería vean
cómo integrar la ecuación de Schroedinger
dependiente del tiempo, los demás se pueden divertir
viendo la naturaleza no intuitiva de la mecánica
cuántica. En MC, una partícula no se
representa por una posición y una velocidad, sino por
una onda cuántica (línea púrpura
sólida en nuestra animación) cuyo valor
cuadrado absoluto representa la probabilidad de observar la
partícula en una posición dada (línea
blanca discontínua):
Figura 1. Simulación Cuántica
Para aquellos que tengan algunos conocimientos de
Ecuaciones Diferenciales Ordinarias, decir que la
ecuación de onda se integra usando el método de
FFT (Transformada Rápida de Fourier)
Split-Operator. Este método es mucho más exacto
y rápido que cualquier método de diferencias
finitas. Es aplicable a la propagación de ondas
no-lineales; el operador de evolución del tiempo se
divide en dos operadores de segundo o mayor orden que sólo
dependen o de la posición o del momento (frecuencia),
entonces se hace evolucionar en el tiempo a la función
de onda aplicando sucesivamente estos operadores cambiando
alternativamente entre el espacio de posiciones y el espacio
de momentos (frecuencias).
El cuerpo del código fuente se puede usar para
muchas otras aplicaciones. Puedes cambiar mi
simulación cuántica por tu función
dependiente del tiempo y obtener una animación maja de
tu sistema. Puedes probar también a escribir un
gnuplot simplificado basado en OpenGL para plotear funciones
y ficheros de datos.
Si el lector ha seguido los artículos previos sobre
GLUT y OpenGL este código fuente será muy
sencillo y fácil de entender (dejando a parte la
mecánica cuántica). No hay nada
extraordinario. En la función main() abrimos
una sola ventana en modo buffer doble, entonces le pasamos
unas funciones callback display() e idle()
que se encargan de dibujar la función de onda e
integrar la ecuación de onda, respectivamente. Entender
lo que pasa en la función idle(), aunque es un
truco muy bonito, no es necesario para captar el contenido de
este artículo. Las cosas nuevas sobre OpenGL
están en la función callback
display:
void
display (void)
{
static char label[100];
float xtmp;
/* Limpiar el espacio de dibujo */
glClear (GL_COLOR_BUFFER_BIT);
/* Escribir la nota al pie */
glColor3f (0.0F, 1.0F, 1.0F);
sprintf (label, "(c)Miguel Angel Sepulveda 1998");
glRasterPos2f (-1.1, -1.1);
drawString (label);
/* Dibujar una rejilla fina */
glLineWidth (0.5);
glColor3f (0.5F, 0.5F, 0.5F);
glBegin (GL_LINES);
for (xtmp = -1.0F; xtmp < 1.0F; xtmp += 0.05)
{
glVertex2f (xtmp, -1.0);
glVertex2f (xtmp, 1.0);
glVertex2f (-1.0, xtmp);
glVertex2f (1.0, xtmp);
};
glEnd ();
/* Dibujar el cuadrado del borde */
glColor3f (0.1F, 0.80F, 0.1F);
glLineWidth (3);
glBegin (GL_LINE_LOOP);
glVertex2f (-1.0F, -1.0F);
glVertex2f (1.0F, -1.0F);
glVertex2f (1.0F, 1.0F);
glVertex2f (-1.0F, 1.0F);
glEnd ();
/* Dibujar la rejilla */
glLineWidth (1);
glColor3f (1.0F, 1.0F, 1.0F);
glBegin (GL_LINES);
for (xtmp = -0.5; xtmp < 1.0; xtmp += 0.50)
{
glVertex2f (xtmp, -1.0);
glVertex2f (xtmp, 1.0);
glVertex2f (-1.0, xtmp);
glVertex2f (1.0, xtmp);
};
glEnd ();
/* Dibujar los ejes de coordenadas */
glLineWidth (2);
glBegin (GL_LINES);
glVertex2f (-1.0, 0.0);
glVertex2f (1.0, 0.0);
glVertex2f (0.0, -1.0);
glVertex2f (0.0, 1.0);
glEnd ();
/* Etiquetas de los ejes */
glColor3f (1.0F, 1.0F, 1.0F);
sprintf (label, "Position");
glRasterPos2f (0.80F, 0.025F);
drawString (label);
glColor3f (1.0F, 0.0F, 1.0F);
sprintf (label, " Quantum Probability ");
glRasterPos2f (0.025F, 0.90F);
drawString (label);
glColor3f (1.0F, 1.0F, 1.0F);
sprintf (label, " Real(Psi) ");
glRasterPos2f (0.025F, 0.85F);
drawString (label);
/* Dibujar la funcion de onda */
psiDraw (NR_POINTS, psi, x);
/* Dibujar la funcion de potencial */
potentialDraw (NR_POINTS, potential, x);
glutSwapBuffers ();
};
Lo primero que se hace es borrar el bit del buffer de
color, que nos da un espacio de dibujo limpio
(negro). Añadimos una nota al pie usando glRasterPos
y glutBitmapCharacter (drawstring no es nada
más que una envoltura para la utilidad clut). En
lecciones futuras glRasterPos aparecerá de
nuevo como una función auxiliar para el trazado de
texturas. Ni OpenGL ni GLUT ofrecen una manera simple y
potente de trazar texto en una ventana gráfica. La
función glutBitmapCharacter básicamente copia
una fuente de mapas de bits en el buffer de color.
Después de la nota al pie vienen una serie de
líneas: el borde exterior, la rejilla de fondo, los
ejes de coordenadas y, naturalmente, las curvas en
cuestión, dibujadas con psiDraw y
potentialDraw. Antes de trazar cada línea hay
una instrucción glLineWidth que especifica el
número de píxels de ancho que se debe dar a la
línea. La Figura 1 muestra la salida en un X Window
System (Linux Alpha). Por alguna razón que desconozco,
la salida del mismo programa en Windows 95 sale bastante mal,
parece que la capacidad de antialiasing no está muy
bien soportada en el driver de OpenGL de SGI; es
difícil diferenciar líneas que en principio
deberían tener anchura diferente, y la rejilla de
líneas de fondo también aparece muy
uniforme. Estos defectos aparecen cuando el monitor
está puesto a alta resolución, luego no es un
defecto de una resolución baja. Es para mi un placer
decir que, una vez más, un X Window System en Linux
sobrepasa por mucho a win95/NT.
Hay dos tipos de trazado de líneas en la
función display(), el modo GL_LINES, que une
vértices con una línea continua abierta, y el
modo GL_LINE_LOOP, que al final cierra el lazo.
Líneas con Antialiasing
He activado el antialiasing para las líneas en la
función callback reshape(),
void
reshape (int w, int h)
{
glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
glViewport (0, 0, w, h);
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
gluOrtho2D (-1.2, 1.2, -1.2, 1.2);
glEnable (GL_LINE_SMOOTH); /* Activar lineas con Antialiasing */
glEnable (GL_LINE_STIPPLE);
};
¿Para qué sirve GL_LINE_STIPPLE? OpenGL nos deja
controlar no sólo el ancho de una línea sino
también su patrón. Activando GL_LINE_STIPPLE
podremos dibujar líneas ralladas, punteadas o con
cualquier otro patrón. La única línea que
lo utiliza en nuestra animación aparece en la
función psiDraw():
glLineWidth (1);
glPushAttrib (GL_LINE_BIT);
glLineStipple (3, 0xAAAA);
glBegin (GL_LINE_STRIP);
for (i = 0; i < nx; i++)
{
xs = ratio1 * (x[i] - XMIN) - 1.0;
ys = ratio2 * (psi[2 * i] - YMIN) - 1.0;
glVertex2d (xs, ys);
};
glEnd ();
glPopAttrib ();
Líneas Punteadas
La función glLineStipple específica el
patrón usado para el punteado, en nuestro ejemplo hemos
usado el patrón 0xAAAA. En binario este número
es 0000100010001000 y OpenGL interpreta esto dibujando 3 bits
apagados, 1 bit encendido, 3 bits apagados, 1 bit encendido, 3
bits apagados, 1 bit encendido y por último 4 bits
apagados. La patrón se lee hacia atrás porque
los bits de menor orden se usan
primero. glLineStipple tiene dos parámetros,
el patrón de punteado que debe ser un número
hexadecimal y un factor entero que sirve para escalar este
patrón; con un factor de 3 nuestra línea
punteada mostrará 9 bits apagados, 3 bits encendidos, 9
bits apagados, 3 bits encendidos, 9 bits apagados, 3 bits
encendidos y por último 12 bits apagados. Jugando con
factores y patrones binarios uno puede dibujar todo tipo de
líneas punteadas complicadas.
Un detalle más: he puesto el trazado de la
línea punteada entre dos sentencias de push y pop de
atributos. ¿Recordáis cuando en nuestro primer
artículo dijimos que OpenGL es una máquina de
estados? En futuros artículos veremos con más
detalle estas operaciones de push y pop, pero brevemente lo
que estamos haciendo con la primera sentencia glPushAttrib
(GL_LINE_BIT) es guardar en una pila el valor acutal de
la variable de estado GL_LINE_BIT (esta variable decide el
patrón de punteado), entonces podemos modificar
GL_LINE_BIT con nuestra sentencia glLineStipple y
cuando hemos acabado llamamos a glPopAttrib que
devuelve el valor antiguo de la variable GL_LINE_BIT. Este
mecanismo es una manera efectiva de modificar las variables de
estado de OpenGL localmente. Si no lo hacemos así
entonces todas las líneas dibujadas después de
glLineStipple tendrían el mismo patrón
de punteado y estaríamos forzados a declarar un
patrón con glLineStipple para cada
línea que trazásemos en nuestra
aplicación. Push y pop nos evitan este molesto
trabajo.
Próximamente ....
OpenGL es famoso por su maravillosa API 3D. Hasta
aquí hemos explorado algunas posibilidades elementales
de trazado 2D con OpenGL. En el próximo número
examinaremos el escenario de OpenGL 3D, cómo poner una
perspectiva, sistemas de coordenadas, planos de corte y
matrices de proyección.
Hasta entonces, a divertirse con OpenGL.......
|