Taller de Visualización 3D

Una introducción a la programación
de aplicaciones en las estaciones de
trabajo SILICON GRAPHICS.
 
Por : HUMBERTO CERVANTES M.
Revisión : ALFONSO MARTINEZ MARTINEZ
UAM Iztapalapa.


Primera Parte: La aplicación en el ambiente gráfico.

1.- Introducción :

El objetivo de este taller es aprender a crear una aplicación de visualización en 3D en las estaciones de trabajo Silicon Graphics, mediante la utilización de las librerías de OpenGL y XFORMS.

Haremos primeramente una breve introduccion a la programación en las estaciones de trabajo, para poder comprender mejor el funcionamiento de XFORMS, despues haremos un pequeño repaso a la graficación en

3D, para permitir una mejor comprension de la librería de OpenGL.

Las palabras itálicas se en encuentran brevemente explicadas al final, en el glosario.

2.- El sistema de ventanas X:

Las estaciones de trabajo Silicon Graphics son máquinas bastante poderosas en lo que se refiere a la graficación. Poseen hardware dedicado a algunas de las tareas que consumen más tiempo y recursos del procesador y así logran un muy buen desempeño en este ambito. Este tipo de estaciones trabajan en un ambiente gráfico (desktop) llamado IRIX y utilizan el sistema operativo UNIX.

Puesto que todo lo que se realiza en la estación aparece en la pantalla en forma de ventana, no es una tarea sencilla el querer desplegar una grafica en la pantalla, no es tan simple como “poner en modo gráfico y dibujar pixeles de algun color” (que es lo que se haria comunmente en las PC). Las estaciones de trabajo utilizan lo que se llama el X Window System que es fundamentalmente un protocolo mediante el cual una aplicación puede generar una salida en una computadora que tiene un despliegue gráfico y que puede recibir entrada de los dispositivos asociados con este despliegue.

El sistema de ventanas X o simplemente X como comunmente se conoce esta basado en un modelo cliente- servidor. El programa de aplicación es el cliente, que se comunica a traves del protocolo X con un servidor que maneja las entradas y salidas directas del despliegue. Este modelo tiene varias caracteristicas importantes :

  • El cliente y el servidor pueden correr ya sea en la misma máquina o en distintas maquinas, estas últimas comunicadas a traves de una red.
  • Sólo el servidor necesita preocuparse por el hardware de despliegue. El protocolo X es independiente de hardware, asi que un cliente puede correr sin alteración utilizando cualquier clase de despliegue que soporte el protocolo.
  • Cada cliente abre una conexión a uno o más servidores. Los clientes y servidores interactuan mediante peticiones, respuestas, errores y eventos. Un cliente envia una petición al servidor para que realice alguna acción, como crear una ventana. Algunas peticiones hacen que el servidor genere respuestas al cliente, y el servidor notifica a los clientes de cambios mediante eventos. La mayor parte de aplicaciones del cliente poseen un ciclo continuo dentro del cual se estan leyendo eventos para despues procesarlos (posiblemente haciendo peticiones mientras se procesan) y enseguida leyendo otros eventos.
     
     

    Xlib:

    Los clientes de X no tienen que negociar con el servidor al nivel del protocolo X. X incluye una interface de cliente al protocolo en lenguaje C, llamada Xlib. Entre las facilidades que otorga Xlib están:

  • Rutinas para crear y manipular los recursos básicos del servidor, incluyendo ventanas, tipos de letras, contextos gráficos (GC) y otros.
  • Rutinas para rendereo de texto y gráficos en dibujables (drawables).
  • Almacenamiento en un buffer de las peticiones al servidor y cola de eventos del servidor.
  • Rutinas para manejo de mapas de colores (colormaps) y para el uso de espacios de colores independientes del dispositivo.
  • Rutinas para generación de entrada y salida de texto.
  • El administrador de recursos X (X resource manager), que es una base de datos de opciones especificadas por el usuario o la aplicación.
  • El administrador de recursos es el que permite al usuario controlar la apariencia ,asi como el estilo de interacción y otras caracteristicas del cliente (como el color y las fuentes usadas por la aplicación).

    Aunque Xlib provee los medios fundamentales para interactuar con el servidor X, el desarrollo de una aplicación compleja usando sólo Xlib es una tarea muy complicada. Xlib provee esencialmente las primitivas para un cliente X. Una aplicación compleja necesita combinar estas primitivas en estructuras que manejen aspectos de interacción con el servidor de una manera más general.

    Es asi como surge la preocupación de construir interfaces de nivel mayor que el de Xlib, por ejemplo podemos citar a Xt, abreviación de X Toolkit (caja de herramientas) que presenta contribuciones a Xlib tales como:

  • Objetos conocidos como Widgets , que se usan para mantener datos y presentar una interface al usuario.
  • Administracion de la geometría de los Widgets, esto es su tamaño, posicion, etc...
  • Manejo y despacho de eventos.
  • Finalmente se puede seguir esta filosofía de construir en ‘capas’ y es así como también existe Motif que añade un cierto numero de características de uso general para aplicaciones y usuarios, como son ventanas gráficas, menús de barras y otros elementos característicos de los GUIs (interfaces gráficas de usuario).

    La creación de aplicaciones con Motif resulta algo complicada pues hay que conocer un poco la filosofía de esta librería, que es la de uso de objetos y de programación orientada a objetos, pero con un lenguaje estructurado que en este caso es C (!).

    Es importante notar que aunque se construyen capas cada vez superiores es necesario tener algunos conocimientos de lo que existe debajo pues hay ocasiones en que no existe manera de realizar algo más que accediendo a las funciones 'de abajo'. De cualquier manera, es preferible que las aplicaciones usen las interfaces de mayor nivel disponibles pues esto resulta normalmente en programas más concisos y también asegura que no haya problemas pues ciertas llamadas a funciones de niveles inferiores afectan directamente a lo que se encuentra encima de ellas.

    3.-XForms:

    En este taller vamos a utilizar otra librería distinta a Xt o a Motif, su nombre es Xforms, es una caja de herramientas (toolkit) desarrollada por T.C. Zhao y Mark Overmars. Esta librería esta construida sobre Xlib exclusivamente lo que garantiza que funcione en todas las estaciones que tienen X instalado. El objetivo con el que se desarrolló esta librería es el de crear fácilmente GUIs (Interfaces Gráficas de Usuario) de una forma intuitiva, simple, poderosa, gráficamente `atractiva' y fácilmente extendible.

    En esta librería la noción principal es la forma, form (podemos ver la analogía que existe con los Widgets) que es una ventana en la cual se ponen diversos objetos. Cuando se cambia el estado de un objeto en particular en una de las formas desplegadas, el programa de aplicación es notificado y toma la acción necesaria, por ejemplo mediante la llamada a algún callback, o sea alguna funcion especifica.

    La librería consiste de un amplio número de rutinas en C para construir formas interactivas con botones, barras de scroll, campos de entrada, menús, etc... de manera sencilla. Estas rutinas pueden ser usadas en C o en C++. La ventaja de esta librería es que viene acompañada con el Form Designer o diseñador de formas, que permite crear la forma de una manera muy cómoda, permite definir el tamaño de la ventana, los botones que tiene, su apariencia, y muchas cosas más. Lo mejor de todo esto es que Form Designer escribe el código en C que genera la forma, de esta manera no hay necesidad de estarse preocupando por el calculo de las posiciones de los elementos ni su apariencia final, puesto que todo se hace de una manera WYSIWYG (lo que se ve es lo que se tiene).

    Esta librería se puede conseguir libremente vía Internet, la dirección es:

    http://bragg.phys.uwm.edu/xforms
     

    4.- La estructura de los programas.

    En la estación de trabajo la estructura de los programas difiere un poco cuando lo que deseamos es trabajar con la interfase gráfica. A diferencia de los programas típicos por ejemplo en C, en los cuales no tenemos que preocuparnos mucho por la salida o la entrada dado que se utilizan la entrada y salida estándar, la programación en el ambiente gráfico necesita de la revisión constante de eventos que a su vez determinan las acciones que se toman. De ahí que la mayor parte de las aplicaciones siguen el esquema siguiente:

  • Inicialización de la aplicación.
  • Creación y administración de los objetos de la aplicación (ya sean formas o widgets según la librería).
  • Asignación de los procedimientos de callback a ser invocados por los objetos.
  • Entrada al ciclo de procesamiento de los eventos (que recibe los eventos y a toma decisiones acerca de las acciones a realizar), del cual normalmente no se sale sino hasta que termina la aplicación.
  • Hasta aquí hemos visto lo indispensable para comprender el funcionamiento de una aplicación en el ambiente gráfico de la estación de trabajo, lo que sigue es una explicación de las librerías que se utilizan para realizar gráficas, con algunos conceptos de visualización en 3 dimensiones, que permitirán una mayor comprensión del tema.
     
     

    Segunda Parte: Visualización en 3 dimensiones.

    5.- La representación tridimensional.

    En la actualidad estamos acostumbrados a ver en la televisión o en el cine, imágenes o efectos que antes eran difícilmente imaginables, como por ejemplo naves espaciales, dinosaurios, o ciudades `futuristas', con un realismo nunca antes logrado. Ahora sabemos que detrás de todos estos efectos especiales están las computadoras, que crean estas imágenes que posteriormente pasan a la cinta. La visualización en 3 dimensiones es una poderosa herramienta también para aplicaciones como CAD (Diseño Asistido por Computadora), simulación, diseño, y una gran cantidad de cosas más. Todo esto nos sorprende un poco pero realmente lo que se halla detrás de todo esto son un número enorme de datos que se procesan y se manipulan matemáticamente. La representación tridimensional es pues, una herramienta muy poderosa, aunque bastante complicada pues no es algo sencillo el crear un mundo de 3 dimensiones para mostrarlo en una pantalla de dos dimensiones.

    Si quisiéramos comenzar a dibujar objetos 3D a partir de las `primitivas' que conocemos que son por ejemplo la línea y el punto (o pixel), pronto nos daríamos cuenta de que hasta ahora cuando hemos realizado gráficas en una computadora, sólo hemos trabajado con 2 ejes, y ahora queremos también tener un eje de `profundidad'. Todo esto puede resultar complicado, y más si nos ponemos a pensar cómo escribir rutinas que dibujen ya no una línea sino un polígono, que decidan cuales facetas son visibles y cuales no, que eliminen aquellas que no están dentro del campo visual, que iluminen nuestros objetos, y muchas otras funciones que son necesarias para lograr cierto realismo dentro de las representaciones. Como podemos ver esto puede ser muy complejo, y nos tomaría mucho tiempo lograr resultados satisfactorios, y peor aún si pensamos que a todo esto queremos desplegar nuestra imagen en una aplicación que trabaja sobre X o sea en un ambiente de red.
     

     

    6.-OpenGL : Una librería estándar.

    Afortunadamente para nosotros ya existen librerías que se encargan de hacer todo lo que mencionamos anteriormente de una manera eficaz. En particular vamos a estudiar la librería OpenGL que es una poderosa interface de software para hardware gráfico, desarrollada por Silicon Graphics y que cada vez se vuelve más un estándar. Podemos encontrar OpenGL en diversas plataformas que van desde las estaciones de trabajo, hasta las PC que trabajan con windows NT.

    OpenGL es una interface que consiste de aproximadamente 120 comandos, que se utilizan para especificar los objetos y las operaciones necesarias para producir aplicaciones tridimensionales interactivas.

    Esta librería está diseñada para trabajar eficientemente aunque la computadora en la cual se despliegan las gráficas no sea la computadora que está corriendo el programa de gráficos. Esto sucede por ejemplo en un entorno de red. La computadora en la cual corre el programa se llamará cliente y en la cual se realiza el despliegue es el servidor (esto, como podemos ver es idéntico que para X). Esto es útil por ejemplo al hacer los cálculos en una computadora poderosa y el despliegue en una PC.

    OpenGL utiliza un protocolo que es el mismo para todas las máquinas que trabajan con esta librería así que los programas OpenGL pueden trabajar a través de la red aunque el cliente y el servidor sean distintos tipos de computadoras. Si no se trabaja a través de una red entonces la misma máquina actúa como cliente y servidor a la vez.

    OpenGL esta diseñada como una librería independiente del hardware para lograr esto NO tiene comandos para realizar tareas de manejo de ventanas o de entradas del usuario (por ello utilizamos Xforms), tampoco provee comandos de alto nivel para describir modelos de objetos tridimensionales complejos, como podrían ser un automóvil o una casa. Lo que si tiene OpenGL es un conjunto de primitivas geométricas : puntos, líneas y polígonos, así como comandos que actúan sobre estas primitivas y que permiten realizar tareas como Sombreado Gouraud, iluminación y texturizado.

    Siguiendo la misma filosofía de construir en `capas' como hemos estado viendo hasta ahora, podríamos pensar en la posibilidad de construir encima de OpenGL una librería que nos permitiera manejar objetos más complejos, por ejemplo, y de hecho, esta librería existe y se llama Open Inventor, y está disponible para las estaciones Silicon Graphics.

    Es interesante notar que OpenGL aprovecha al máximo el hardware de las plataformas a las cuales ha sido portado, así es que por ejemplo en la Silicon Graphics, hará uso de los circuitos especializados para gráficos, por esto es una librería muy veloz en esta plataforma.

    Dentro de las diversas funciones que puede realizar OpenGL sobre las primitivas, podemos mencionar:

  • Descripción y despliegue de primitivas tales como puntos, líneas y polígonos, así como el cálculo de sus normales.
  • Manejo del `objeto' y de la `cámara' en un espacio tridimensional. Dentro del manejo se incluyen transformaciones tales como rotaciones, escalamientos, proyecciones...
  • Creación de listas de despliegue, esto es la agrupación de comandos de OpenGL, que posteriormente pueden ser llamados fácilmente (podríamos hacer una analogía con una macro).
  • Iluminación, posicionamiento de fuentes de luz locales y ambientales.
  • Y algunos más avanzados como son:

  • Blending (mezclado), Antialiasing (antiseudónimos) y neblina.
  • Despliegue de pixeles, Bitmaps (mapas de bits), Fuentes e Imágenes.
  • Mapeo de texturas o texturizado, esto es la aplicación de una Imagen o de un Bitmap a alguna de las primitivas.
  • Existen más opciones pero no las analizaremos, para más detalles consultar el libro: OpenGL Programming Guide (Jackie Neider, Tom Davis y Mason Woo, OpenGL ARB, editorial Addison Wesley).

    Vamos a dar una breve introducción a algunos de los comandos de OpenGL así como una explicación, para preparar al lector de tal forma que pueda poner en práctica los comandos desde un principio.

    Es necesario notar que todos los comandos de OpenGL comienzan con el prefijo gl (graphics language) y las constantes definidas con el prefijo GL. Algunos comandos de la librería pueden recibir diversos tipos de datos, y se pondrá un asterisco para hacerlo notar, por ejemplo para el comando glVertex*(), el asterisco significa que existen las variantes glVertex {2,3,4} {s,i,f,d} [v] (TYPE coords).

    - 2,3,4 : Si se quieren 2 , 3 o 4 datos, por ejemplo el usar 2 hace que se tomen las coordenadas de z=0.
    - s,i,f,d : Si se quieren usar coordenadas short , int , float o bien double.
    - v : Si se quiere usar las coordenadas como un arreglo.

    7.- Primitivas Gráficas.

    Enseguida veremos cuales son los pasos a seguir para realizar el despliegue de un objeto en 3 dimensiones, y conforme avancemos, veremos los comandos necesarios para lograrlo.

    Dentro del ejemplo que vamos a tomar, usaremos el siguiente objeto:

     

    Como podemos ver en la fig 1. El objeto que vamos a tratar aquí es un cubo sencillo. Sabemos que un cubo tiene por definición 6 caras o facetas distintas. De ahí que el primer paso que debemos hacer cuando queremos programar en OpenGL es conocer los puntos que definen las facetas de nuestro objeto. En este caso son puntos muy sencillos y es fácil imaginar un cubo en tres dimensiones pero resulta imposible determinar los puntos que definen por ejemplo la carrocería de un automóvil con tan solo la imaginación! Es por eso que existen programas que permiten crear figuras en tres dimensiones y que escriben código de OpenGL.

    De aquí en adelante usaremos los términos polígono y faceta de manera idéntica.

    Cuando creamos un objeto en 3 dimensiones con la librería OpenGL, tenemos la opción de escoger diversas primitivas para construirlo. Sin embargo tenemos que tomar en cuenta algunas restricciones:

  • Los bordes de los polígonos no pueden intersectarse.
  • Los polígonos deben ser convexos.
  • Los puntos que forman un polígono deben estar en un mismo plano. El único polígono que nos garantiza esto es el triángulo, de hecho una gran parte de los programas de 3D trabajan con triángulos nada más.
  • Dentro de las primitivas que permite utilizar OpenGL se encuentran las siguientes:

    GL_POINTS Dibuja puntos.

    GL_LINES Dibuja líneas no conectadas.

    GL_POLYGON Dibuja un polígono de n vértices donde n es como mínimo 3.

    GL_TRIANGLES Dibuja una serie de triángulos.

    GL_LINE_STRIP Dibuja una serie de líneas interconectadas.

    GL_LINE_LOOP Igual que el anterior pero el primer y último están interconectados.

    GL_QUADS Dibuja una serie de cuadriláteros.

    GL_QUAD_STRIP Dibuja cuadriláteros pegados unos con otros.

    GL_TRIANGLE_STRIP Dibuja triángulos pegados unos con otros (uno frente a otro).

    GL_TRIANGLE_FAN Dibuja triángulos pegados unos con otros en forma de abanico.
     
     

    La información más importante acerca de los vértices son sus coordenadas, que se especifican con el comando glVertex*(). Por cada uno de los vértices se puede especificar un color, un vector normal y coordenadas de textura. Al querer definir un polígono, debemos hacerlo las llamadas a glVertex*() exclusivamente dentro de los limites que fijan las instrucciones: glBegin() y glEnd(). Para la asignación de colores usaremos el comando glColor*(). Es también necesario saber que un polígono tiene dos caras, se define la cara anterior como aquella para la cual los vértices van en el sentido contrario al de las manecillas del reloj.

    Es así como para nuestro programa vamos a hacer los comandos siguientes para cada una de las caras del cubo:

    glBegin(GL_POLYGON);
            glColor3f(0.0,0.0,0.0);
            glVertex3f(1.0,1.0,1.0);
            glVertex3f(1.0,1.0,-1.0);
            glVertex3f(-1.0,1.0,-1.0);
            glVertex3f(-1.0,1.0,1.0);
    glEnd();
     

    Si nos fijamos en la figura 1. anteriormente mostrada, podemos apreciar que se trata de la cara superior del cubo, y que su color es negro. Es necesario notar que OpenGL es una máquina de estado, esto significa que se puede poner en diversos estados o modos que permanecen hasta que uno pide su cambio. Por ejemplo, el color actual es una variable de estado, se pone en un cierto valor y este permanece para las funciones que hacen uso del color hasta que no se vuelve a llamar a glColor*(). Existen más variables que controlan cosas como los modos de dibujo de los polígonos, el posicionamiento de las luces, y las propiedades materiales de los objetos que están siendo dibujados. Se activan y desactivan mediante llamadas a glEnable() y glDisable(), aunque todas las variables de estado tienen un valor predefinido.
     
     

    8.- Limpiando la pantalla. Aunque hemos visto en la sección anterior los pasos necesarios para la `definición' de las facetas de un objetos tridimensional, antes de comenzar a dibujar cualquier cosa en la pantalla, debemos primero limpiarla, en el caso de OpenGL, lo que vamos a hacer es limpiar los buffers principales.

    Entre los diversos buffers que existen, debemos conocer principalmente dos de ellos: el buffer de color que contiene el valor del color de los pixeles que son desplegados, y el buffer de profundidad. Este último es muy importante pues contiene el valor en el eje Z de cada uno de los pixeles que hay en la pantalla, así que si se desea dibujar un nuevo objeto, el valor en el eje Z de sus puntos se compara con el que existe en el buffer de profundidad, y si sus puntos se hallan más `próximos' a la pantalla que los que existían anteriormente, entonces se dibujan los puntos del objeto, de lo contrario, esto no se realiza.

    El `limpiado' de los buffers se realiza de maneras diferentes para cada uno de los mencionados, pues para el de color, basta hacer una llamada a glClearColor() , dando el color con el cual se desea limpiar la pantalla y después a glClear() para hacer el limpiado. El buffer de profundidad realmente no se `limpia' sino que más bien se llena con un valor de profundidad así que una vez que esto se realiza, si los siguientes objetos están igual o más cerca de la pantalla que los que se llenaron con el valor dado, siempre serán dibujados. Normalmente se llena con el valor máximo de profundidad. Para limpiar los buffers de una sola llamada se puede hacer lo siguiente:

    glClearColor(0.0,0.0,0.0,0.0); /* El color de limpiado será cero */
    glClearDepth(0.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
     

    En nuestros programas haremos uso de la técnica de doble buffer esto para poder realizar animaciones de nuestros objetos. El empleo de dos buffers nos permite dibujar una imagen mientras se despliega la otra, así mientras una vista del objeto se muestra, la siguiente se calcula, y así al intercambiar las imágenes, tenemos la impresión de que estamos viendo una animación.

    9.-Visualizando.

    Una vez que hemos hablado de las primitivas gráficas, así como de los buffers en los cuales estas se presentarán, debemos estudiar la visualización que es el otro aspecto esencial a considerar. Para visualizar un objeto en 3 dimensiones, podemos hacer una analogía con la actividad de tomar una fotografía, pues básicamente se siguen los mismos pasos. En el primero se pone la cámara apuntando a la escena que queremos tomar, después posiblemente modifiquemos algunas cosas de la escena a ser fotografiada, luego ajustamos el acercamiento, y finalmente se escoge de que tamaño se quiere la impresión final. Estos mismos pasos tienen nombres específicos en el campo de la visualización 3D, pero aquí se habla de transformaciones. Siguiendo el mismo orden que citamos anteriormente, vendrían siendo: la transformación de vista (viewing), la de modelado (modeling), la de proyección (projection) y por último la del puerto de vista (viewport) .

    fig3. Etapas de transformación de los vértices.
     

    Las transformaciones de vista deben preceder las de modelado forzosamente, las demás pueden ser especificadas en cualquier momento, antes de que se haga el dibujo. Los vértices se componen de 4 coordenadas, estas son (x,y,z,w). Si la coordenada w es diferente de cero, entonces las coordenadas corresponden al punto tridimensional (x/w,y/w,z/w). La coordenada w se usa poco, y si no se especifica, toma el valor 1 por omisión.

    Cada vértice de la escena es multiplicado por una matriz de tamaño 4x4 que se construye a partir de las transformaciones de vista, modelado y proyección. Las normales se ven afectadas automáticamente por esta operación. Las coordenadas de recortado crean un volumen de vista, en el cual sólo los objetos que se hallan en su interior se toman en cuenta. Y finalmente las coordenadas ya transformadas se convierten en coordenadas de ventana, el puerto de vista puede ser manipulado causando modificaciones a la imagen final tales como agrandamiento, reducción o estiramiento.

    Vamos a estudiar cada una de estas transformaciones individualmente ya que es necesario aplicarlas correctamente para obtener una imagen aceptable en la pantalla. Es importante limpiar la matriz actual o de trabajo, antes de hacer modificaciones, de lo contrario pueden obtenerse resultados equivocados si la matriz de trabajo contiene operaciones realizadas anteriormente. Para esto se utiliza el comando glLoadIdentity().

    Antes de realizar transformaciones, debemos especificar con cual de las matrices vamos a estar trabajando, para esto, se usa el comando glMatrixMode() con uno de los argumentos siguientes: GL_PROJECTION o bien GL_MODELVIEW, dependiendo de la que necesitemos.

    La transformación de vista:

    Como hemos visto anteriormente esta transformación es análoga al posicionamiento y orientación de la cámara. Es importante notar que en OpenGL, la cámara así como los demás objetos de la escena aparecen en el origen, y la cámara apunta hacia el eje z negativo. Se pueden usar funciones como glTranslate*() o glRotate*() para hacer modificaciones. Esta transformación se aplica a la proyección, usaremos entonces: glMatrixMode(GL_PROJECTION).

    La transformación de modelado:

    Esta transformación se utiliza para posicionar y orientar el modelo u objeto que vamos a visualizar, se puede rotar, trasladar o cambiar la escala del modelo (scaling), para las dos primeras operaciones se usan los comandos mencionados en la transformación de vista, para el cambio de escala se usa glScale*(). Es necesario especificar que trabajamos con el modelo, de ahí que usaremos: glMatrixMode(GL_MODELVIEW).

    La transformación de proyección:

    Esta transformación equivale a poner lentes diferentes a la cámara, que cambiarán el campo de vista (es diferente usar un telefoto que un gran angular) además de esto, la transformación determina de que forma se proyectarán los objetos en la pantalla, existen dos maneras de realizarlo, la primera es usando perspectiva que hace cambiar el tamaño de los objetos dependiendo de la distancia a la cual están con respecto al observador, para esto se usa el comando glFrustum(). El otro tipo de proyección es ortográfica en la cual el tamaño de los objetos no cambia (esta proyección se usa comúnmente en aplicaciones CAD).

    La transformación de puerto de vista:

    Esta transformación define el tamaño y la posición de la imagen final (como la impresión de una fotografía), se usa el comando glViewPort() y es necesario modificar el puerto de vista cada que la ventana se modifica.

    Una vez que todas estas transformaciones son aplicadas a cada uno de los vértices, la imagen puede ser desplegada dentro de la ventana.

    La sintaxis de los comandos de transformación es la siguiente:

    glTranslate {fd} (TYPE x, TYPE y TYPE z);

    glRotate {fd} (TYPE ángulo,TYPE x, TYPE y, TYPE z);

    glScale {fd} (TYPE x, TYPE y, TYPE z);

    En donde TYPE es float o int, dependiendo del comando que se escogió.

    10.- Normales, Color e Iluminación:

    Ejemplo de llenado suave.
     

    Cuando hablamos de que cada vértice puede tener atributos propios, mencionamos color y normales. El color puede ser el mismo para los todos los vértices o distinto, lo que hará la diferencia es el tipo de `llenado' de los polígonos. Existen dos `modelos' de llenado, uno uniforme (flat) y el otro suave (smooth o Gouraud), se especifican con el comando glShadeModel() con uno de los argumentos siguientes GL_SMOOTH (que es el que existe por omisión) o GL_FLAT. Si los vértices tienen colores distintos y se escoge el llenado uniforme, se tomara el color de uno de los vértices y se llenara todo el polígono con ese color, si se tiene el llenado suave, los colores irán variando gradualmente entre los bordes del polígono. Recordemos que el color se asigna con el comando glColor3*() cuya sintaxis es la siguiente:

    glColor3 {b,s,i,f,d} (TYPE rojo, TYPE verde, TYPE azul);

    El uso de normales es necesario cuando queremos aplicar iluminación. Es importante notar que cada uno de los vértices tiene una normal, esto tal vez no es algo evidente en un principio cuando pensamos que cuatro vértices que conforman un plano tienen 4 normales distintas, pero veamos la ilustración siguiente:

    fig4. Diferente asignación de las normales.
     

    Como podemos ver, en la figura de la izquierda, tenemos una sola normal al plano que forman las facetas. De cualquier forma tenemos que dar una normal para cada vértice, lo que hacemos es usar la misma. En el otro caso, cada vértice tiene una normal, que es calculada a partir de la normal de las facetas adyacentes. Al aplicar una fuente de luz, podemos ver la diferencia de resultados, el segundo caso dará la impresión de una superficie más suave, no compuesta por facetas. Asignar las normales es una tarea sencilla a nivel de código pero puede ser algo más complicado el saber sus coordenadas, para esto es necesario recordar lo que se aprendió en los cursos de matemáticas. El comando para agregar normales es glNormal3*(), y su sintaxis es la siguiente:

    glNormal3 {bsidf} [v] (TYPE nx, TYPE ny, TYPE nz);

    Un ejemplo de esto es el siguiente:

    glBegin(GL_POLYGON);
            glNormal3fv(n0);        /* n0 es un arreglo que contiene los datos */
            glVertex3fv(v0);
            glNormal3fv(n1);
            glVertex3fv(v1);
            glNormal3fv(n2);
            glVertex3fv(v2);
    glEnd();
     

    Una vez que hemos asignado normales a los vértices es posible iluminar nuestro objeto. Existen varios pasos a seguir:

  • Creación, posicionamiento y activación de las fuentes de luz.
  • Selección de un modelo de iluminación.
  • Definición de las propiedades materiales de los objetos en la escena.
  • Las fuentes de iluminación tienen varias propiedades, como el color, la posición y la dirección, se usa el comando glLight*() para especificar los diversos valores. El tema de iluminación se explicara más a fondo durante el taller.

    11.- Para finalizar:

    Todo lo que hemos visto anteriormente nos permite ya tener bases para realizar una aplicación de visualización 3D en la estación de trabajo. No hemos tratado otros aspectos como son el texturizado, pues estos serán tratados durante el taller si hay suficiente tiempo.

    Es importante leer atentamente esta pequeña introducción pues permitirá un avance más veloz durante el taller cuya duración impide hacer mucho énfasis en los puntos que se explican, aunado a la amplitud de los temas tratados.

    La idea de este taller es que los participantes diseñen una pequeña aplicación de su propia imaginación. Así que se recomienda pensar en algo sencillo, podría ser por ejemplo una habitación en donde uno pueda pasear `virtualmente' o un objeto que se pueda controlar interactivamente, existen muchas posibilidades. Lo que buscamos es despertar el interés en un campo muy interesante y que esta poco difundido en este momento en la universidad.

    Cualquier comentario es bienvenido en la dirección siguiente : hcc@hp9000a1.uam.mx
     
     
     
     

    Glosario:

    Aquí se explican algunos de los términos mencionados anteriormente:

    Protocolo: Es un conjunto de reglas y formatos de datos expresados en software, que permiten a un sistema intercambiar mensajes con otro mediante una red de comunicaciones.

    Modelo cliente-servidor: Es un modelo de cómputo en el cual se define la interacción de dos entidades por medio de software, cada una con papeles distintos. Uno juega el papel de cliente, esto es la computadora que ejecuta los comandos (en el caso de OpenGL y X), y otra de servidor, que es en la cual son ejecutados los comandos.

    Graphics Context (GC): En X una primitiva gráfica no contiene toda la información necesaria para dibujar un gráfico en particular. Un recurso del servidor llamado el contexto gráfico especifica las variables restantes como el ancho de línea, colores y motivos de llenado. El GC modifica todo lo que va a ser dibujado en un Drawable.

    Rendereo: Conversión de primitivas especificadas en coordenadas del objeto a una imagen en el buffer. Es la principal operación que realiza OpenGL.

    Drawable: Una ventana no es el único lugar en donde se puede dibujar. Los Pixmaps (que son bloques de memoria fuera de la pantalla en el servidor) también son destinos válidos para la mayoría de las peticiones gráficas. Las ventanas y los pixmaps se conocen como Drawables, o sea áreas en donde se puede dibujar.

    Callback: Es una función que se ejecuta a partir de la recepción de un evento.

    Faceta: Es un elemento de un objeto tridimensional, puede ser un polígono tal como un triángulo, u otra primitiva.