Aula Macedonia


Curso de Programación Gráfica en OpenGL


Artículo realizado por
Oscar García "Kokopus".





Capitulo 1.
Conceptos previos.

Si estais aquí es porque os interesa el curso y eso me parece perfecto...¡vamos alla pues!

Sistemas gráficos. Dispositivos y elementos.

Tal como comenté en el artículo de presentaciín, es inevitable hablar de cómo programar con OpenGL sin, a la par, mencionar determinados conceptos. Por tanto primero debemos entender qué es un "pipeline" gráfico, qué elementos lo configuran y cuáles son sus entradas y salidas.

Un sistema gráfico típico se compone de los siguientes elementos físicos:



Estas son las características generales de cada uno de los elementos:



Antes de continuar aclarar el termino "pixel" (picture element). Un pixel es la unidad mínima de pantalla y los encontramos dispuestos en filas en cualquier monitor o televisor de manera que el conglomerado de pixels con sus colores asociados da lugar a la imagen.

El frame buffer se caracteriza por su resolucion y por su profundidad.

La resolución viene dada por el producto ancho x alto, es decir, el numero de filas multiplicado por el numero de columnas análogamente a las resoluciones que nuestro monitor puede tener (640 x 480, 800 x 600 ...).

La profundidad es el numero de bits que utilizamos para guardar la información de cada pixel. Este numero dependerá de la cantidad de colores que deseemos mostrar en nuestra aplicación. Típicamente si queremos "color real" necesitaremos ser capaces de mostrar 16,7 millones de colores simultáneamente que es la capacidad aproximada de nuestro sistema visual. En este caso y suponiendo una resolución de 800 x 600 pixels en pantalla necesitaremos...:

800 pixels/fila x 600 filas x 24 bits/pixel = 1.37 Megabytes de memoria para el frame buffer...



dado que color real implica 256 posibles valores de rojo, 256 de verde y 256 de azul por pixel y esto implica un byte/pixel para cada una de estas componentes. De todas formas mas adelante abordaremos este tema en profundidad cuando hablemos de color....¡no os preocupéis si ahora mismo no lo veis del todo claro!.

Por ahora debéis quedaros con la idea de que necesitamos una área especial de memoria para poder dibujar a cada momento nuestra imagen 3D. Según la resolución que deseemos para nuestra ventana de visionado y segun la calidad de color esperada, necesitaremos mas o menos memoria...así es.

Entonces pensareis...y ¿cómo lo hago para "reservar" esta memoria?...¿tengo que tratar "yo" directamente con ella?.....NO, precisamente esto será lo que OpenGL hará por si sola sin que nosotros nos demos ni cuenta aunque mas adelante veremos que también podríamos llegar a este nivel de detalle.

Nuestra librería o API (Application Programming Interface) , en este caso OpenGL, contactara directamente con los elementos de la figura anterior a medida que lo crea necesario. Por tanto esto nos será totalmente transparente y no deberá preocuparnos.

El modelo "cámara sintética"

OpenGL utiliza este modelo semántico para interpretar una escena que debe ser dibujada. Básicamente se trata de imaginar un objeto situado en un determinado lugar y filmado por una cámara...es tan sencillo como esto. Si conseguís tener claro este punto de vista tendréis mucho ganado os lo aseguro. Uno de los problemas al programar gráficos es no tener una visión mental clara del mundo 3D y no saber interpretar QUE es lo que esta pasando. Vamos a echarle un vistazo al siguiente diagrama:




Contamos con un "mundo 3D" que estamos observando desde una determinada posición. Podéis pensar en el observador como vosotros mismos mirando hacia vuestro mundo virtual o bien como una cámara que lo esta filmando. Lo llaméis como lo llaméis, ese punto es el centro de proyección tal y como se observa en la figura. Evidentemente el mundo es tridimensional pero su proyección en un plano (plano de proyección) es bidimensional. Este plano es nuestro frame buffer antes de dibujar o bien la pantalla del monitor después de haberlo hecho.

Por tanto queda claro que pasamos de coordenadas del mundo en 3D a coordenadas de pantalla en 2D y por lo tanto necesitamos proyectar.

El modelo "camara sintética" se compone pues de los siguientes elementos:


Es muy importante que notéis la independencia que todos estos elementos tienen entre si. La cámara es independiente de los objetos puesto que estos están en una posición y ella en otra. Manejaremos los objetos (Geometria) por un lado y la cámara por otro de manera que los primeros "actuaran" en nuestro mundo y la segunda "filmara" desde una determinada posición con un determinado ángulo, ¿ok?

Arquitecturas graficas

Hablemos ahora del "pipeline gráfico", elemento que marcara la pauta de actuación para OpenGL e incluso para vuestras manos en el momento de programar.

La geometría que deseáis dibujar será la entrada de vuestro pipeline gráfico y como salida tendréis una imagen en pantalla. Pero que ocurre con todos esos puntos, vectores y polígonos desde que entran hasta que salen?....veámoslo en la figura:



El punto de entrada de nuestra geometría es el superior izquierdo y a partir de ahí solo tenéis que seguir las líneas que conectan los diferentes módulos. Quizás ahora penséis que esto no es será útil y que es simplemente teoría. No, de ninguna manera podéis pretender programar OpenGL si no sabéis QUE y en QUE ORDEN hace las cosas...lo veréis cuando empecemos a programar...

Analicemos cada uno de los modulos



El pipeline gráfico puede implementarse vía software o hardware. En máquinas dedicadas, por ejemplo Silicon Graphics, todos los módulos están construidos en la placa madre de manera que el sistema es muy rápido. En sistemas mas convencionales como PC o Mac, todo se realiza vía software y por tanto es mas lento. Evidentemente esta es la razón de que una estación gráfica potente no sea precisamente barata.

De todas formas lo interesante de OpenGL es que funciona independientemente de la implementación del pipeline. No tendréis que cambiar el código si cambiáis de plataforma, ¡funcionara igual!...lo único es que según el sistema conseguiréis mas o menos velocidad.

Primeros Comandos

Empecemos pues con algunos comandos básicos de OpenGL. Como ya dije, supongo que tenéis nociones de C y por tanto no debe de seros complicado de asimilar.

OpenGL tiene sus propios tipos en cuanto a variables se refiere. Así aunque podemos usar los típicos (int, float, double), nos acostumbraremos a los definidos por esta librería (GLint, GLfloat, GLdouble). Como veis son los mismos pero con el prefijo "GL" delante. Se declaran exactamente igual que cualquier variable en C y tienen casi las mismas propiedades. Usémoslos porque el funcionamiento general del sistema sera mas optimo.

Las funciones OpenGL empiezan con el prefijo "gl", en minúsculas. Veamos un ejemplo. supongamos que queremos crear un vértice, es decir un punto:
Usaremos...

glVertex3f( 0.0 , 0.0 , 0.0 );
o...
GLfloat vertice[3] = { 0.0, 0.0, 0.0 };
y después...
glVertexfv( vertice );

Ambas funciones crean un vértice situado en el origen de coordenadas del mundo, es decir, x = y = z = 0.0. En el primer caso el nombre de la función termina en "3f" (3 floats). Esto significa que vas a especificar el vértice con 3 valores o variables de tipo real, o sea float. En cambio en el segundo caso tenemos "fv" (float vector). Estamos indicando a OpenGL que el vértice lo daremos mediante un array/vector de floats. Precisamente este es el array que defino justo antes de llamar a la función.

Trabajamos en 3D y especificamos las coordenadas del vértice en este orden: X, Y, Z. Si deseamos trabajar en 2D solo tenemos que hacer una coordenada igual a 0.0, normalmente la Z.

Ya que estamos puestos vamos a definir nuestro primer polígono, un triángulo. OpenGL tiene varios tipos definidos de manera que nos facilita la creación de polígonos simples. Vamos allá:

glBegin( GL_TRIANGLES );
    glVertex3f (-1.0, 0.0, 0.0);
    glVertex3f (1.0, 0.0, 0.0);
    glVertex3f (0.0, 1.0, 0.0);
glEnd( );


Este código crea un triángulo situado en el plano XY ya que observareis que los valores de Z son todos 0.0 para los tres vértices que lo forman. Sus tres vértices se encuentran en las posiciones ( -1.0, 0.0 ), ( 1.0, 0.0 ) y (0.0, 1.0 ) según la forma ( X, Y ).

Un polígono se encapsula entre las funciones glBegin y glEnd. El parámetro que recibe la primera sirve para decirle a OpenGL que tipo de polígono deseamos crear, en nuestro caso un triángulo. GL_TRIANGLES es una constante ya definida en la librería. Ya veréis como usaremos muchisimas constantes de este tipo para programar.

Por claridad es conveniente tabular (indentar) el código entre glBegin y glEnd tal y como veis en el ejemplo. Cualquier programa OpenGL que examinéis seguirá esta convención si esta bien estructurado.

Vamos a definir alguno de los atributos de nuestro triángulo, por ejemplo su color. Usamos:

glColor3f (0.5, 0.5, 0.5);

donde los tres valores (floats) que se le pasan a la función glColor son por orden, la cantidad de rojo (Red) que deseamos, la cantidad de verde (Green) y la cantidad de azul (Blue). Es el llamado sistema RGB que muchos conoceréis sobradamente. Aplicando una cantidad de cada color conseguimos el tono deseado (Teoría aditiva del color). Los valores de cada color deben estar entre 0.0 (No aplicar ese color) y 1.0 (Aplicar ese color en su máxima intensidad). Por tanto:

glColor3f (0.0, 0.0, 0.0); se corresponde con el color NEGRO

mientras que...

glColor3f (1.0, 1.0, 1.0); ¡se corresponde con el BLANCO!

y de esta manera si queremos definir un triángulo blanco obraremos asi:

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


de manera que primero especificamos el color y TODO lo que dibujemos a partir de este momento será de ese color, en este caso el triángulo.

Al ejecutar cada función glVertex, el vértice en cuestión "entra" en el pipeline y se traslada a la posición que hemos especificado para el. Entonces se "mapea" en el frame buffer (representación en memoria del plano de proyección 2D) pasando posteriormente a la pantalla de nuestro monitor. Así en este caso tenemos 3 vértices que sucesivamente entran en el pipeline uno detrás de otro.

Algo muy importante que debéis tener en cuenta. Cuando defináis un polígono a base de sus vértices deberéis seguir un orden concreto. Si no lo hacéis, OpenGL no asegura que la representación en pantalla sea la correcta. La convención es crear los vértices siguiendo el polígono según el sentido antihorario de las manecillas del reloj. Comprobad mi ejemplo sobre papel y veréis como esta definido siguiendo este criterio. OpenGL supone la cara "a dibujar" del polígono como la que se define de esta manera.

Respecto a las constantes que podemos usar en la función glBegin tenemos entre otras:



De hecho los tipos mas usados son los tres primeros mientras que el resto pueden sernos de ayuda en casos determinados. Ya lo iremos viendo...

Bueno creo que con este material concluyo este primer capitulo del curso. Espero que os sea de interés y sigáis leyendo en la próxima edición.
En el próximo capitulo ya seremos capaces de teclear un programa de iniciación y prueba. Por ahora es importante que todo os quede muy claro a nivel teórico pues sino mas adelante ¡os daréis con una pared!
También intentare mencionar aquellos web's que os pueden ser de mas interés en cuanto a este tema así como posible bibliografía a consultar.

¡Hasta ahora! ;)




AULA MACEDONIA
a
MACEDONIA Magazine