Está en la página 1de 44

Delphi en 3 Dimensiones

OpenGL

Autor:

Carlos García Trujillo


cgar1136@yahoo.com

Prohibida la reproducción total o parcial sin permiso explícito del autor.


Delphi en tres dimensiones

Un vistazo a las API’s de gráficos 3D

Bien, ahora hablaremos un poco acerca de las opciones que tenemos para realizar aplicaciones que
involucren gráficos 3D en Delphi, ya sean salva pantallas, juegos, demos multimedia, interfaces 3D, ó
hasta aplicaciones de simulaciones y de realidad virtual.
Así que mencionaremos las características mas relevantes de las dos API’s gráficas de mayor importancia
en el mundo de las computadoras personales: Direct3D y OpenGL.
¿Que por qué usar una de estas API’s?, Los motivos son varios : Ahorrarnos tiempo de trabajo, permitir
un amplio soporte de hardware y lograr código fácilmente portable.

Direct3D
Direct3D es el API 3D de Microsoft. Es un completo conjunto de servicios de gráficos 3D tanto de
transformaciones, iluminación y renderizado de escenas, así como transparente acceso a la aceleración
por hardware y una comprensible solución 3D para la nueva generación de PC’s.
Es la última adición al altamente popular conjunto de API’s Microsoft® DirectX™ de tecnología
multimedia. Este conjunto incluye las API ’s DirectDraw™, DirectSound™, DirectInput™ y
DirectPlay™.
Direct3D proporciona acceso a avanzadas capacidades gráficas de aceleradores 3D por vía Hardware,
tales como el z-buffering (El Z-buffer se usa para registrar la proximidad de un objeto al observador, y es
también crucial para el eliminado de superficies ocultas), antializado de líneas (reduce los bordes
escalonados en las líneas dibujadas sobre una pantalla), transparencias, efectos atmosféricos, y correcta
perspectiva del mapeado de texturas.
Entre las desventajas que podríamos enumerar de Direct3D es que es un API complicada y poco portable.
Sin embargo, si queremos que nuestras aplicaciones gráficas corran bajo Windows 9x soportando
aceleración de hardware en casi todas las tarjetas del mercado y al mismo tiempo funcionen sin tarjetas
aceleradoras o saquen provecho del MMX y los microprocesadores que vendrán sin tener que programar
las rutinas de rasterización por software aparte, la única opción es Direct3D.
Aquí cabe hacer mención que Direct3D soporta un juego de chips gráficos mucho más amplio que el
soportado por OpenGL, y esta es una de las razones por las que este sea tan popular en el área del
desarrollo de juegos para PC’s, y que muchas empresas de desarrollo de este tipo de aplicaciones basen
sus programas en esta API. Direct3D es el API elegida para portar juegos de consolas como Playstation a
PC.
Los programadores de Delphi tenemos mucho de donde escoger en cuanto a Direct3D, ya que existen en
Internet una gran cantidad de componentes y librerías Freeware que funcionan como interfaz entre
Delphi y DirectX.
Entre los componentes más relevantes que podemos mencionar respecto a DirectX con Delphi están los
famosos DelphiX de Hiroyuki Hori, y los agregados que se han escrito para esta librería, como el
TCollisionTester3DX de Henrik Fabricius, entre otros; y que decir además de la suite de componentes
Delphi Games Creator, la cual también se distribuye de manera gratuita y es una muy sencilla y práctica
interfaz entre Delphi y la gran mayoría de funcionalidades de DirectX.

OpenGL
OpenGL es una librería gráfica escrita originalmente en C que permite la manipulación de gráficos 3D a
todos los niveles. Esta librería se concibió para programar en máquinas nativas Silicon Graphics bajo el
nombre de GL (Graphics Library). Posteriormente se consideró la posibilidad de extenderla a cualquier
tipo de plataforma y asegurar así su portabilidad y extensibilidad de uso con lo que se llegó al término
Open Graphics Library, es decir, OpenGL.
La librería se ejecuta a la par con nuestro programa independientemente de la capacidad gráfica de la
máquina que usamos. Esto significa que la ejecución se dará por software a no ser que contemos con
hardware gráfico específico en nuestra máquina. Si contamos con tarjetas aceleradoras de vídeo,
tecnología MMX, aceleradoras 3D, pipelines gráficos implementados en placa, etc ... por supuesto
gozaremos de una ejecución muchísimo más rápida en tiempo real.
Así esta librería puede usarse bajo todo tipo de sistemas operativos e incluso usando una gran variedad de
lenguajes de programación. Podemos encontrar variantes de OpenGL para Windows 95/NT, Unix, Linux,

1
Delphi en tres dimensiones
Iris, Solaris, Java e incluso lenguajes de programación visuales como Visual Basic, Borland C++ Builder
y por supuesto Delphi.
En contraste con la antigua IRIS GL-library de SGI, OpenGL es por diseño independiente de plataformas
y sistemas operativos como ya lo mencionamos, y esto es un punto que podemos tomar en cuenta los
programadores de Delphi, pensando en que próximamente saldrá una versión de Delphi para Linux, en la
que obviamente no podremos contar con DirectX.
Además es perceptiva a la red, de manera que es posible separar nuestra aplicación OpenGL en un
servidor y un cliente que verdaderamente produzca los gráficos. Existe un protocolo para mover por la red
los comandos OpenGL entre el servidor y el cliente. Gracias a su independencia del sistema operativo, el
servidor y el cliente no tiene porque ejecutarse en el mismo tipo de plataforma, muy a menudo el servidor
será una supercomputadora ejecutando una compleja simulación y el cliente una simple estación de
trabajo mayormente dedicada a la visualización gráfica. OpenGL permite al desarrollador escribir
aplicaciones que se puedan desplegar en varias plataformas fácilmente.
Por encima de todo, OpenGL es una biblioteca estilizada de trazado de gráficos de alto rendimiento, hay
varias tarjetas gráficas aceleradoras y especializadas en 3D que implementan primitivas OpenGL a nivel
hardware. Hasta hace poco, estas avanzadas bibliotecas gráficas solían ser muy caras y solo estaban
disponibles para estaciones SGI u otras estaciones de trabajo UNIX. Las cosas están cambiando muy
deprisa y gracias a las generosas licencias y el kit de desarrollo de controladores de SGI, vamos a ver más
y más hardware OpenGL para usuarios de PC’s.
AL contrario de Direct3D, OpenGL es un API fuertemente portable y sencilla. Bueno, lo de sencilla es
más bien en comparación con Direct3D, ya que para poder comprender la mayor parte de las funciones de
OpenGL es necesario tener un poco de conocimientos de Álgebra Lineal, Geometría y un poco de Física,
aunque a final de cuentas no es tan difícil como pareciera.
OpenGL provee prácticamente de las mismas funcionalidades en cuanto a capacidades gráficas que
Direct3D, incluso en algunos aspectos se comporta de una manera más eficiente, y posee una serie de
características que facilitan al programador la tarea de construir la escena, como tal es el caso de las
Listas de Despliegue, que son una manera de almacenar comandos de dibujo en una lista para un trazado
posterior.
Los Programadores de Delphi tenemos un amplio panorama en frente nuestro respecto a OpenGL, ya que
de por sí las versiones de Delphi de 32 bits, ya incluyen la DLL OpenGL32 la cual es la versión de
OpenGL para Win32, además incluye una unidad llamada OpenGL.Pas la cual es la interfaz entre la DLL
y nuestras propias aplicaciones, incluso algunas versiones de Delphi traen también archivos de ayuda
respecto a esta API y sus múltiples funciones.
También debemos mencionar que existen muchos programadores de Delphi que han hecho valiosas
aportaciones al mundo de los gráficos, tales como: Mitchell E. James con su componente GLPanel, ó
Mike Lischke, el cual es un prominente miembro del grupo JEDI (Joint Endeavor of Delphi Innovators,
Grupo de Esfuerzos de Innovadores de Delphi), y que también ha escrito varias librerías para OpenGL en
Delphi, esto por mencionar solo a algunos, en fin existen muchos programadores reconocidos en este
ámbito.

Otras consideraciones adicionales


Está claro que en un futuro cercano las tarjetas aceleradoras 3D de hardware reemplazarán por completo
las actuales, permitiéndonos olvidarnos de las rutinas de rasterización por software. Pero también es claro
que saldrán mas versiones de Direct3D, incorporando mejoras y que no saldrán nuevas versiones de
OpenGL para Windows (al menos esa es la intención de Microsoft). Por otra parte tenemos los obscuros
planes de Microsoft de lanzar una consola basada en Direct3D y Windows CE en conjunción con SEGA y
de crear una nueva API trabajado conjuntamente con Silicon Graphics, que promete ser una fusión de
ambas APIs.
Por esto no debemos preocuparnos por un futuro lleno de conjeturas y ocuparnos por el presente. Nuestra
recomendación (si de algo sirve para aquellos que después de leer esto quedaron aun más confundidos) es
determinar el mercado de la aplicación que pretendemos realizar y el tiempo que se tiene para terminar el
proyecto, de esta forma podremos decidir por una complicada API con mercado amplio en las PC’s como
Direct3D; o bien una API sencilla con un mercado más restringido en las PC’s que nos permite la
posibilidad de portar código a casi cualquier plataforma de una forma mas sencilla como tal es el caso de
OpenGL.

2
Delphi en tres dimensiones

Estudiaremos más a fondo cada una de estas API’s (OpenGL y Direct3D), y presentaremos algunos
programas interesantes que podemos realizar con Delphi basándonos en estas tecnologías (como el
mostrado en la figura 1), así como algunas cuestiones relacionadas con la representación virtual de
objetos.

Figura 1. Un ejemplo de una Aplicación 3D hecha en Delphi usando tecnología OpenGL.

Introducción a la Programación de Gráficos con OpenGL

Construcción de Aplicaciones Gráficas. Matrices y Vectores. Inicialización


de Contextos y consideraciones adicionales.

Bien, ahora nos dedicaremos a estudiar específicamente el API de gráficos OpenGL. En el articulo del
número pasado hablábamos de las comparaciones entre OpenGL y Direct3D, y de lo que es un API de
gráficos solo en teoría, pero esta vez estudiaremos más a detalle y con ejemplos de código como se
construye una aplicación gráfica basada en OpenGL.
Siendo honestos, la programación de gráficos 3D no es un tema sencillo, ya que requiere de
conocimientos previos de análisis numérico, álgebra lineal, física y otros muchos temas extras, sin
embargo aquí trataremos de hacer esto lo más digerible posible para que todo mundo podamos entenderlo
sin tanto embrollo; aunque desafortunadamente, no hay forma de evitar la necesidad de tener
conocimientos sobre matrices, vectores, y un poco de geometría así que más conviene dedicar algo de
tiempo a estudiar estos temas si se quieren hacer este tipo de aplicaciones.

¿Como Funciona OpenGL?


OpenGL funciona a través de una serie de librerías DLL que suelen tener variados nombres, pero que a
final de cuentas cubren funciones muy específicas y son muy sencillas de identificar, ahora veremos
cuales son estas:
OpenGL.DLL (Open Graphics Library, Librería de Gráficos Abierta).- Esta podemos encontrarla también
con el nombre de OpenGL32.DLL (para el caso de los sistemas operativos de 32 bits) o bien simplemente
como GL.DLL . Esta es la librería principal, que contiene la gran mayoría de las funciones que aquí
utilizaremos. Las funciones contenidas en esta librería inician con las letras gl.

3
Introducción a la Programación de Gráficos con OpenGL

GLU.DLL (Graphics Utility Library, Librería de Utilerías de Gráficos).- También la podemos encontrar
como GLU32.DLL. Contiene funciones para objetos comunes a dibujar como esferas, donas, cilindros,
cubos, en fin, varias figuras ya predefinidas que pueden llegar a ser útiles en ciertos casos, así como
funciones para el manejo de la cámara entre muchas otras. Podremos identificar las funciones que
pertenecen a esta librería porque llevan antepuestas en su nombre las siglas glu.
GLUT.DLL (GL Utility Toolkit, Equipo de Herramientas de Utilería para el desarrollo de Gráficos).-
Esta también permite crear objetos complejos como GLU, aunque la principal función de esta librería es
permitir que los programas se vuelvan interactivos, o sea que permite la libre creación de ventanas, así
como el acceso al mouse y al teclado. Nosotros podríamos llegar a prescindir en ese aspecto de GLUT, ya
que Delphi nos ofrece de manera natural poder crear ventanas y responder a los diferentes eventos del
manejo del mouse y el teclado; sin embargo, muchos programadores (principalmente programadores de C
y C++) piensan que programar aplicaciones basadas en GLUT tiene muchísimas ventajas, ya que esto
ofrece independencia del sistema operativo, pues no es lo mismo crear una ventana basándose en las
API’s de Windows que crear una ventana en algún ambiente gráfico de Uníx o Linux, por esto si se usa
GLUT como manejador de ventanas no se tendría que rescribir el código para migrar la aplicación de
plataforma, ya que solo habría que conseguir la versión de GLUT para el ambiente gráfico deseado y
volver a compilar nuestro programa en el nuevo ambiente. Esto debemos pensarlo los programadores de
Delphi si deseamos usar OpenGL en la inminente nueva versión de Delphi para Linux. Las funciones
contenidas en esta librería empiezan con las letras glut.
Estas librerías se encuentran disponibles
en Internet, y se distribuyen de manera
gratuita en diferentes sitios,
principalmente se pueden encontrar en
el sitio oficial de OpenGL en
http://www.opengl.org , donde
seguramente encontrarán las librerías
para su sistema operativo en específico.
Otro componente es el llamado Frame
Buffer, que no es otra cosa, que el área
de memoria donde se construyen los
gráficos antes de mostrarlos al usuario,
es decir, que nuestro programa de
OpenGL escribe en esta área de
memoria, y automáticamente envía su
contenido a la pantalla una vez que la
escena está completamente construida.
La figura 1 muestra gráficamente como
esta constituida la estructura de
Figura 1. Niveles de Abstracción en OpenGL
abstracción de OpenGL.

Las Piedras angulares: las matrices y los vectores


Aquí es donde empieza el trabajo sucio de todo este asunto. Toda la geometría que se despliega en las
aplicaciones OpenGL está basada en los conceptos de Matrices y Vectores y las operaciones aritméticas
aplicables a estas estructuras.
En OpenGL existen básicamente 3 matrices principales:
Una matriz de proyección llamada GL_PROJECTION, la cual nos permite determinar la perspectiva que
usaremos para observar la escena generada, así como el tipo de proyección a usar (esto tiene que ver
mucho con cuestiones de óptica y de geometría, y abordaremos este tema en otra ocasi ón más
específicamente ya que es bastante extenso y complejo). Esta matriz tiene una gran relación con otro
concepto también muy interesante llamado clipping, que consiste en recortar u ocultar todo aquello que
“está pero no se ve”, es decir lo que queda fuera del foco de nuestra cámara o nuestro campo visual
activo, este es un proceso que se hace automáticamente una vez habiendo inicializado pertinentemente
esta matriz.
Una matriz de Modelado llamada GL_MODELVIEW, donde esta es la matriz que usaremos para aplicar a
nuestra escena operaciones de rotación, traslación o escalamiento, o bien para manipular la posición y

4
Introducción a la Programación de Gráficos con OpenGL

y orientación de la cámara, para


obtener así las animaciones. La
figura 2 muestra algunos ejemplos
de matrices de transformación
lineal para el caso de sistemas de 3
dimensiones.
Y una última matriz para el manejo
de Texturas llamada GL_TEXTURE,
sobre la cual también podemos
aplicar las transformaciones
lineales de rotación, traslación y
escalamiento para manipular las
Figura 2. Matrices Genéricas de Transformación Lineal para texturas a aplicar a las figuras de
Sistemas de 3 Dimensiones. nuestra escena, cuando estemos
mas avanzados explicaremos mas a
detalle el manejo de texturas en nuestros programas, pero debemos aprender a gatear antes de intentar
correr.
Podemos hacer uso de cada una de estas matrices mediante el procedimiento
GLMatrixMode(), el cual nos permite seleccionar una de estas matrices para su
configuración. Para inicializar con valores iniciales a cada matriz se invoca a un
procedimiento
l l a m a d o
GLLoadIdentity(), el Figura 3.
cual carga la La Matriz Identidad.
m a t r i z
identidad, donde la matriz identidad es
aquella que solo contiene valores de 1
en toda su diagonal (como se muestra
en la figura 3), lo cual hace que
multiplicar cualquier vector por esta
matriz nos dé como resultado al mismo
vector sin que haya sufrido ninguna
transformación. Ahora tal vez se
preguntarán “¿y de cual hierba hay que
fumar para saber como multiplicar
vectores por matrices?”, pues basta con
echar una mirada a cualquier libro de
Álgebra Lineal y ahí se explica el
oscuro procedimiento.
Figura 4. Secuencia de Transformación de Coordenadas. En la figura 4 podemos observar el
orden en como se aplican las matrices a
cada uno de los vectores, y como se transforman las coordenadas hasta llegar a las coordenadas reales de
la ventana.

Vectores de diversos tipos


Los vectores son un conjunto de valores que nos permiten definir dentro de nuestra escena, desde los
vértices de las figuras, hasta los colores, los materiales, y las luces entre otras muchas otras cosas. Existen
básicamente 2 formas de trabajar con vectores en OpenGL, trabajar con sus elementos como variables
independientes, ó manejarlos como una estructura de datos. Cuando trabajamos sus elementos de forma
independiente cada vector es descompuesto en 3 ó 4 valores según sea el caso. Y cuando se maneja como
una estructura estos valores están contenidos en una estructura de datos que bien puede ser un arreglo ó
un registro.
Decimos que los vectores pueden descomponerse en 3 o 4 valores, ya que en el caso de los colores estos
están compuestos por 3 valores que determinan el color, codificados en RGB, y un elemento extra en
algunos casos, que determina el nivel de transparencia para el objeto, cara ó vértice al cual corresponda

5
Introducción a la Programación de Gráficos con OpenGL

dicho color, a esta característica de transparencia se le conoce comúnmente como Alpha Blending. Todos
estos valores (en el caso específico de los colores) tienen un dominio que fluctúa en el rango de 0 a 1.
Los vectores están compuestos por números que pueden ser de tipo real, enteros, bytes, doubles, short,
etc... Aquí cabe señalar que OpenGL provee de sus propios tipos los cuales por convencionalismos
similares al que usamos en Delphi anteponiendo una T a las clases que usamos, en OpenGL se antepone
GL a la definición de Tipos que se utiliza, así por ejemplo GLFloat, es el equivalente del tipo Single en
Delphi, y así existen muchas equivalencias entre ambas tipologias (Ver recuadro: Tipos en OpenGL).
Para declarar un vector como un vértice descomponiendo sus elementos usaríamos la instrucción
GLVertex3f(X,Y,Z), donde X, Y y Z son variables ó constantes de tipo GLFloat, Real, ó Single. La parte final de la
instrucción que usamos: 3f , nos indica que el procedimiento recibe como parámetro 3 valores de punto
flotante. Así la instrucción GLVertex3i() funcionará exactamente igual solo que con valores enteros.
Cuando trabajamos los vectores como una estructura de datos lo que hacemos es pasar la dirección de
memoria de esta estructura en la llamada del procedimiento, usando el operador @. Por ejemplo: Si
suponemos una variable V de tipo Tvector, donde Tvector está definido como: Tvector = Array [0..2] of GLFloat;
podríamos usar la instrucción: GLVertex3fv(@V); para declarar el vértice a partir de este dato estructurado.
Observen como en estos casos las instrucciones para manejar estructuras terminan en: v.

¡En Delphi!, ¡En Delphi por favor!


Bueno, ahora realizaremos nuestro primer programa OpenGL en Delphi, pero para esto, a fin de hacer las
cosas mucho más sencillas y que no nos cueste tanto trabajo empezar, utilizaremos una adecuación a la
librería OpenGL.Pas (versión 1.2) de Delphi hecha por Mike Lischke, un alemán que ha hecho muchas
aportaciones al mundo de los gráficos en Delphi, y que provee en esta librería muchas funciones que
facilitan el proceso de inicialización de contextos, que nos permitirán entender este caso de una manera
muy sencilla. Tanto el código fuente que aquí veremos, como las librerías .pas que utilicemos podrán
descargarlas desde el sitio web de esta revista sin ningún problema.
Primero que nada echemos un vistazo al Listado 1, y ahora analizaremos bit por bit este código fuente, ó
al menos eso intentaremos.
unit Unit1;

interface

uses
Windows, Forms, OpenGL, Classes, ExtCtrls, Messages, Controls, StdCtrls,Graphics,
sysUtils, Dialogs;

type
TForm1 = class(TForm)
Timer1: TTimer;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormPaint(Sender: TObject);
procedure FormResize(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
{ Private declarations }
procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
public
{ Public declarations }
end;

var
Form1: TForm1;
RC : HGLRC;
Angulo : GLInt;

implementation

{$R *.DFM}

procedure TForm1.FormCreate(Sender: TObject);


begin
RC:=CreateRenderingContext(canvas.Handle,[opDoubleBuffered],32,0);
//Primero Creamos un contexto...
end;

6
Introducción a la Programación de Gráficos con OpenGL

procedure TForm1.FormDestroy(Sender: TObject);


begin
DestroyRenderingContext(RC); //Se libera el Contexto...
end;

procedure TForm1.FormPaint(Sender: TObject);


Var X,Y,Z:GLInt;
begin
ActivateRenderingContext(canvas.Handle,RC); // Se asocia el contexto con el
// manejador del Canvas...
glClearColor(0,0.2,0,0); // Ponemos un color Verde Oscuro de Fondo...
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); // Algo así como borrar la
Pizarra...
glMatrixMode(GL_MODELVIEW);
glLoadIdentity;
gluLookAt(0,0,3,0,0,0,0,1,0); // Posición y punto de vista de la cámara...

glRotatef(30,1,0,0); //Primero hacemos una rotación de 30 grados respecto


// a X para obtener perspectiva...
glRotatef(Angulo,0,1,0); //Se Rota la figura en el eje Y a partir de
//la variable Angulo...

GLPointSize(2.0); //Asignamos un tamaño de 2 pixeles para cada punto...

//Ahora con tres ciclos anidados dibujamos un cubo con puntos de colores variados...
GLBegin(GL_POINTS);
For x := -5 to 5 do
for y := -5 to 5 do
for z := -5 to 5 do
begin
GLColor3f(X,Y,Z);
GLVertex3f(X/10,Y/10,Z/10);
end;
GLEnd;

SwapBuffers(canvas.Handle); //Copiar el Back Buffer en el canvas del formulario...


DeactivateRenderingContext; //Libera el contexto...
end;

procedure TForm1.FormResize(Sender: TObject);


begin // Cuando se cambia de tamaño hay que actualizar el puerto de visión...
wglMakeCurrent(canvas.handle,RC); // Otra manera de hacer el contexto
//dibujable cuando este ya está creado...
glViewport(0,0,Width,Height); // Especificar un puerto de visión....
glMatrixMode(GL_PROJECTION); // Activar matriz de proyección...
glLoadIdentity; // Poner estado inicial en esta matriz...
gluPerspective(35,Width/Height,1,100); // Especificar Perspectiva ...
wglMakeCurrent(0,0); // Otra manera de liberar el contexto...
Refresh; // Redibujar la escena ...
end;

procedure TForm1.Timer1Timer(Sender: TObject);


begin
Inc(Angulo,4); //Rotamos el angulo de observación de la escena...
Angulo := Angulo mod 360;
Refresh; //y la volvemos a dibujar ...
end;

procedure TForm1.WMEraseBkgnd(var Message: TWMEraseBkgnd);


begin //Para borrar el fondo, y evitar el parpadeo ...
Message.Result:=1;
end;

end.

7
Introducción a la Programación de Gráficos con OpenGL

Como podemos observar en el listado, tenemos un formulario y un componente Timer, el formulario nos
servirá como la ventana en donde se mostrarán los gráficos; y el Timer será quien coordine nuestra
animación, en esta ocasión a fin de hacer las cosas más sencillas usaremos el Timer para esta finalidad,
pero ya veremos más adelante en artículos posteriores que es mucho mas eficiente utilizar hilos de
ejecución de alta prioridad para hacer esto, por ahora dejémoslo de ese tamaño.
Primeramente y como se deben de imaginar, necesitamos un lienzo en el cual plasmar nuestras “obras de
arte”, por lo cual necesitamos de procesos que nos permitan acceder directamente a algún objeto gráfico
que nos sirva como este lienzo. En este primer ejemplo usamos el Objeto Canvas de nuestro formulario,
pero en teoría podemos utilizar el Canvas de cualquier otra clase que contenga a este objeto (un PaintBox
por ejemplo). A este lienzo le llamaremos el contexto.
Como pueden darse cuenta tenemos una variable global llamada RC de tipo HGLRC, esta variable será la que
represente nuestro contexto dibujable. En el evento OnCreate del formulario hemos escrito una línea de
código que sirve para asociar nuestro contexto con el manejador (handle) del objeto Canvas del formulario,
lo cual hace que nuestras animaciones se desplieguen sobre este Canvas. Los parámetros que se pasan a
este procedimiento los estudiaremos a detalle en otra ocasión.
Del mismo modo en el Evento OnDestroy del formulario debemos escribir una línea de código que libere
nuestro contexto del Canvas, para evitar así apuntadores perdidos por ahí.
Ahora, las animaciones como han de suponer deben construirse escena por escena, como en las películas
de dibujos animados, con ligeras diferencias entre ellas para obtener calidad en nuestra animación, y a la
vez debemos hacer uso de algún evento para mostrar estas escenas; así que podemos utilizar el evento
OnPaint del formulario para irlas mostrando una por una. Como sabemos este evento se dispara cada vez
que Windows tiene que dibujar el formulario así que podemos ocuparlo para que dibuje en él lo que
nosotros deseamos mostrar.
En este evento es donde escribimos el código para generar las escenas que nos interesa mostrar, vemos
como con cada frame o escena, hacemos el contexto dibujable y al finalizar lo liberamos de nuevo. La
instrucción glclearcolor() recibe como parámetro un vector de 4 valores que determinarán el color con el que
se inicializará nuestro frame buffer antes de escribir en él, dando como resultado un color de fondo. La
instrucción GLClear() sirve para borrar de este frame buffer valores de escenas que hayan sido construidas
previamente a la actual. También podemos observar como hacemos uso de la matriz ModelView, y su
inicialización con GLLoadIdentity(), para poder aplicar las operaciones de rotación con las operaciones
GLRotate() a los vértices que luego se definen.
La operación gluLookAt() sirve para determinar un punto de observación (ó cámara) para la escena,
podríamos obtener el mismo efecto de rotación si en vez de transformar los vértices de la escena solo
movemos este punto de observación, es algo similar a lo que se hace en los clásicos juegos 3D de estilo
Doom. En posteriores artículos hablaremos concretamente de la cámara y su utilización en concreto.
Esta animación la construiremos utilizando solo puntos aislados, por lo que usamos la operación
GLPointSize() para determinar el número de píxeles reales que ocupará cada uno de nuestros puntos, en
nuestro caso 2. Y luego entre las declaraciones GLBegin() y GLEnd() que sirven para determinar los vértices de
una figura, llamamos a GLColor3f() y GLVertex3f() dentro de tres ciclos anidados para dibujar todos los vértices
con su correspondiente color. Nótese que se deben de declarar primero los colores que los vértices,
porque como veremos más adelante OpenGL funciona como una máquina de estados finitos, donde se
deben declarar los atributos antes de las figuras.

¿Y el resto?
Hasta ahora ya utilizamos la matriz de modelado para hacer las rotaciones, pero como vemos en el
evento OnResize hacemos uso de la matriz de proyecci ón para definir nuestro puerto de visión y
perspectiva. Entender como funciona realmente la matriz de proyección es un poco complicado y ya la
estudiaremos a su tiempo, por ahora solo debemos entender que el evento OnResize se dispara cada vez que
modificamos el tamaño de nuestra ventana, por lo cual debemos ajustar la escala de lo que estamos
observando para no perder las dimensiones, es por eso que debemos tenerlo presente. Observen como al
final del código para este evento usamos el método Refresh del TForm, lo cual obliga al formulario a volver a
dibujar la escena con la matriz de proyección ya ajustada. También cuando Windows trata de dibujar el
formulario después de haberlo creado se dispara OnResize, lo que nos garantiza que desde el principio
tendremos ajustada nuestra perspectiva sin la necesidad de modificar nosotros mismos el tamaño de
nuestra ventana. Una vez ejecutando este programa traten de modificar el tamaño ustedes mismos para
que vean como es que funciona esto.

8
Introducción a la Programación de Gráficos con OpenGL

El procedimiento del Timer lo único que hace es incrementar una variable global llamada Angulo y
volver a dibujar la escena; la operación mod que efectuamos a esta variable es para asegurarnos que su
dominio solo fluctuará entre los valores 0 y 359, que son los 360 grados de una circunferencia. Nosotros
en el evento OnPaint rotamos la escena conforme a esta variable lo que ocasiona que escena con escena
obtengamos nuestra figura en diferentes posiciones relativas, y con esto obtener la animación. El numero
de unidades que incrementemos a esta variable con cada escena será lo que determine la calidad de
nuestra animación a final de cuentas.
Ahora como estamos utilizando el Canvas del formulario como lienzo y continuamente estamos mandando
a redibujar la escena, forzamos a que Windows tenga que borrar el contenido del formulario anterior
antes de mostrar el nuevo contenido, lo cual ocasiona un continuo parpadeo en nuestra animación, cosa
que no es nada deseable. Por esto lo que tenemos que hacer es evitar que Windows se tome esta molestia,
de cualquier modo nosotros mismos estamos dibujando sobre esta ventana lo que se nos va antojando, así
que para que perder ese valioso tiempo.
Para evitar que Windows borre el contenido del formulario debemos utilizar un manejador de mensajes
que opere para el mensaje WM_ERASEBKGND el cual es enviado por Windows cada que se necesita borrar el
fondo de una ventana, para evitar que Windows tome alguna acción solo debemos devolver como
resultado un valor distinto de 0 para indicar que nosotros mismos haremos ese trabajo (en nuestro caso
devolvemos un 1). Los manejadores de mensajes en este tipo de aplicaciones suelen ser bastante útiles, ya
veremos después como responder a cambios en la paleta de colores y en la resolución de la pantalla por
citar algunos casos.
Y con esto es suficiente para poder compilar y observar nuestra primera animación con OpenGL (Figura
5), ¿sorprendente verdad?, con muy pocas líneas de código hemos conseguido resultados sorprendentes,
como ya es costumbre con los programas que hacemos con Delphi.
Ahora algunas consideraciones extras: Por alguna
extraña razón las aplicaciones que hacemos con
Delphi usando OpenGL ocasionan un conflicto
cuando las ejecutamos desde el IDE, por lo cual es
recomendable solo compilarlas y ejecutarlas desde
fuera del Ambiente de Desarrollo Integrado. Y una
cosa más, recordemos que OpenGL utiliza
aceleración por Hardware para optimizar el
renderizado de las escenas, pero cuando nuestro
Hardware no provee esas caracter ísticas de
aceleración, todo lo que debería de hacerse por
Hardware se hace por Software, así que no se
quejen si en su equipo las animaciones se ven
demasiado lentas, lo más seguro es que su
Hardware sea el culpable.
Bueno, hasta aquí llegamos en esta ocasión, porque
parece que tenemos suficiente con que
entretenernos por ahora, y todavía mucho por
estudiar en el futuro...
Figura 5.
Nuestra primera aplicación de OpenGL en Ejecución

Tipos en OpenGL
Debemos recordar que las librerías de OpenGL fueron escritas originalmente en C, así que conservan
muchas características de este lenguaje, entre ellas, la estructura de sus procedimientos, y los tipos que se
utilizan.
Aunque en los tipos de OpenGL se anteponen las siglas GL, estos guardan una marcada equivalencia con
los tipos genéricos de C, así GLFloat sería el equivalente al tipo float de C, y GLint, al tipo int. Y los rangos
del dominio alcanzado por cada uno de estos tipos depende de su correspondiente equivalencia en C.
Aunque OpenGL también provee de ciertos tipos de datos estructurados para el manejo tanto de matrices
como de vectores, y generalmente estos tipos ya vienen en las librerías de OpenGL para Delphi con su
respectiva equivalencia en tipos de Object Pascal.

9
Líneas con OpenGL en dos dimensiones
Proyección Ortogonal 2D. Construcción de gráficos con Líneas.
Antializado. Algoritmos recursivos de Fractales.

OpenGL no se utiliza solamente para generar impresionantes gráficos 3D, sino como veremos a
continuación también puede utilizarse para hacer gráficas en 2 dimensiones. En esta ocasión trabajaremos
con los diferentes tipos de líneas que nos ofrece OpenGL; y a fin de hacer este tema todavía mas
interesante veremos un extra bastante útil e impresionante: Los Fractales, un concepto matemático muy
utilizado en la graficación por computadora.

Visualización 2D
Aunque aquí tratemos el tema de la visualización 2D en OpenGL, es importante recordar que este es un
API gráfico de 3 dimensiones; así que si queremos utilizarlo para hacer representaciones 2D tenemos que
hacer un pequeño truco. Como ustedes han de suponer lo que hemos de hacer es graficar todo lo que
deseemos en un plano, para obtener así la apariencia de que estamos trabajando con 2 dimensiones
aunque realmente sigamos trabajando en 3.
Entonces la visualización 2D se basa en tomar un área rectangular de nuestro mundo 3D y transferir su
contenido a nuestro despliegue. El rectángulo de visualización está en el plano z=0. Por default si no se
específica el volumen de visualización se construye un rectángulo de 2x2, donde las coordenadas de este
rectángulo quedan como: ((-1.0,-1.0),(-1.0,1.0),(1.0,-1.0),(1.0,1.0)), estas coordenadas, claro, en el plano
z=0.
En este caso cualquier coordenada que definamos como (x,y,z), será proyectada sobre el plano de
visualización z=0 como (x,y,0), y si esta coordenada entra dentro de nuestro rectángulo será mostrada en
nuestro despliegue (ver figura 1). Si es nuestra intención modificar las coordenadas de nuestro rectángulo
de visualización debemos definirlas mediante
la instrucción glOrtho2D(), con la cual declaramos
los valores de 2 coordenadas que determinarán
el área del rectángulo, es decir, la esquina
superior izquierda y la inferior derecha.

¿Qué son los Fractales?


Para comprender lo que es un fractal,
hagamos un pequeño ejercicio: tomemos una
hoja de papel y dibujemos sobre ella un
segmento rectilíneo. La geometría Euclidiana
nos dice que se trata de una recta y por tanto
tiene una sola dimensión (su longitud). Ahora
extendamos el segmento rectilíneo haciendo
trazos de un lado a otro sin que éstos se crucen
hasta llenar toda la hoja. La geometr ía
Euclidiana nos dice que todavía se trata de una
línea y que tiene una sola dimensión, pero
Figura 1. Proyección Ortogonal en 2D en el plano Z=0 nuestra intuición nos dice que si la línea llena
completamente el plano debe tener más de una
dimensión, si bien no llegará a tener dos. Esto implica que nuestro dibujo debe tener una dimensión
fraccionaria.
Este concepto tan simple comenzó toda una revolución en las matemáticas. Varios matemáticos de fines
del siglo XIX y principios del XX propusieron la existencia de una dimensión fraccionaria y no debiera
sorprendernos que sus colegas los tildaran de locos, pues esa idea aparentemente tan aberrante atentaba
contra la noción de dimensiones enteras que todos conocemos. Hubieron de pasar cerca de 50 años para
que un matemático se tomara en serio tan aventuradas teorías. En 1975 el Dr. Benoît Mandelbrot, acuñó
un nombre para estas figuras que tienen una dimensión fraccionaria. Las llamó fractales, término que
derivó del adjetivo latino fractus, que significa "irregular" o "interrumpido". Mandelbrot afirma que así
como las figuras geométricas convencionales son la forma natural de representar objetos hechos por el
hombre (cuadrados, círculos, triángulos, etc.), los fractales son la forma de representar objetos que
existen en la Naturaleza. De tal forma, los fractales tienen una doble importancia: como objetos artísticos

10
Líneas con OpenGL en dos dimensiones
(por poseer una singular belleza) y como medio para representar escenarios naturales. Es más, los
fractales están presentes en las expresiones utilizadas para describir fenómenos tan variados como la
predicción del clima, el flujo turbulento de un líquido, el crecimiento o decrecimiento de una población y,
hasta para comprimir imágenes.
La idea principal detrás de los fractales es que algunas imágenes pueden ser producidas repitiendo una
fracción de sí mismas. Tomemos como ejemplo un árbol: si observamos una de sus ramas, caeremos en la
cuenta de que asemeja un árbol en pequeño. Valiéndonos de esta curiosa propiedad (llamada auto-
similitud o "self-similarity" en inglés) puede dibujarse una figura compleja repitiendo una imagen mucho
más simple. A esta repetición se le llama recursividad. Llevando la recursión a niveles más elevados
llegaremos al concepto de Sistemas de Funciones Iteradas o IFS (por sus siglas en inglés): tomemos un
punto y movámoslo por la pantalla, trasladémoslo, rotémoslo y ajustemos su escala aleatoriamente. Esto
nos permitirá obtener imágenes relativamente complejas a partir de patrones sumamente simples, si
iteramos un número adecuado de veces.
Todos estos conceptos aunque de entrada parezcan complejos, veremos que su aplicación no lo es tanto y
cómo podemos nosotros usar fractales para representar todo un paisaje, con objetos de la naturaleza de
una manera muy sencilla y simple.

Tipos de Lineas en OpenGL


Existen básicamente 3 tipos de líneas en OpenGL: Líneas sencillas, Líneas en ciclo y Líneas con
patrones. Como habíamos visto en el número anterior la definición de figuras en OpenGL se hace con las
instrucciones GLBegin() y GLEnd(), donde a GLBegin() se le pasa como parámetro el tipo de figura que nos
interesa dibujar con los vértices definidos entre estas instrucciones (el ejemplo pasado lo hicimos solo con
puntos usando GL_POINTS), así que ahora estudiaremos como hacer líneas con ellas.
Para hacer líneas sencillas usamos GL_LINES, con esta instrucción como parámetro para GLBegin() indicamos
a OpenGL que dibuje una línea que una cada dos vértices que nosotros definamos. Algo muy similar a
cuando trabajábamos con el Objeto Canvas y usábamos las instrucciones MoveTo(x,y) y LineTo(x,y), ¿lo
recuerdan?... ¡ah... que tiempos aquellos! ...
Las líneas en ciclo nos sirven para definir contornos. Para hacer este tipo de líneas usamos GL_LINE_LOOP,
cuando utilizamos esta instrucción unimos todos los vértices que definimos con una misma línea en el
orden en que hayan sido estos definidos, y al mismo tiempo se unen el último vértice definido con el
primero, cerrando así la figura. Esto es muy útil para dibujar contornos de polígonos (ya sean regulares o
irregulares), por ejemplo.
¿Recuerdan cuando trabajábamos con el Canvas que podíamos cambiar el estilo de la pluma para dibujar
líneas punteadas o con diversos patrones?. Pues en OpenGL también se pueden declarar varios patrones
para dibujar líneas. Para ello usamos en nuestro ejemplo la instrucción GL_LINE_STRIP como parámetro, éste
permite escribir una secuencia de líneas unidas vértice a vértice, pero a diferencia de GL_LINE_LOOP el
último vértice no se une con el primero. Ya veremos sobre la marcha como declaramos los patrones a
utilizar.

Calidad en el trazado de líneas


También, como en el número anterior vimos la instrucción GLPointSize() para hacer más grande el tamaño
de los puntos que dibujamos, en el caso de las líneas podemos hacer que OpenGL las dibuje más gruesas
de cómo las presenta originalmente con la instrucción glLineWidth() , pasándole como parámetro un número
entero que indique el número de pixeles que ocupará el ancho de las líneas a dibujar.
Volviendo a los añejos recuerdos del Canvas... ¿Recuerdan como cuando dibujábamos líneas con cierto
ángulo diagonal, se dibujaban en forma de escalerita?; esto se debe en gran medida a que los algoritmos
clásicos de trazado de líneas como el método de Bresenham ó el del DDA (Análisis Diferencial Digital)
entre otros, se limitan solo a encontrar aquellos pixeles que puedan representar a la línea en cuestión, pero
no se ocupan de la presentación que esta tendrá para el usuario. En OpenGL existe un concepto muy
interesante al respecto llamado antialiazing o antializado. El antializado consiste precisamente en
eliminar esos escalonamientos que se presentan tanto en las líneas como en los contornos de las figuras a
representar para obtener así figuras más realistas, que no se vean tan “poligonales”. En esta ocasión
estudiaremos el antializado en cuanto a líneas.

11
Líneas con OpenGL en dos dimensiones
¡Vamos al Código!
Bueno, vamos directamente al ejemplo que aquí presentamos. Echemos una mirada al Listado 1, y a ver
que podemos aprender de esto. Por ahora pasemos por alto los procedimientos que hemos definido para
dibujar los fractales y centrémonos en los eventos de los objetos.

unit Unit1;

interface

uses
Windows, Forms, OpenGL, Classes, ExtCtrls, Messages, Controls, StdCtrls,Graphics,
sysUtils, Dialogs;

type
TForm1 = class(TForm)
Timer1: TTimer;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormPaint(Sender: TObject);
procedure FormResize(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
{ Private declarations }
procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
public
{ Public declarations }
end;

var
Form1: TForm1;
RC : HGLRC;
Angulo : GLInt;
Radio : GLFloat = 0;

implementation

{$R *.DFM}

procedure Arbol(x,y,t,teta,w:real);
var x1,y1,t1:real;
begin
if t > 0.01 then {condición de paro}
begin
glbegin(GL_LINES);
glVertex2f(x,y);
x1 := x -t * cos(teta);
y1 := y -t * sin(teta);
glVertex2f(x1,y1);
glend;
t1 := t /1.7;
{Se llama recursivamente al sistema}
Arbol(x1,y1,t1,teta-w,w);
Arbol(x1,y1,t1,teta,w);
Arbol(x1,y1,t1,teta+w,w);
end;
end;

Procedure Star(X,Y,t,Teta:Real);
var X1,Y1:real;
begin
if Teta < 10 then {Nuestra condición de paro}
begin
glEnable (GL_LINE_STIPPLE); //Habilitamos el uso de los patrones...

glPushAttrib (GL_LINE_BIT); //Salvamos el estado de las lineas...

12
Líneas con OpenGL en dos dimensiones
glLineStipple (1, $5555); //Declaramos el patrón a utilizar...
glbegin(GL_LINE_STRIP);
glVertex2f(x,y); //Todas las lineas tienen el mismo centro...
x1 := x -t * cos(teta);
y1 := y -t * sin(teta);
glVertex2f(x1,y1);
glend;
glPopAttrib (); //Recuperamos el estado anterior...

{Llamamos recusivamente al sistema}


Star(X,Y,t,Teta+0.25);
glDisable (GL_LINE_STIPPLE); //Inhibimos los patrones...
end;
end;

Procedure Luna;
var i:integer;
Coseno,Seno:GLFloat;
begin
GLBegin(GL_LINE_LOOP);
For i := 0 to 100 do
begin
Coseno := Cos(i*2*PI/100);
Seno := Sin(i*2*PI/100);
GLVertex2f(Coseno,Seno);
end;
GLEnd;
end;

procedure TForm1.FormCreate(Sender: TObject);


begin
RC:=CreateRenderingContext(canvas.Handle,[opDoubleBuffered],32,0); //Primero Creamos
un contexto...
end;

procedure TForm1.FormDestroy(Sender: TObject);


begin
DestroyRenderingContext(RC); //Se libera el Contexto...
end;

procedure TForm1.FormPaint(Sender: TObject);


begin
ActivateRenderingContext(canvas.Handle,RC); // Se asocia el contexto con el manejador
del Canvas...

glClearColor(0,0,0,0); // Ponemos un color Verde Oscuro de Fondo...

glClear(GL_COLOR_BUFFER_BIT); // Algo así como borrar la Pizarra...

glMatrixMode(GL_MODELVIEW);
glLoadIdentity;
gluOrtho2D (-1.0, 1.0, 0.0, 1.5); //Para trabajar en 2 dimensiones...

{Dibujamos algunos arbolitos felices...}


glPushMatrix(); //Salvamos la matriz del ambiente...
glLineWidth (1);
glcolor3f(0,0,1);
glrotatef(270,0,0,1); //Se incorporan los arboles...
Arbol(0,0,0.3,0,0.5);
Arbol(0,0.2,0.2,0,0.25);
Arbol(0,-0.15,0.15,0,radio);
Arbol(0,-0.65,0.25,0,2.1);
Arbol(0,0.5,0.2,0,radio);
Arbol(0,0.7,0.35,0,0.55);
Arbol(0,0.85,0.18,0,0.25);
glPopMatrix(); //Recuperamos el ambiente...

{Ponemos algunas estrellitas Felices...}

13
Líneas con OpenGL en dos dimensiones
glpushmatrix();
glLineWidth (1);
glscalef(0.5,0.5,0);
GlColor3f(0.5,1,0);
Star(0.4,2,0.2*sin(Angulo),0);
GlColor3f(1,1,0);
Star(0.8,2.25,0.2*sin(Angulo),0);
GlColor3f(1,0,0);
Star(-0.3,2.4,0.2*sin(Angulo),0);
GlColor3f(1,1,1);
Star(1.5,2.4,0.2*sin(Angulo),0);
glpopmatrix();

GLPushMatrix;
glEnable (GL_LINE_SMOOTH); //Habilitamos el Antializado para las Lineas
glLineWidth (5); //Modificamos el ancho de las lineas...
GLColor3f(1,1,1);
GLScalef(0.15,0.15,0);
GLTranslatef(-4,7,0);
LUNA;
glDisable (GL_LINE_SMOOTH); //inhibimos el antializado...
GLPopMatrix;

SwapBuffers(canvas.Handle); //Copiar el Back Buffer en el canvas del formulario...


DeactivateRenderingContext; //Libera el contexto...

radio := radio - 0.03; //Variamos la variable radio


if radio <= -6.3 then radio := 0; //para animar los arbolitos...
end;

procedure TForm1.FormResize(Sender: TObject);


begin // Cuando se cambia de tamaño hay que actualizar el puerto de visión...
wglMakeCurrent(canvas.handle,RC); // Otra manera de hacer el contexto dibujable cuando
este ya está creado...
glViewport(0,0,Width,Height); // Especificar un puerto de visión....
glMatrixMode(GL_PROJECTION); // Activar matriz de proyección...
glLoadIdentity; // Poner estado inicial en esta matriz...
wglMakeCurrent(0,0); // Otra manera de liberar el contexto...
Refresh; // Redibujar la escena ...
end;

procedure TForm1.Timer1Timer(Sender: TObject);


begin
Inc(Angulo,4); //Rotamos el angulo...
Angulo := Angulo mod 365;
Refresh; //y volvemos a dibujar la escena...
end;

procedure TForm1.WMEraseBkgnd(var Message: TWMEraseBkgnd);


begin //Para borrar el fondo, y evitar el parpadeo ...
Message.Result:=1;
end;

end.

Como pueden darse cuenta la estructura de este programa es muy similar a la del programa que
estudiamos en el número anterior, ya que seguimos usando la misma versión de OpenGL.Pas, y el método
de inicialización de contextos no cambia para nada.
Las diferencias las empezamos a encontrar en el evento OnPaint(). Como vemos en la instrucción GLClear(),
solo pasamos como parámetro el Buffer de Color (GL_COLOR_BUFFER_BIT) y ya no el buffer de profundidad
(GL_DEPTH_BUFFER_BIT) como en el ejemplo pasado; la raz ón es que ahora sólo trabajaremos en 2
dimensiones, así que no hay relación de profundidad entre las figuras que dibujaremos, por lo que no
necesitamos limpiar este buffer.
Luego encontramos la instrucción gluOrtho2D (-1.0, 1.0, 0.0, 1.5); con la que definimos un rectángulo de
visualización cuya esquina superior izquierda es (-1.0,1.5) y la esquina inferior derecha es (1.0,0.0).

14
Líneas con OpenGL en dos dimensiones
Ahora aquí vienen unas instrucciones que tal vez les resulten nuevas en este momento glPushMatrix() y
glPopMatrix().
Suponemos que todos aquí hemos trabajado alguna vez con estructuras de datos de tipo Pila, y
que entendemos los procedimientos de inserción y eliminación de elementos de esta estructura. Bueno,
pues estas instrucciones trabajan con una pila de matrices y sirven para hacer respaldos de la matriz de
transformaciones que luego podemos recuperar. La finalidad de hacer estos respaldos es para poder hacer
ciertas transformaciones sobre figuras específicas sin que éstas afecten al resto de las figuras en nuestra
escena. Esto es verdaderamente útil como veremos más adelante.
Delimitemos ahora lo que pretendemos realizar con este código. Bien, pues lo que dibujaremos es: “un
paisaje en el campo durante una noche de Eclipse de Luna”, ¿qué les parece?, no me negarán que es un
título bastante artístico para nuestra “obra de arte”; y todo usando solo líneas.
Nuestro paisaje estará compuesto por árboles, estrellas, y la luna eclipsada, por supuesto. Así que
necesitamos procedimientos que dibujen cada una de estas figuras. Tanto los árboles como las estrellas
las construiremos usando fractales con procedimientos recursivos.
Los fractales que usaremos serán del tipo de los sistemas S->eS*, lo que significa que serán sistemas que
contendrán elementos y a la vez a alguno ó algunos subsistemas similares. En nuestro caso, los elementos
serán Líneas y los subsistemas serán llamadas recursivas al mismo procedimiento. Basándonos en este
tipo de sistemas podemos construir diversos tipos de figuras de la naturaleza, como árboles, estrellas,
piedras, nubes, e incluso podríamos llegar a representar el sistema solar.
Analicemos primeramente el procedimiento para construir árboles. La llamada a este procedimiento tiene
los siguientes parámetros: Arbol(x,y,t,teta,w:real); la idea con este procedimiento es dibujar a partir del punto
(x,y), un segmento de línea de longitud t e inclinación teta. Y después volver a llamar recursivamente a
este procedimiento, pasando como parámetros el extremo final del segmento de recta generado y los
parámetros t y teta con valores modificados para hacer simétrica la figura. A fin de cuentas lo que
hacemos es dibujar un arbolito más pequeño al final de cada segmento de recta. Observen como ahora
utilizamos glVertex2f() para declarar nuestros vértices ya que trabajamos en dos dimensiones. El parámetro
w nos servirá para determinar el tipo de árbol que deseamos dibujar, pues como veremos, este simple
procedimiento nos permite dibujar Cipreses, Jacarandas y Pinos. ¿A poco no es sorprendente?.
Algo similar pasa con el procedimiento para dibujar las estrellas, solo que aquí todos los segmentos de
recta tienen un mismo origen, el centro de la estrella. Así que lo que variamos es el ángulo de inclinación
de cada segmento para obtener así la figura final.

Las líneas punteadas


Siguiendo con el análisis sobre el procedimiento Star(), que dibuja las estrellas, vemos que aquí es donde
usamos los patrones con las líneas.
La función glLineStipple() especifica el patrón usado para el punteado, por ejemplo, si usáramos el patrón
$AAAA (este es un número en hexadecimal), en binario este número es 1000100010001000, 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 3 bits apagados y 1 encendido. El 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ía 9 bits apagados, 3 bits encendidos, 9 bits apagados, 3 bits encendidos, 9
bits apagados, 3 bits encendidos y por último 9 bits apagados y 3 bits encendidos. Jugando con factores y
patrones binarios, uno puede dibujar todo tipo de líneas punteadas complicadas. En nuestro ejemplo
usamos el patrón $5555, con un factor de 1, lo que nos da un patrón uniforme de 1 pixel apagado por 1
encendido. ¡Hagan cuentas!.
Un detalle más: hemos puesto el trazado de la línea punteada entre dos sentencias de push y pop de
atributos. ¿Recuerdan cuando en el artículo del número anterior dijimos que OpenGL es una máquina de
estados?, en futuros artículos veremos con más detalle estas operaciones de push y pop para el caso de los
atributos, pero brevemente lo que estamos haciendo con la primera sentencia glPushAttrib(GL_LINE_BIT) es
guardar en una pila el valor actual 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

15
Líneas con OpenGL en dos dimensiones
aplicación. Push y pop nos evitan este molesto trabajo.

¿Y la luna y el antializado?
Bien, pues la luna la construiremos como si fuera un polígono con GL_LINE_LOOP, recuerden que a final de
cuentas un circulo podemos definirlo como un polígono que tiene muchos lados, tantos que no se llegan a
distinguir. En nuestro caso hacemos uno de 100 lados. Como lo que pretendemos es dibujar una luna
eclipsada, entonces solo debemos preocuparnos de dibujar el contorno de esta luna, y aprovechamos que
el color con el que borramos la pizarra es el negro para evitar dibujar el resto.
Ahora, como decíamos, dibujar tantas líneas en tan diferentes ángulos ocasiona que nuestro circulo no
luzca bien definido, por lo que aquí si es conveniente utilizar el antializado.
El antializado para el caso de las líneas se habilita con la instrucción glEnable(GL_LINE_SMOOTH); y se inhibe
con glDisable(GL_LINE_SMOOTH); Lo inhabilitamos porque no nos interesa dibujar toda nuestra escena con
antializado, si no solo la luna. Pueden probar que sucede cuando ignoran estas líneas de código, ya verán
que así no parece luna.

Para terminar un poco de animación


Por último, puesto que ya hemos utilizado los diferentes tipos de líneas disponibles en OpenGL, vamos a
agregar un poco de animación a nuestra escena. Si mal no recordamos las estrellas “titilan”, por lo que
usamos una variable para simular este efecto, así como una variable adicional para mostrar con 2 árboles
pequeños, como es que se transforman las figuras fractales para generar los diferentes tipos de árboles
que tenemos en nuestra escena, modificando el par ámetro w que habíamos mencionado. Estas
animaciones están controladas nuevamente por un componente Timer.
Por ahora eso es todo, ya tenemos nuestra escena construida (Ver Figura 2); que se diviertan
experimentando con las líneas y los fractales. Recuerden que pueden descargar el código fuente de este
programa y las unidades extras que utilizamos directamente desde el sitio web.

Figura 2. Nuestro Programa de Fractales y Líneas

16
Transformaciones Lineales en OpenGL

Las transformaciones lineales, aunque pudieran parecer un concepto un


poco oscuro, no lo son tanto, y son la base para poder generar
animaciones con API’s gráficas.

Bien, esta vez abordaremos el tema de las transformaciones lineales; ciertamente es un tanto difícil
hablar de términos matemáticos y de geometría de manera coloquial, sin embargo aquí trataremos de
hacerlo lo más ligero posible para que todos nos enteremos, aunque de cualquier modo es recomendable
que cualquiera que se vea interesado en estos temas profundice en los libros de Álgebra Lineal, donde
muchas dudas seguramente serán resueltas (¿o tal vez ampliadas?).

Empecemos con la teoría...


Bueno, creo que deberemos empezar explicando el escabroso procedimiento mediante el cual se aplican
operaciones sobre matrices y vectores.¿Recuerdan cuando hace algunos meses hablábamos de que toda la
geometría que se despliega en las aplicaciones de OpenGL está basada en los conceptos de Matrices y
Vectores y las operaciones aritméticas aplicables a estas estructuras?, bien, pues ahora vamo s a
sumergirnos un poco en lo que respecta a estas operaciones.
Primeramente vamos a entender una matriz como una tabla que contiene valores, y a un vector como un
caso especial que puede representar un renglón o una columna de dicha tabla. Cuando multiplicamos un
vector renglón por un vector columna lo que obtenemos es un valor al cual le llamamos “escalar”, La
Figura 1 muestra como obtenemos el valor escalar a partir de la multiplicación de 2 vectores.
Las matrices por su parte pueden ser
multiplicadas por otras matrices o
por vectores. En el caso de la
multiplicación de matrices con
matrices, el procedimiento es
Figura 1. Multiplicación de un Vector por otro vector relativamente sencillo: lo único que
hay que hacer es multiplicar cada
uno de los respectivos renglones y columnas de los argumentos como se muestra en la Figura 2,
obteniendo los valores escalares de estas multiplicaciones de vectores, es decir que cuando multiplicamos

Figura 2. Multiplicación de una matriz por otra matriz

una matriz por otra matriz, lo que obtenemos es una nueva matriz formada por estos valores escalares.
Ahora solo hay que tener en cuenta que las dimensiones de ambos argumentos deben de corresponder
para poder hacer esta operación, es decir que el número de columnas de la primera matriz corresponda
con el número de renglones de la segunda, ¿Lo ven?, de otro modo no se podrían hacer las operaciones
sobre los vectores.
Y para multiplicar matrices por vectores lo que hacemos es tratar el vector en forma de renglón y

17
Transformaciones Lineales en OpenGL
multiplicarlo por cada una de las columnas de la matriz, como se muestra en la figura 3. Así que aquí
podemos apreciar que al multiplicar un vector por una matriz, lo que obtenemos es un nuevo vector
“transformado”.

Figura 3. Multiplicación de un Vector por una matriz

Bueno, ¿y eso que?


Ahora, a nosotros lo que nos interesa es multiplicar vectores de 3 valores por matrices de 3*3 para
obtener las transformaciones en tres dimensiones, recordemos un articulo pasado donde mostrábamos las
diferentes matrices de transformación lineal para sistemas de 3 dimensiones, así que en la figura 4
podemos ver como podemos obtener vectores transformados a partir de la multiplicación del vector

Figura 4. Matrices de rotación y escalamiento por vectores

original por las matrices de Traslación y Escalamiento, haciendo un resumen de las operaciones
necesarias para esto. Como ven en la figura, hemos agregado un elemento extra tanto al vector como a la
matriz, para poder tener una matriz “cuadrada” de 4*4, esto porque las matrices de transformación para
sistemas de 3 dimensiones son de este tamaño, y por lo tanto necesitamos ajustar el vector de coordenadas
con un valor extra, pero que este valor en realidad no altera en nada el resultado sobre el vector
transformado.
La principal ventaja que tenemos de usar las matrices de transformación, es que si deseamos aplicar
diferentes transformaciones a un mismo vector podemos multiplicar antes las matrices correspondientes,
y después obtener el producto por el vector de la siguiente manera:
| X'| = (| X |*| A |) *| B |
puede aplicarse también como:
| X'| = | X |* ( | A |*| B | )
Donde X’ representa al vector transformado, X al vector original, y A y B son 2 matrices de
transformación distintas.
Y si en el caso anterior tomamos:
| K | = | A |*| B |
entonces podemos sustituir K en la segunda expresión como:

18
Transformaciones Lineales en OpenGL
| X'| = | X |*| K |
¿Lo ven?... Podemos aplicar primero todas las transformaciones a una sola matriz, y luego multiplicar por
esta a todos los vectores o vértices que forman a nuestra figura, y así tener más claro el procedimiento;
bien, pues esta es la filosofía que se sigue en el modelado con OpenGL, todas las transformaciones que
definimos se definen en la matriz de modelado (GlModelView) y se aplican a cada uno de los vértices que
nosotros definimos con glvertex...(); ¿No es tan complicado o si?
Las figuras 5 a la 7 muestran gráficamente los
efectos que se ocasionan sobre un cuerpo al aplicarle
las matrices de transformación tanto de Traslación,
Rotación, Escalamiento y Reflexión.
Ahora, el orden en como se apliquen estas
operaciones importa y mucho...Como bien sabemos
en la aritmética común la multiplicación tiene la
propiedad conmutativa, es decir, que el orden de los
factores no altera el producto; sin embargo en cuanto
a la multiplicación de matrices de transformación
lineal, no se aplica esta propiedad.
Así que obtendremos diferentes resultados
dependiendo de cual transformación apliquemos
primero. La figura 8 muestra claramente los efectos
obtenidos de aplicar las transformaciones de
Rotación y Traslación a un mismo cuerpo en
diferente orden. Y como podemos observar son
Figura 5. Traslación de un cuerpo bastante diferentes.

¿Y como representamos las


transformaciones en OpenGL?
Bien, una vez que nosotros definimos que usaremos
una determinada matriz, ya sea la de modelado, la de
proyección ó la de Texturas, podemos inicializarla
usando la operación glLoadIdentity(); la cual carga la
matriz identidad en ella. Donde tenemos que la
matriz identidad es una matriz que no ejerce ninguna
modificación cuando multiplicamos un vector por
ella, es decir que si I es una matriz identidad y V es
un vector cualquiera tenemos que:
|V| * |I| = |V|
Las rotaciones se definen con la operación glRotate...();
Figura 6. Rotación de un cuerpo
La cual recibe 4 parámetros, el primero es el número
de grados que nos interesa rotar el cuerpo o la escena,
y los otros 3 determinan el nivel en que se verá
afectada la operación respecto a cada uno de los 3
ejes X, Y y Z. Por ejemplo, si nos interesara hacer
una rotación de 30 grados solo sobre el eje X,
pasaríamos un 30 en el primer parámetro, un1 en el
segundo, y 0’s en los restantes.
La operación de Traslación se define con glTranslate...();
esta recibe 3 parámetros, que son el número de
unidades que deseamos trasladar la figura en cada
uno de los 3 ejes. También podemos pasar como
parámetros valores negativos, y as í obtener
traslaciones en sentidos contrarios.
Figura 7. Escalamiento de un cuerpo Y el escalamiento se define con: glScale...(); este
también recibe 3 parámetros que definen el grado de

19
Transformaciones Lineales en OpenGL

Figura 8. Diferencias en el orden de la aplicación de las transformaciones

escalamiento que deseamos aplicar a cada uno de los 3 ejes. Si en este caso usamos glScalef(); podemos
pasar valores fraccionarios como parámetros y poder así hacer escalamientos más exactos.
Recordemos que las operaciones deben aplicarse antes de la definición de los vértices de las figuras que
se verán afectadas por estas transformaciones, para que así estos vectores sean multiplicados por esta
matriz ya definida.
Si deseamos aplicar una transformación solo a un determinado cuerpo de nuestra escena, debemos definir
primero las trasformaciones que afectarán a toda la escena y los vértices que no se verán afectados por la
transformación nueva, y luego salvar la matriz en una pila con la instrucción glPushMatrix(); Aplicar la
transformación nueva, definir los vértices de la figura(s) afectada(s); y posteriormente, si nos interesa
seguir definiendo vértices que no deberían ser afectados por esta transformación antes recuperar el estado
anterior de la matriz desde la pila con glPopMatrix(); . Estas instrucciones que manipulan la pila de matrices
son muy útiles, y aunque cuesta un poco entender como funcionan en un principio, son muy prácticas una
vez que uno ha captado la idea.

Las primitivas geométricas

Figura 9. Algunas primitivas de graficación en OpenGL

20
Transformaciones Lineales en OpenGL
Hasta ahora en los ejemplos que habíamos visto solo habíamos usado líneas y puntos para crear nuestras
figuras, pero en OpenGL existen muchísimas primitivas de figuras que ya iremos viendo a su tiempo. La
figura 9 nos muestra algunas de estas primitivas y cual es su comportamiento.
Hay algunos apuntes que hacer también al respecto pero eso lo reservaremos para otra ocasión, por ahora
basta con saber que existen y que podemos hacer uso de ellas.
Ahora veamos un ejemplo para estudiar de manera práctica las transformaciones lineales junto con
algunas cosas nuevas.

Vamos al ejemplo!...
Demos un vistazo al Listado 1, y veamos que podemos aprender de este.

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ExtCtrls, OpenGL, StdCtrls, ComCtrls;

type
TForm1 = class(TForm)
Panel1: TPanel;
Timer1: TTimer;
TrackBar1: TTrackBar;
Label1: TLabel;
TrackBar2: TTrackBar;
Label2: TLabel;
TrackBar3: TTrackBar;
Label3: TLabel;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Dibuja(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;
Angle: integer;
implementation

{$R *.DFM}

procedure cubo; // Para dibujar el cubo de nuestra escena ...


begin
glbegin(GL_QUADS);

glcolor3f(10,10,10);
glVertex3f(1.0, 1.0, 1.0);
glcolor3f(-10,10,10);
glVertex3f(-1.0, 1.0, 1.0);
glcolor3f(-10,-10,10);
glVertex3f(-1.0, -1.0, 1.0);
glcolor3f(10,-10,10);
glVertex3f(1.0, -1.0, 1.0);

glcolor3f(10,10,-10);
glVertex3f(1.0, 1.0, -1.0);
glcolor3f(10,-10,-10);
glVertex3f(1.0, -1.0, -1.0);
glcolor3f(-10,-10,-10);
glVertex3f(-1.0, -1.0, -1.0);
glcolor3f(-10,10,-10);
glVertex3f(-1.0, 1.0, -1.0);

21
Transformaciones Lineales en OpenGL
glcolor3f(-10,10,10);
glVertex3f(-1.0, 1.0, 1.0);
glcolor3f(-10,10,-10);
glVertex3f(-1.0, 1.0, -1.0);
glcolor3f(-10,-10,-10);
glVertex3f(-1.0, -1.0, -1.0);
glcolor3f(-10,-10,10);
glVertex3f(-1.0, -1.0, 1.0);

glcolor3f(10,10,10);
glVertex3f(1.0, 1.0, 1.0);
glcolor3f(10,-10,10);
glVertex3f(1.0, -1.0, 1.0);
glcolor3f(10,-10,-10);
glVertex3f(1.0, -1.0, -1.0);
glcolor3f(10,10,-10);
glVertex3f(1.0, 1.0, -1.0);

glcolor3f(-10,10,-10);
glVertex3f(-1.0, 1.0, -1.0);
glcolor3f(-10,10,10);
glVertex3f(-1.0, 1.0, 1.0);
glcolor3f(10,10,10);
glVertex3f(1.0, 1.0, 1.0);
glcolor3f(10,10,-10);
glVertex3f(1.0, 1.0, -1.0);

glcolor3f(-10,-10,-10);
glVertex3f(-1.0, -1.0, -1.0);
glcolor3f(10,-10,-10);
glVertex3f(1.0, -1.0, -1.0);
glcolor3f(10,-10,10);
glVertex3f(1.0, -1.0, 1.0);
glcolor3f(-10,-10,10);
glVertex3f(-1.0, -1.0, 1.0);
glEnd;
end;

procedure setupPixelFormat(DC:HDC); //Para definir el formato de pixeles a usar...


const
pfd:TPIXELFORMATDESCRIPTOR = (
nSize:sizeof(TPIXELFORMATDESCRIPTOR); // tamaño
nVersion:1; // versión
dwFlags:PFD_SUPPORT_OPENGL or PFD_DRAW_TO_WINDOW or
PFD_DOUBLEBUFFER; // usamos doble-buffer
iPixelType:PFD_TYPE_RGBA; // Tipo de color
cColorBits:16; // paleta a usar...
cRedBits:0; cRedShift:0; // bits de color
cGreenBits:0; cGreenShift:0;
cBlueBits:0; cBlueShift:0;
cAlphaBits:0; cAlphaShift:0; // no usamos buffer alfa...
cAccumBits: 0;
cAccumRedBits: 0; // ni tampoco
cAccumGreenBits: 0; // valores de acumulación
cAccumBlueBits: 0;
cAccumAlphaBits: 0;
cDepthBits:16; // buffer de profundidad
cStencilBits:0; // no usamos Stencil Buffer =:-(
cAuxBuffers:0; // ni buffers auxiliares
iLayerType:PFD_MAIN_PLANE;
bReserved: 0;
dwLayerMask: 0;
dwVisibleMask: 0;
dwDamageMask: 0;
);
var pixelFormat:integer;
begin
pixelFormat := ChoosePixelFormat(DC, @pfd);
if (pixelFormat = 0) then begin
MessageBox(WindowFromDC(DC), 'Fallo en la elección del formato', 'Error',
MB_ICONERROR or MB_OK);

22
Transformaciones Lineales en OpenGL
exit;
end;
if (SetPixelFormat(DC, pixelFormat, @pfd) <> TRUE) then begin
MessageBox(WindowFromDC(DC), 'Fallo en la asignación del formato', 'Error',
MB_ICONERROR or MB_OK);
exit;
end;
end;

procedure TForm1.FormCreate(Sender: TObject);


var DC:HDC;
RC:HGLRC;
begin
DC:=GetDC(Panel1.Handle);
SetupPixelFormat(DC); //Esta vez nos conectamos
RC:=wglCreateContext(DC); //con el manejador del Panel ...
wglMakeCurrent(DC, RC);

glViewport(0, 0, Form1.Panel1.Width, Form1.Panel1.Height);


glMatrixMode(GL_PROJECTION); // Activamos la matriz de proyección y
glLoadIdentity; // cargamos la matriz identidad
gluPerspective(35,Form1.Panel1.Width/Form1.Panel1.Height,1,100); //Definimos la perspectiva
end;

procedure TForm1.FormDestroy(Sender: TObject);


var DC:HDC;
RC:HGLRC;
begin
DC := wglGetCurrentDC; //Cuando destruyamos la forma
RC := wglGetCurrentContext; //debemos liberar el contexto del Panel
wglMakeCurrent(0, 0);
if (RC<>0) then wglDeleteContext(RC);
if (DC<>0) then ReleaseDC(Panel1.Handle, DC);
end;

procedure TForm1.Dibuja(Sender: TObject);


begin
glClearColor(0,0,0.3,1); // el color del fondo
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); // Borramos la pizarra...
glMatrixMode(GL_MODELVIEW); // activamos la matriz de transformaciones y
glLoadIdentity; // Cargamos la matriz identidad...
gluLookAt(0,0,6,0,0,-10,0,1,0); // definimos un punto de observación...
glTranslatef(0,0,Trackbar2.Position); // Trasladamos la figura respecto al eje Z...
glRotatef(TrackBar1.Position,0,1,0); // Rotamos sobre el eje Y....
glRotatef(30,1,0,0); // Aplicamos otra pequeña rotación para obtener perspectiva...
glScalef(TrackBar3.Position/20,1,1); //Escalamos respecto al eje X...
glEnable(GL_DEPTH_TEST); // Habilitamos la prueba de profundidad....
Cubo; //Dibujamos el cubo...
SwapBuffers(wglGetCurrentDC); //Vaciamos el frame-buffer en el panel...
Timer1.Enabled := False;
end;

end.

Como podemos ver, hemos definido al principio un procedimiento que se encargará de dibujar el cubo
que mostraremos en nuestra escena. Para esto lo que hicimos fue utilizar la primitiva GL_QUADS, la cual lo
que hace es dibujar un cuadro con cada 4 vértices que nosotros definimos. Y como ven también hemos
definido un color diferente para cada uno de los 8 vértices que definen las aristas de nuestro cubo. Debido
a que no hemos definido ningún modelado de despliegue para nuestra escena (todavía no llegamos a
tanto), lo que se hace es tomar el default, y hacer un degradado entre los vértices de la figura, por eso es
que vemos que las caras de nuestra figura se encuentran iluminadas por un degradado de colores bastante
llamativo. Por ahora hemos definido la construcción de este cubo en un procedimiento separado, pero más
adelante veremos como usar una herramienta bastante útil llamada las “listas de despliegue”, las cuales
tienen una función semejante a esto que hacemos con este procedimiento.

23
Transformaciones Lineales en OpenGL
En los ejemplos anteriores habíamos usado a los formularios como lienzos, ¿recuerdan?...pero pudiera
suceder que no nos interesara utilizar toda la superficie del formulario para desplegar nuestras escenas,
sino solo una porción de este, y el resto del espacio utilizarlo para colocar controles que manipulen la
escena, ¿no?...Bueno, pues esta vez veremos como hacer eso; para este ejemplo usaremos como lienzo a
un objeto de tipo Tpanel.
Como ya no usaremos la versión de OpenGL.pas de Lischke, ahora nos sumergiremos un poco más en las
profundidades de cómo inicializamos un contexto “a pie”, y entre las cosas que debemos definir “a pie”
es el formato de pixeles que usaremos en el control (en este caso el Panel); para esto hemos definido el
procedimiento: procedure setupPixelFormat(DC:HDC) el cual nos servirá para definir este formato. Como ven
hemos definido una constante pfd de tipo TPIXELFORMATDESCRIPTOR la cual contendrá toda la información
acerca del formato que elijamos para nuestro control OpenGL, y esa es la que pasamos como parámetro a
la función ChoosePixelFormat(); la cual recibe además el manejador del control donde desplegaremos la escena.
Sobre los valores que componen el tipo de datos TPIXELFORMATDESCRIPTOR no hablaremos mucho por ahora,
ya que a estas alturas hay cosas que todavía ni siquiera hemos mencionado, mas adelante hablaremos de
cosas como los diferentes buffer’s, así como de las paletas de colores a utilizar.
De igual manera que en los anteriores ejemplos el contexto lo inicializamos cuando creamos la forma y lo
liberamos al destruirla.
Como pueden observar tenemos un procedimiento llamado dibuja(), el cual es el que se encarga de vaciar el
frame-buffer en el Panel, y dentro de este procedimiento, hemos definido las transformaciones de
rotación, traslación y escalamiento en función de propiedades de 3 componentes de tipo TtrackBar las cuales
se encargar de volver a dibujar la escena cada vez que sus posiciones cambian, ya que tenemos asociado
este procedimiento al evento OnChange de estas componentes.
En este ejemplo, hacemos rotaciones sobre el eje Y, traslaciones sobre el eje Z, y escalamientos sobre el
eje X. Esto con el fin de poder apreciar como es que funcionan estas transformaciones. Y el orden en
como aplicamos las transformaciones es: primero la traslación, luego la rotación y por último el
escalamiento.
El resto del programa no difiere demasiado de los que ya hemos visto anteriormente, así que no creo que
requiera de mayores explicaciones. Además los comentarios en el código explican cada línea a detalle.
Como quiera aún tenemos mucho que aprender en el futuro.
Que se diviertan con esto y hasta pronto.

24
Interacción de gráficos con dispositivos

Cómo podemos obtener programas gráficos interactivos de manera


simple usando componentes creados en tiempo de ejecución

Para esta ocasión hemos preparado un pequeño articulo sobre cómo podemos crear programas que
resulten interactivos para el usuario, es decir, hacer a este participar en la ejecución, y no limitarlo solo a
observar los resultados de todos los cálculos matemáticos internos que deben realizarse para presentar
gráficos en tres dimensiones.
¿Familiar, no?... Este es el concepto que se utiliza frecuent emente en los juegos de video para
computadora... Son muy comunes hoy en día y algunos de ellos son verdaderamente sorprendentes por la
calidad de imágenes que muestran y lo interesantes que suelen ser. Pues bien, ahora empezaremos con un
paso elemental, que es el como interactuar con los eventos causados por el usuario de nuestra aplicación,
esta vez veremos como responder a los eventos del ratón y el teclado.

¿Cómo hacemos cuerpos complejos en OpenGL?


Bien, OpenGL provee además de las primitivas básicas que ya hemos visto hasta ahora, algunos cuerpos
complejos que suelen ser de utilidad en algunas ocasiones. La ventaja de estos cuerpos es que la manera
en como son construidos y mostrados al usuario está muy optimizada, para hacer las aplicaciones bastante
rápidas. Además de que nos evitan estar ideando la manera de cómo crear procedimientos para construir
estos objetos que también suelen ser de uso bastante común.
Los que en esta ocasión estudiaremos se encuentran en la librería GLU32.DLL. ¿Recuerdan cuando en
algún articulo pasado mencionábamos que en esta librería se encontraban algunas figuras ya
predefinidas?. Pues bien, así es, aquí podemos encontrar procedimientos para construir: cilindros, conos
(que en realidad vienen a ser una especialización de un cilindro), discos y esferas.
Los nombres de los procedimientos que crean estos cuerpos comienzan con las siglas glu, y reciben
diferentes parámetros cada uno, dependiendo del tipo de cuerpo que deseamos construir. Por ejemplo,
para construir un cilindro lo que indicamos como parámetros, son: la altura que deberá tener este cilindro,
así como la longitud de los 2 radios que lo componen. Y para el caso de las esferas, debemos indicar el
radio, y el número de paralelos y meridianos que la compondrán (dependiendo de el número de paralelos
y meridianos que definamos será la calidad en la definición de nuestra esfera).
Para cada uno de los objetos que creemos con las funciones de glu, debemos asignar un identificador de
tipo gluQuadricObj, el cual es en realidad un apuntador a una zona de memoria donde se construirá el objeto
en cuestión. No es tan complicado, ya lo veremos mas claro en el ejemplo.
En otra ocasión veremos como crear nuestros propios cuerpos tridimensionales sin demasiado esfuerzo,
por ahora nos limitaremos a usar los predefinidos de OpenGL.

Veamos el ejemplo, ¿cómo respondemos a los eventos?


Bien, en el ejemplo del número anterior veíamos como podíamos crear un contexto de OpenGL sobre un
componente Tpanel, ¿recuerdan?, pero bueno, en realidad podemos crear este tipo de contextos en
cualquier componente que esté derivado de la clase TCustomControl.
De todos es sabido que Delphi es un poderoso lenguaje orientado a objetos, y pues estaría mal que
nosotros no utilizáramos esta gran ventaja a la hora de programar gráficos, ¿no?. Supongamos que lo que
ahora nos interesa es poder desplegar nuestra escena 3D sobre una porción del formulario, y responder a
los eventos del ratón solo en esa determinada región. Una solución sería usar un componente Tpanel
como en el ejemplo anterior, pero bueno, vamos a hacer un componente especial para lo que ahora nos
interesa específicamente.
Como pueden ver en el “Listado 1”, hemos definido una clase llamada GLControl derivada de
TCustomControl, la cual nos servirá precisamente como nuestro lienzo y controlador de eventos.
Como pueden observar, en el evento OnCreate del formulario asignamos las coordenadas que tendrá nuestro
componente, inicializamos el contexto OpenGL, y por último asociamos nuestros procedimientos para
responder a los eventos del ratón.
Como pueden ver volvemos a hacer uso de la función SetupPixelFormat(), la cual usaremos siempre que
creemos un contexto “a medida”. También usamos dos procedimientos extras: Draw(), el cual se encarga de
dibujar y mostrar la escena, y GLInit() que se encarga de configurar la perspectiva y el ambiente de la

25
Interacción de gráficos con dispositivos

procedure TForm1.FormPaint(Sender: TObject);


begin
Draw;
end;

procedure TForm1.GLMouseDown(Sender: TObject; Button: TMouseButton;


Shift: TShiftState; X, Y: Integer);
begin
Label1.Caption:=Format('abajo: %d, %d',[X,Y]);
mStartX:=X;
mStartY:=Y;
end;

procedure TForm1.GLMouseUp(Sender: TObject; Button: TMouseButton;


Shift: TShiftState; X, Y: Integer);
begin
Label1.Caption:=Format('arriba: %d, %d',[X,Y]);
//Ajustamos los nuevos ángulos de visualización...
alpha:=alpha+dx;
dx:=0.0;
betha:=betha+dy;
dy:=0.0;
end;

procedure TForm1.GLMouseMove(Sender: TObject; Shift: TShiftState; X,


Y: Integer);
begin
if (ssLeft in Shift) then begin
Label1.Caption:=Format('moviendo: %d, %d',[X,Y]);
dx:=(x-mStartX)/2;
dy:=(y-mStartY)/2;
Draw;
end;
end;

procedure TForm1.ComboBox1Change(Sender: TObject);


begin
// Con esto seleccionamos el tipo de cuerpo que deseamos dibujar...
cuerpo :=TCuerpo(ComboBox1.ItemIndex);
Draw;
end;

end.
Listado 1

escena. Cabe señalar aquí, que en este ejemplo tuvimos que definir algunas luces a fin de que se pudieran
distinguir con claridad los cuerpos que habíamos creado, pero ya tocaremos el tema de la iluminación más
a fondo en otra ocasión.

Bueno, pero ¿cómo hago que un cuerpo se mueva tridimensionalmente


de acuerdo al movimiento del ratón?
Bien, para eso debemos utilizar algunas variables auxiliares para lograr el efecto.

26
Interacción de gráficos con dispositivos
Primeramente aclaremos que lo que haremos aquí será solo simular la rotación de un cuerpo respecto a
dos ejes dimensionales, es decir solamente rotaciones en los ejes X e Y; sin embargo puede quedárseles de
tarea como podríamos además trasladar el cuerpo respecto al eje Z (para obtener efectos de zoom)
arrastrando por ejemplo el ratón con el botón derecho presionado. De cualquier modo aquí simulamos ese
efecto con un TTrackBar como en el ejemplo del número pasado.
Usaremos dos variables para controlar los ángulos que se verán afectados para cada uno de los ejes, otras
dos para controlar el desplazamiento del movimiento con respecto a estos ángulos, y por último otras dos
para determinar el punto de inicio del movimiento. ¿Sencillo, no?. Pensemos en que estaremos
desplazando el ratón sobre una superficie bidimensional, al cual le daremos un comportamiento
tridimensional, algo que en teoría no es tan simple.
Ahora veamos lo que debemos de programar para ir modificando estas variables:
Primero veamos que en el evento OnMouseDown() lo único que debemos hacer es almacenar las coordenadas
donde el usuario ha pulsado el botón del ratón (ahora no estamos haciendo distinción de que botón ha
sido presionado).
En el evento OnMouseMove() lo que hacemos es verificar si está presionado el botón izquierdo del ratón,
en cuyo caso debemos modificar el desplazamiento sobre los ángulos con respecto a las coordenadas de
inicio del movimiento, y las coordenadas sobre las que estamos desplazándonos. En este caso podemos
aplicar un factor de suavizado, el cual es un número que nos servirá para hacer menos bruscos los

Figura 10

27
Interacción de gráficos con dispositivos
movimientos en sistemas más rápidos. Para aplicar este factor solo debemos dividir las diferencias de las
coordenadas entre este factor, en nuestro caso usamos un factor de dos unidades.
Por último, en el evento OnMouseUp(), solo nos resta sumar a los ángulos de referencia los desplazamientos
obtenidos en OnMouseMove() e inicializar dichos desplazamientos nuevamente a cero.
Ahora veamos como dibujamos la escena respecto a estas variables con instrucciones de rotación de
OpenGL:
La instrucción glRotatef(alpha+dx,0.0,1.0,0.0); nos permite rotar el cuerpo respecto al eje Y, usando como
referencia las variables de inclinación y desplazamiento del eje X. Y por el otro lado, la instrucción
glRotatef(betha+dy,1.0,0.0,0.0); hace lo propio con el otro eje. Y esto produce a final de cuentas el efecto de que
los cuerpos definidos parecen “seguir” el movimiento del ratón sobre nuestro control.
Debemos tener en cuenta que todo esto lo hemos hecho para poder tener una referencia en dos ejes para
un solo cuerpo, pero con un poco más de ciencia podríamos generalizar el método para aplicarlo a todos
los objetos de una escena, o solamente a unos cuantos de ellos.
Del mismo modo podríamos programar procedimientos que respondieran a eventos generados por el
teclado asignándolos a los eventos OnKeyDown(), OnKeyUp() y OnKeyPress() de nuestro control. Cabe señalar que
en nuestro ejemplo también podemos obtener respuesta a las teclas de cursor de nuestro teclado, siempre
y cuando el TrackBar tenga el foco en nuestra aplicación.
Una vez habiendo realizado todo esto tenemos nuestra aplicación ya terminada (Ver figura 1). Esto es
solo un comienzo, pero es un muy buen comienzo.

Consideraciones adicionales con otros dispositivos


Hasta aquí ya hemos visto como podemos interactuar de una manera muy simple con el ratón y el
teclado, sin embargo existen en la red diversos componentes que permiten también recibir eventos desde
un Joystick por ejemplo, en cuyo caso, no existe mucha diferencia con lo visto hasta ahora.
Y en caso de que nos interesara guardar una escena como un archivo de imagen, ó bien imprimirla
tampoco hay mayor problema, ya que basta con usar la famosa función CopyRect() del objeto Canvas
asociado con el control sobre el cual desplegamos nuestra escena y con ese rectángulo podemos hacer lo
que se nos pegue en gana.
Bueno, hasta aquí llegamos por esta vez, que tengan suerte, y que aprendan mucho de esto.
Hasta Pronto.

28
Tipos de proyecciones y manejo de la cámara en
OpenGL

Como pasar de coordenadas 3D a coordenadas


de dos dimensiones, y como interactuar con la
cámara.

Bien, en esta ocasión vamos a tratar de dar continuidad al artículo del mes pasado hablando de cómo
podemos interactuar en la escena que mostramos a traves de los dispositivos de entrada como el teclado y
el ratón, y esta vez daremos un ejemplo un poco más completo al respecto.
También hablaremos más específicamente sobre las técnicas de proyección que nos brinda OpenGL,
y como es que trabajan en teoría cada una de ellas. Este es un concepto que de algún modo resulta
interesante conocer, ya que puede llegar a ser sumamente útil a la hora de crear nuestras propias
aplicaciones gráficas, pues de esto puede depender la calidad de nuestra presentación.

¿Cómo pasar de coordenadas 3D a coordenadas 2D en nuestra ventana?


Esta es una muy buena pregunta, ¿Cómo hacer que aparezcan en nuestra ventana, que es 2D, puntos
que en realidad deberán tener una relación de profundidad entre sí?... Bien, pues la respuesta no es tan
compleja si la pensamos un poquito, lo que hay que hacer es proyectar toda nuestra geometría en un
plano, al que llamaremos Plano de Proyección.
Como todos los planos, este representa una región 2D, y por convención suele situarse en Z = 0, es
decir en el plano XY. Y primero debemos hacer la proyección para después hacer las conversiones de
tipos de coordenadas. Entendamos esto de las conversiones de tipos:
Cuando trabajamos con coordenadas 3D, es común que las definamos como números de punto
flotante, y es perfectamente válido hacer esto, si no ¡imagínense como funcionarían programas de diseño
como por ejemplo Autocad usando solo aritmética entera!...Sin embargo, nuestro monitor está mapeado
en pixeles que tienen cada uno dos coordenadas enteras, por esta razón es que debemos hacer una
conversión que elimine los decimales sin que estos dejen de ser significativos en la proyección. Por eso es
que hay que convertir forzosamente al final a enteros.
Existen muchos tipos de proyecciones y podríamos escribir libros completos al respecto, pero aquí
nos limitaremos a ver solo dos que resultan algo significativas para nuestros propósitos, la proyección
Ortográfica, y la proyección en Perspectiva. Ambas están clasificadas dentro de las proyecciones
planares.
Y básicamente ambas consisten en definir una línea de visión que va desde el observador hasta el
objeto observado, y trazar una serie de líneas que cortarán el plano de proyección que hayamos definido
generando así la figura 2D que finalmente aparecerá en nuestra pantalla.

La proyección Ortográfica
Este tipo de proyección está clasificada como un tipo de proyección paralela, y esto es porque
consiste en trazar líneas perpendiculares al plano de proyección que resultan ser paralelas entre sí. Este es
una tipo de proyección relativamente sencilla, que suele ser bastante útil en ciertos casos. La figura 1
muestra gráficamente como se construyen las líneas paralelas en esta proyección, podemos ver como se
obtiene un dibujo de una de las caras de nuestro cubo en nuestro plano de proyección y así obtenemos lo

Figura 1. Proyección Ortográfica

29
Tipos de proyecciones y manejo de la cámara en
OpenGL

que veremos en pantalla.

Este tipo de proyección no preserva las dimensiones reales de los objetos según la distancia hasta
ellos. Con eso queremos decir que aún acercándose ó alejándose de ellos no se producen cambios de
tamaño con lo cual el realismo no es total. Se utiliza tradicionalmente en proyectos de ingeniería del tipo
de programas CAD/CAM. Quién no ha visto una herramienta de modelación 3D (AutoCAD, 3DSMax,
etc...) sin las 3 correspondientes vistas ortográficas desde ambos laterales y desde arriba. La cuarta vista
suele ser una perspectiva que comentaremos a continuación.
Los parámetros a especificar en este caso son las dimensiones de una caja (Xmin, Xmax, Ymin,
Ymax, Zmin, Zmax). A los valores MAX y MIN también se les denomina FAR o BACK y NEAR o
FRONT. ¿Qué por qué una caja?... pues porque también hay que definir nuestro campo visual, donde
campo visual es la "cantidad" de mundo que la cámara alcanza a ver. Porque imagínense que ustedes
tienen “Ojo de águila” y alcanzan a ver mucho más de lo que un topo medio ciego que apenas distingue
su propia nariz, ¡Ah verdad!... pues esta caja determina qué tan buena vista tendremos en nuestra
aplicación.
En OpenGL la podemos definir de la siguiente forma:
Cargamos la matriz de proyección, la inicializamos y usamos glOrtho(x_min, x_max, y_min, y_max, z_min, z_max);
ó bien: gluOrtho2D(x_min, x_max, y_min, y_max); si se aceptan los valores por defecto de Zmin = -1.0 y Zmax = 1.0.
Ya habíamos hecho alguna vez en artículos anteriores una proyección Ortogonal, cuando
hablábamos de cómo hacer gráficas en 2D y en esa ocasión usamos gluOrtho2D()... ¡¡revisen sus apuntes!!

La proyección en Perspectiva
Esta es la que definitivamente utilizaremos para dotar del mayor realismo a nuestras aplicaciones.
Las proyecciones perspectiva preservan las dimensiones reales de los objetos si nos acercamos ó alejamos
de ellos. Por tanto el efecto visual es justo el que necesitamos en casos de apariencia real. Veamos la
figura 2 para tener una mejor idea de cómo funciona.

Figura 2. Proyección en Perspectiva

En la figura tenemos una proyección perspectiva con un sólo COP o punto de fuga. Todas las líneas
ó proyectores emanan de él y se dirigen hasta el objeto intersectando el plano de proyección. Como
podemos observar en este caso los proyectores no son paralelos entre ellos.
En OpenGL definimos esta proyección cargando la matriz de proyección, inicializandola, y usando
gluPerspective( FOV en grados, Relación de Aspecto, z_near, z_far); donde los parámetros que hay que pasarle a este
método son los siguientes:
El FOV se refiere al ángulo de abertura inicial, que determinará nuestro campo de visión. La
relación de aspecto es el cociente entre la anchura y la altura del plano de proyección deseado, es decir

30
Tipos de proyecciones y manejo de la cámara en
OpenGL

de nuestra ventana, ó el control que usaremos para desplegar nuestra escena. Y los valores Near y Far
son los mismos que en la proyección ortográfica, es decir que determinan qué tan lejos y tan cerca
seremos capaces de ver.

Podemos definir también una proyección perspectiva usando la función: glFrustrum(x_min, x_max, y_min,
y_max, z_min, z_max);. En este caso OpenGL calculará el "frustrum piramidal", es decir, el volumen de
visualización perspectiva más idóneo. Son simplemente dos maneras distintas de acabar creando lo
mismo. A cada uno con su elección...¿no creen?...
Las distancias NEAR y FAR son siempre positivas y medidas desde el COP hasta esos planos, que
serán obviamente paralelos al plano Z = 0. Dado que la cámara apunta por defecto en la dirección
negativa de Z, el plano de front (near) estará realmente situado en z = -zmin mientras que el de back (far)
estará en z = -zmax.
Lo hemos dado más o menos por asumido pero vamos a recordar un poco. Pensemos que no hay que
proyectar toda la geometría existente sino tan sólo la que ve la cámara desde su posición y orientación.
Tenemos que acotar en este sentido y es por eso que además de definir un plano de proyección y una
forma de proyectar, creamos un volumen de visualización finito con unas fronteras bien marcadas. Todo
aquello que no se encuentre dentro del volumen será rechazado y no proyectado dado que no debe verse.
Para tenerlo más claro pensemos en nuestros propios ojos. ¿Acaso vemos todo a nuestro
alrededor?...no...tenemos una cierta apertura visual, de unos 60º a 80º y más allá de eso nada de nada, solo
conjeturas.
No somos como las palomas con los ojos en los laterales de la cabeza o como una gran lente angular
de una cámara. De hecho tan sólo variando la apertura visual (FOV) observaremos como es más o menos
la cantidad de escena que entra en pantalla.

Ahora vamos con la camara...


La cámara son nuestros ojos virtuales. Todo lo que ella vea será proyectado, discretizado y
finalmente mostrado en la ventana de nuestra aplicación. Podemos imaginar que de la cámara emana el
volumen de visualización de forma que se traslada con ella. Los parámetros a definir en cuanto a la
cámara son:
Posición XYZ en el mundo 3D. Al igual que un objeto cualquiera, la cámara debe posicionarse. En
un juego arcade 3D típico la cámara nos da la visión frontal del mundo y se va moviendo a nuestro antojo
con las teclas o el ratón. Cada vez que modificamos la posición varían las coordenadas XYZ de cámara.
Orientación. Una vez situada debe orientarse. Osea, yo puedo estar quieto pero girar la cabeza y mirar
hacía donde me venga en gana. Dirección de "AT". Define hacia dónde estoy mirando, a qué punto
concretamente.
En OpenGL definimos la cámara con: glulookAt(eyeX,eyeY,eyeZ,atX,atY,atZ,upX,upY,upZ);
Esta es la función que determina dónde y cómo está dispuesta la cámara. Cuidado que la posición de
la cámara no tiene nada que ver con el tipo de proyección que hayamos definido. La proyección se define
sólo una vez, típicamente al principio del programa en una función inicializadora, mientras que la cámara
se mueve continuamente según nos interese y más en un juego 3D !!!. Añadir a esto que la matriz que se
modifica al llamar a esta función no tiene que ser GL_PROJECTION sino GL_MODELVIEW. OpenGL calculará
todas las transformaciones que aplicará al mundo 3D para que manteniendo la cámara en el origen de
coordenadas y enfocada en la dirección negativa de las Z's, nos dé la sensación de que lo estamos viendo
todo desde un cierto lugar. Para ello debemos recordar siempre activar esta matriz antes de llamar a
gluLookAt().
En cuanto a los parámetros que demanda la función son los siguientes:
Coordenadas del "eye". Es literalmente la posición XYZ dónde colocar la cámara dentro del mundo.
Coordenadas del "at". Es el valor XYZ del punto al que queremos que mire la cámara. Un punto del
mundo obviamente. Coordenadas del vector "up". Si, es un vector y no un punto. Con él regularemos
la orientación de la cámara. Este ha de ser el vector que "mira hacia arriba" si entendemos que el vector
que mira hacia adelante es el que va del "eye" hasta el "at". Variando el "up" variamos la orientación, en
la figura 3 podemos verlo gráficamente.

31
Tipos de proyecciones y manejo de la cámara en
OpenGL

Figura 3. Las variables de la cámara

En Delphi!!, En Delphi!!...
Bien, veremos a continuación un ejemplo hecho en Delphi en el que dibujaremos un pequeño
escenario virtual, en el cual podremos desplazarnos y observarlo detalladamente con ayuda del ratón y del
teclado. Veamos el listado 1 y la figura 4 para ver que pretendemos hacer.

Listado 1:

unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, OpenGL,
ExtCtrls;

type
TForm1 = class(TForm)
Timer1: TTimer;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormPaint(Sender: TObject);
procedure FormResize(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
procedure FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
procedure FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure FormMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
private
{ Private declarations }
//Para el movimiento del Ratón usamos estas variables...
dx,dy:GLFloat; //Actual desplazamiento de los angulos
mStartX,mStartY:integer; //Punto de inicio del movimiento del ratón

public
{ Public declarations }
RC : HGLRC;
Angle: Integer;
procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
end;

var
Form1: TForm1;
eyex: real = -0.1;
Eyey: real = 1.5;

32
Tipos de proyecciones y manejo de la cámara en
OpenGL
Eyez: real = 2.5;

CenterX: real = 0;
CenterY: Real = 0;
CenterZ: Real = -10;
Upx: real = 0;
Upy: real = 1;
Upz: real = 0;
Z : GLFloat;

implementation

{$R *.DFM}

procedure S(x,y,z,t,teta,teta2,w:real);
var x1,y1,z1,t1:real;
begin
if t > 0.01 then {condición de paro}
begin
{Se elije el color en que se deplegará la linea}
if t < 0.05 then
glcolor3f(0,1,0) else
if t < 0.1 then glcolor3f(1,1,1)
else
glcolor3f(1,0,0);

glbegin(GL_LINES);
glVertex3f(x,y,z);
x1 := x -t * cos(teta);
y1 := y -t * sin(teta);
z1 := z -t * sin(teta2);
glVertex3f(x1,y1,z1);
glend;

t1 := t /1.85;

{Se llama recursivamente al sistema}


S(x1,y1,z1,t1,teta-w,teta2-w,w);
S(x1,y1,z1,t1,teta,teta2,w);
s(x1,y1,z1,t1,teta+w,teta2+w ,w);
S(x1,y1,z1,t1,teta+w,teta2-w,w);
S(x1,y1,z1,t1,teta-w,teta2+w,w);

end;
end;

procedure TForm1.FormCreate(Sender: TObject);


begin
RC:=CreateRenderingContext(Canvas.Handle,[opDoubleBuffered],32,0);
end;

procedure TForm1.FormDestroy(Sender: TObject);


begin
DestroyRenderingContext(RC);
end;

procedure TForm1.FormPaint(Sender: TObject);


var
p: PGLUquadricObj;
begin
ActivateRenderingContext(Canvas.Handle,RC); // Hacer el contexto dibujable
glClearColor(0.2,0.3,0,1); // Color de Fondo...
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); // Borra el Fondo y Depth-Buffer

glMatrixMode(GL_MODELVIEW); // Activamos la matriz de modelado


glLoadIdentity; // la inicializamos
gluLookAt(eyex,eyey,eyez,centerx,centery,centerz,upx,upy,upz); // Definimos un punto de visión...

glEnable(GL_DEPTH_TEST);
glShadeModel ( GL_SMOOTH) ;

gltranslatef(0,0,-3);
glrotatef(25,0,1,0);

33
Tipos de proyecciones y manejo de la cámara en
OpenGL

glcolor3f(0,0.5,0);
glbegin(gl_quads);
glcolor3f(0,0.5,0);
glvertex3f(1.5,0, -1.5); //dibujamos un piso ficticio...
glcolor3f(0,0.5,1);
glvertex3f(1.5,0,1.5);
glcolor3f(1,0.5,1);
glvertex3f(-1.5,0,1.5);
glcolor3f(1,1,1);
glvertex3f(-1.5,0,-1.5);
glend;

glpushmatrix;
glcolor3f(0.8,0.5,0.1); //Le ponemos una base feliz.
gltranslatef(0, 0.2, 0);
glrotatef(90,1,0,0);
P := GLUNewQuadric;
gluQuadricTexture(p,1);
gluCylinder(p,0.13,0.3,0.2,10,10);
gluDeleteQuadric(p);
gldisable(GL_TEXTURE_2D);
glpopmatrix;

glpushmatrix;
glscalef(5,5,5);
glrotatef(270,0,0,1); //Se incorpora el arbol...
s(0,0,0,0.15,0,0,(Angle/360)*3);
glpopmatrix;

SwapBuffers(Canvas.Handle); //Copiar el Back Buffer en el canvas del formulario...


DeactivateRenderingContext; //Libera el contexto...
end;

procedure TForm1.FormResize(Sender: TObject);


begin // Cuando se cambia de tamaño hay que actualizar el puerto de visión...
wglMakeCurrent(Canvas.handle,RC); // Otra manera de hacer el contexto dibujable
glViewport(0,0,Width,Height); // Especificar un puerto de visión....
glMatrixMode(GL_PROJECTION); // Activar matriz de proyección...
glLoadIdentity; // Poner estado inicial...
gluPerspective(35,Width/Height,1,100); // Especificar Perspectiva ...
wglMakeCurrent(0,0); // Otra manera de liberar el contexto ...
Refresh; // Redibujar la escena ...
end;

procedure TForm1.WMEraseBkgnd(var Message: TWMEraseBkgnd);


begin //Para borrar el fondo, y evitar el parpadeo ...
Message.Result:=1;
end;

procedure TForm1.Timer1Timer(Sender: TObject);


begin
Inc(Angle,2);
Repaint;
end;

procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X,


Y: Integer);
begin
if (ssLeft in Shift) then begin
dx:=(x -mStartX)/10;
dy:=(y -mStartY)/10;
CenterX := CenterX + dx/Width;
CenterY := CenterY + dy/Height;
end;
end;

procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;


Shift: TShiftState);
begin
Case Key of
38: if eyez >= -1 then eyez := Eyez - 0.1;
40: if eyez <= 3 then eyez := Eyez + 0.1;
39: eyex := eyex -0.1;

34
Tipos de proyecciones y manejo de la cámara en
OpenGL
37: eyex := eyex +0.1;
end;
end;

procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;


Shift: TShiftState; X, Y: Integer);
begin
mStartX:=X;
mStartY:=Y;
end;

procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton;


Shift: TShiftState; X, Y: Integer);
begin
dx:=0.0;
dy:=0.0;
end;

end.

Figura 4. Nuestro mundo virtual

A fin de hacer esto, que resulta ya de por sí un tanto complicado, más digerible, volvamos a la
librería de OpenGL de Litschke que nos facilitaba tanto la vida, para no perder tiempo en cuestiones más
vanales...
Como ven hemos definido variables globales para controlar la cámara, tanto su posición, como su
orientación. ¿Recuerdan el pequeño procedimiento recursivo que hicimos en el número 2 para generar
arboles fractales en dos dimensiones?, bien, pues aquí tenemos una generalización para hacer arbolitos en
3D; el procedimiento S() se encarga de este trabajo.
Como ven, hemos definido como modelo de despliegue a GL_SMOOTH, lo cual hace que podamos tener
colores degradados en nuestra escena, bueno en realidad esto es porque forzamos a que se calcule un
color para cada uno de los pixeles específicamente, y además hacemos una prueba de profundidad para
los cuerpos.
La base de nuestro árbol en realidad es un cilindro, y noten como debemos transformar la geometría
del árbol antes de mostrarlo, esto para ajustarlo a nuestro ambiente virtual.
Ahora aquí es donde empieza lo verdaderamente interesante, ahora sí podemos entender que eran los
números que poníamos en gluperspective(), aquí vemos como hemos definido el ángulo de visualización a 35,
y como pasamos el parámetro de la relación de aspecto, y los valores near y far para este método.
Observemos como con los eventos del teclado y el ratón vamos modificando los valores asociados a
la cámara. Para el teclado es muy simple, ya que los cambios se hacen directamente sobre las variables de

35
Tipos de proyecciones y manejo de la cámara en
OpenGL

la cámara, sin embargo para el ratón debemos hacer el mismo truco del mes pasado que habíamos hecho
sobre calcular los desplazamientos del ratón sobre nuestra ventana simulando un “Drag & Drop”, solo
que aquí lo hacemos para mover la cámara y observar la escena desde un ángulo diferente.
Fijándonos bien observaremos que el teclado nos sirve para cambiar la posición del Ojo observador,
mientras que los eventos del ratón cambian el punto hacia el cual estamos observando.
Hasta aquí por ahora... como pueden ver esto no resulta ser tan difícil. Con lo que ya sabemos
podemos hacer ya pequeños mundos virtuales, aunque no muy detallados, y navegar a través de ellos, y
con un poco de imaginación, podemos hacer maravillas.
Hasta Pronto.

36
Iluminación con OpenGL (1)

Cálculo de Normales, Tipos de Iluminación.

¿Qué chiste tendría un mundo sin luz?... imaginemos por un momento un lugar en donde los colores
sean simples y vanos...donde no existan matices... Aburrido, ¿no?. Siempre las luces dan un sentido
realístico a las escenas. Así es como en el mundo del arte tenemos a verdaderos maestros del
“claroscuro”, que han hecho verdaderas obras maestras combinando las luces y las sombras.
Ciertamente el manejo eficiente de las luces y las sombras no es tarea sencilla para realizar
representaciones estéticas. Ya que en esto siempre se requiere de buen gusto e imaginación. La correcta
posición de las luces, y la intensidad de cada una de estas es de gran relevancia en el área de la fotografía
por ejemplo.
También una luz tenue puede darle a una escena un ambiente de melancolía, tristeza o invitar a la
reflexión, mientras que una luz intensa y brillante puede servirnos para representar alegría, gozo y
regocijo. Como vemos las luces tienen múltiples aplicaciones estéticamente hablando.

¿Qué es la luz?
Bueno, técnicamente la luz es una forma de energía que actuando sobre nuestros ojos nos hace ver
los objetos. Según los físicos esta forma de energía se desplaza en forma de onda a una gran velocidad y
tiene extraños comportamientos cuando choca con una superficie reflejante o refractante.
Su estudio le corresponde a la disciplina de la Óptica, la cual es una parte de la Física que trata del
estudio de la luz y de los fenómenos luminosos. Además también estudia sobre el comportamiento de los
espejos y las lentes.
A lo largo de este curso veremos como las leyes de la física, en particular de la óptica son muy
aplicables en las aplicaciones gráficas. Ya que si tratamos de simular la realidad, antes debemos entender
como suceden los fenómenos en la realidad, ¿no creen?.

La clave de todo: Las Normales


Para iluminar una superficie o plano, necesitamos información sobre su vector normal asociado.
Primero veamos que es una normal: la normal de un plano es en realidad un vector perpendicular a este,
así de fácil. ¡Volvemos otra vez a la tediosa Álgebra Lineal!... Bueno, no todo es miel sobre hojuelas en
este ambiente, ni modo... aquí necesitamos entonces saber como calcular este vector y como
especificárselo a OpenGL, así que debemos repasar un poco lo que ya habíamos estudiado sobre
aritmética de vectores ¿lo recuerdan?.
Tratemos de ver esto con un caso práctico: veamos la figura 1... Supongamos que tenemos el objeto
que se presenta en la figura y deseamos calcular el vector normal a la cara superior de este objeto, la
formada por los vértices A, B, C y D. Todos estos vértices son coplanares (adoro estos términos ?) es
decir, que pertenecen a un mismo plano, así que si queremos encontrar un vector normal a este plano solo
tenemos que encontrar un vector que sea perpendicular a cualquiera de estos vértices... Pues bien, para
esto solo tenemos que calcular dos vectores pertenecientes a la cara y hacer su producto vectorial (El
producto vectorial ya lo hemos estudiado en artículos anteriores). El resultado de esta operación será un

Figura 1. Calculo de normales para una cara.

37
Iluminación con OpenGL (1)
vector perpendicular a ambos y por lo tanto una normal del plano.

En la figura 1 podemos ver como a partir de tres vértices (A, B y C) creamos dos vectores que tras su
producto vectorial obtenemos el vector normal. Y este vector ya puede asociarse a la correspondiente
cara.
Una vez calculada la normal debemos de normalizar, es decir, dividir ese vector por su propio
módulo para que sea unitario. De esta forma tenemos un vector normal de módulo igual a la unidad que
es lo que OpenGL necesita.
Ahora, solo debemos tener cuidado con el orden en el que efectuamos estos productos vectoriales,
porque de este orden depende que el vector normal apunte hacia fuera o hacia dentro de la cara. Pensemos
que siempre queremos que nuestras normales apunten hacia fuera de la cara visible, es decir hacia el
frente. Ya que este vector nos servirá para calcular la incidencia de la luz en la cara, y ¿para que
queremos calcularla en una superficie que no es visible?, ¿lógico, no?.
Aquí cabe hacer un pequeño apunte que resulta algo relevante: OpenGL utilizará la normal asociada
a cada vértice para evaluar la luz que incide sobre éste. Si un vértice pertenece a más de una cara (es un
caso obvio ilustrado en la figura 1 donde el vértice A por ejemplo, pertenece a tres caras distintas), ¿qué
debemos hacer?. En este caso hay que promediar para obtener cálculos correctos por parte de OpenGL.
Tendremos que calcular la normal de cada una de las caras a las que pertenece el vértice, promediarlas y
después normalizar el resultado, con lo cual ese vértice presentará un vector normal al cual han
contribuido todas las caras a las que pertenece.

Eso está bien, pero...¿Cómo se definen normales en OpenGL?


Bien, para definir normales en OpenGL usamos el procedimiento glNormal3f(); declarándolo ya sea una
normal por cara ó bien por cada vértice de nuestra figura. A este procedimiento le pasamos las tres
coordenadas (X, Y, Z) del vector en cuestión. Como siempre hay variaciones sobre este procedimiento
dado que es del tipo glNormal* al igual que como comentamos en otra ocasión para glVertex *.
Esto es calcular las normales “A pié” como diríamos en México, ya que involucra que nosotros
mismos hagamos los cálculos de las normales ya sea para cada vértice o cara de nuestra figura. Y cuando
no somos muy buenos que digamos para el Álgebra de Vectores, pues esto pudiera resultar un poco
tedioso, ¿no es así?... Pues OpenGL también cuenta con un cálculo automático de normales, (¡Va ya, que
alivio!). Por un lado esto es bueno para nosotros los programadores porque pues nos ahorra tal vez algo
de tiempo en pensar en métodos para calcular las normales, ó tal vez calcularlas “a pié” en alguna hoja de
calculo; pero por otro lado puede llegar a resultar contraproducente si nuestra aplicación corre en una
configuración que no presenta aceleración para OpenGL, pues se carga al sistema con cálculos
innecesarios que ralentizan aún más lo que ya de por sí es computacionalmente exigente, es decir, el
cálculo automático de la iluminación.
Bueno, de cualquier modo debemos mencionar aquí todas las posibilidades... para habilitar el cálculo
automático de las normales solo tenemos que usar la instrucción glEnable(GL_NORMALIZE); y así OpenGL hará
el trabajo por nosotros. Y cuando deseemos nosotros tomarnos esa molestia solo debemos inhibir el
cálculo automático con glDisable(GL_NORMALIZE);. Estas funciones para habilitar e inhabilitar cosas ya las
habíamos mencionado anteriormente, cuando hablábamos de que OpenGL se comportaba como una
máquina de estados finitos (autómata), bien, pues aquí está un ejemplo más de eso.

Tipos de iluminación
Ya sabemos que será necesaria la especificación de una normal por vértice. Ahora vamos a ver que
tipos de iluminación soporta OpenGL con lo cual empezaremos a ver más claro el uso de estos vectores.
Existen múltiples tipos de iluminación, pero aquí nos dedicaremos a estudiar 2 de ellos: la iluminación
Plana, y la iluminación Suave.
La iluminación plana (ó FLAT) es la más simple e ineficiente. En ella todo el polígono presenta el
mismo color pues OpenGL evalúa solo un color para todos sus puntos, ya que en este caso se calcula una
sola normal por cada cara de la figura. Este tipo de iluminación no es muy recomendable para
aplicaciones donde el realismo sea importante. Aunque por otra parte es muy eficiente dado que los
cálculos que se efectúan son mínimos.
Y la iluminación suave ó Smooth ó Gouraud efectúa cálculos de color para cada uno de los puntos
del polígono. Se asocian las normales a los vértices y OpenGL calcula los colores que éstos deben tener e

38
Iluminación con OpenGL (1)
implementa una interpolación de colores para el resto de puntos. De esta forma ya empezamos a
presenciar escenas granuladas, con degradados en la geometría y la calidad ya emp ieza a ser notable.
Para definir cada uno de estos tipos de iluminación en nuestras escenas debemos usar la función
glShadeModel(); pasándole como parámetro el tipo de iluminación que deseamos. Usamos GL_FLAT como
parámetro para definir una iluminación plana, y GL_SMOOTH para una iluminación suave. También existen
otros modos de definir el tipo de iluminación cuando trabajamos con objetos de la librería GLU, pero eso
lo veremos cuando analicemos nuestro programa de ejemplo.

Y se hizo la luz!!!...
OpenGL soporta en principio hasta 8 luces simultaneas en un escenario. De hecho también depende
de la máquina que poseamos y de la RAM que le dejemos usar.
Las luces cuentan con nombre propio del estilo GL_LIGHT0, GL_LIGHT1, GL_LIGHT2 y así sucesivamente.
Para activar o desactivar cada una de ellas también usamos glEnable() y glDisable() pasando como parámetro el
nombre de la luz que deseamos afectar. También podemos activar y desactivar todo el cálculo de
iluminación pasando como parámetro GL_LIGHTING a estos procedimientos.
Ahora tenemos que ver las características específicas de cada una de estas luces, tanto como su
posición en la escena, como su orientación y su color por ejemplo. Hay algunas otras características, pero
esas las estudiaremos en el siguiente número (no se trata de bombardear con información para luego no
enterarse de nada, ¿no?).
Para todas estas características usaremos la función que en lenguaje C está definida así:

void glLightfv( Glenum light, Glenum pname, const Glfloat *params );

El valor de light será siempre la luz a la que nos estemos refiriendo (GL_LIGHT0, GL_LIGHT1, GL_LIGHT2,
etc.) En cuanto a *params, le pasamos un arreglo de valores RGBA reales que definen la característica en
concreto. Estos valores RGBA definen el porcentaje de intensidad de cada color que tiene la luz. Si los
tres valores RGB valen 1.0 la luz es sumamente brillante; si valen 0.5 la luz es aún brillante pero empieza
a parecer oscura, de un tono de gris.
En cuanto al pname este nos servirá para determinar la característica que deseamos modificar de
dicha luz. Ahora veremos que valores puede tomar este parámetro:
Pasamos GL_AMBIENT para definir la característica ambiental, la cual define la contribución de esta
fuente de luz a la luz ambiental de la escena. Por defecto esta contribución es nula.
Si usamos GL_DIFFUSE modificamos la característica difusa de la luz, que es lo que entendemos como
el color que tiene la luz. Para GL_LIGHT0 los valores RGBA por defecto valen 1.0. Para el resto de luces los
valores por defecto son 0.0.
Con GL_ESPECULAR modificamos la característica especular de la fuente de luz. Esta se trata de la luz
que viene de una dirección particular y rebota sobre un objeto siguiendo una determinada dirección. Es la
componente responsable de las zonas más brillantes en la geometría, de los llamados “highlights” o
“luces altas”.
Y por último la posición de la luz. Esta se define pasando como parámetro pname a GL_POSITION en la
función glLightfv() antes descrita. En este caso *params se corresponde con el valor de la coordenada
homogénea (X, Y, Z, W) donde colocar la luz. Si w es igual a 0.0 se considera que la luz se encuentra
infinitamente lejos de nosotros. En este caso su direccióin se deduce del vector que pasa por el origen y
por el punto (X, Y, Z). Si w es 1.0 se considera su posición con toda normalidad.
Por defecto la luz se encuentra en (0.0,0.0,0.1,0.0) iluminando en la dirección negativa de las Z’s. Y
los rayos de luz se asumen paralelos.
Podemos mover una luz a gusto por una escena incluso podemos movernos con ella como si fuera
una linterna. Para ello tan solo tenemos que considerarla como un Objeto 3D más que se ve afectado por
cambios en la matriz de transformación “MODEL_VIEW” de OpenGL. Podemos rotarla, trasladarla,
escalar su posición, como si se tratara de un polígono.

Vamos al ejemplo!!!
Bien, empezaremos a analizar el código que mostramos en el listado 1.

Listado 1

unit Unit1;

39
Iluminación con OpenGL (1)
interface

uses
Windows, Forms, OpenGL, Classes, ExtCtrls, Messages, Controls, StdCtrls,Graphics
,sysUtils, glut;
type
TMainForm = class(TForm)
Timer1: TTimer;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormPaint(Sender: TObject);
procedure FormKeyPress(Sender: TObject; var Key: Char);
procedure FormResize(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
RC : HGLRC;
Angle : Integer;
procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
end;
var
MainForm: TMainForm;
glfLightPosition : Array[0..3] of GLfloat = (0, 0, 1, 1.0);
glfLightDiffuse : Array[0..3] of GLfloat = (0.7, 0.7, 0.7, 1.0);
glfLightSpecular: Array[0..3] of GLfloat = (0.7, 0.7, 0.7, 1.0);

implementation

uses Dialogs, Unit2;

{$R *.DFM}

procedure TMainForm.FormCreate(Sender: TObject);


begin
RC:=CreateRenderingContext(Canvas.Handle,[opDoubleBuffered],32,0);
end;

procedure TMainForm.FormDestroy(Sender: TObject);


begin
DestroyRenderingContext(RC);
end;

procedure TMainForm.FormPaint(Sender: TObject);


var p: PgluQuadric;
begin
ActivateRenderingContext(Canvas.Handle,RC); // Hacer el contexto dibujable

glClearColor(0.1,0.2,0,5); // Color de Fondo...

glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); // Borra el Fondo y Depth-Buffer

glMatrixMode(GL_MODELVIEW);
glLoadIdentity;
glTranslatef(0,0,-3);

glEnable(GL_DEPTH_TEST);

glLightfv(GL_LIGHT0, GL_POSITION, @glfLightPosition); // Se definen las


glLightfv(GL_LIGHT0, GL_DIFFUSE, @glfLightDiffuse); //carácterísticas de la
glLightfv(GL_LIGHT0, GL_SPECULAR,@glfLightSpecular);

glrotatef(270,1,0,0);
glrotatef(360-4*angle,0,0,1);

//Se habilitan las luces...


If Form2.CheckBox1.Checked then
begin
glEnable(GL_LIGHTING);
glenable(GL_LIGHT0);
end;

P := gluNewQuadric;
Case Form2.RadioGroup1.ItemIndex of
0:gluQuadricNormals(p,GLU_FLAT);
1:gluQuadricNormals(p,GLU_SMOOTH);
end;

40
Iluminación con OpenGL (1)
Case Form2.RadioGroup2.ItemIndex of
0:gluQuadricDrawStyle(p,GLU_FILL);
1:gluQuadricDrawStyle(p,GLU_LINE);
end;
gluSphere(p,0.5,15,15);
GluDeleteQuadric(p);

//Se inhiben las luces...


gldisable(GL_LIGHTING);
gldisable(GL_LIGHT0);

GLPushMatrix;
Glscalef(0.2,0.2,0.2);
GLRotatef(angle*2,0,0,1);
gltranslatef(2.5,3,0);
GLRotatef(Angle*4,0,1,1);

glcolor3f(1,1,1);
P := gluNewQuadric;
gluQuadricDrawStyle(p,GLU_SILHOUETTE);
gluSphere(p,1,15,15);
gluDeleteQuadric(p);
GLPopMatrix;

SwapBuffers(Canvas.Handle); //Copiar el Back Buffer en el canvas del formulario...


DeactivateRenderingContext; //Libera el contexto...
end;

procedure TMainForm.FormKeyPress(Sender: TObject; var Key: Char);


begin// Se acaba el demo ...
if Key = #27 then Application.Terminate;
end;

procedure TMainForm.FormResize(Sender: TObject);


begin // Cuando se cambia de tamaño hay que actualizar el puerto de visión...
wglMakeCurrent(Canvas.handle,RC); // Otra manera de hacer el contexto dibujable
glViewport(0,0,Width,Height); // Especificar un puerto de visión....
glMatrixMode(GL_PROJECTION); // Activar matriz de proyección...
glLoadIdentity; // Poner estado inicial...
gluPerspective(35,Width/Height,1,100); // Especificar Perspectiva ...
wglMakeCurrent(0,0); // Otra manera de liberar el contexto...
Refresh; // Redibujar la escena ...
end;

procedure TMainForm.Timer1Timer(Sender: TObject);


begin // Se hace la animación.....
Inc(Angle,1);
if Angle >= 360 then Angle:=0;
Repaint;
end;

procedure TMainForm.WMEraseBkgnd(var Message: TWMEraseBkgnd);


begin //Para borrar el fondo, y evitar el parpadeo ...
Message.Result:=1;
end;

end.

Como veremos, esta vez tenemos una aplicación con dos formularios, uno para mostrar la escena, y
el otro para mostrar una serie de controles que nos permitan modificar la luz que lo ilumina todo.
La primera parte ya nos debe de resultar familiar a estas alturas, ¿no es así?. Lo único nuevo que
observamos aquí es que hemos declarado 3 arreglos como variables globales que nos servirán para
determinar la Posición, el valor difuso y especular de la luz que tendremos en nuestra escena.
Realmente la parte distinta que podemos apreciar en el código es en la parte del evento OnPaint() del
formulario principal. Como ven, no difiere mucho la primera parte de este evento de lo que ya hemos
estudiado anteriormente hasta que llegamos a la parte de los glLightfv() donde definimos las características
de la única luz que tendremos en escena: la posición, la difusa y la especular.
Tendremos una esfera la cual será nuestro objeto iluminado, y esta esfera estará rotando sobre su
propio eje Y para que así tengamos una mejor apreciación del movimiento de nuestra luz. En la segunda
forma hemos puesto un CheckBox el cual nos permitirá habilitar e inhabilitar la luz en nuestra escena, y
así poder apreciar el modo en como esta influye.

41
Iluminación con OpenGL (1)
Ahora bien, para construir la esfera usaremos un objeto de la librería glu. Así que por esto vemos
que declaramos una variable local en este evento de tipo PgluQuadric (esto ya lo habíamos mencionado en un
articulo anterior si lo recuerdan). Primeramente hay que definir como se calcularán las normales para este
objeto, y a fin de poder seleccionarlo pusimos en la segunda forma un RadioGroup que nos permite elegir
entre la iluminación plana y la iluminación suave. Para el caso de objetos de la librería glu también
podemos determinar el modo de iluminación usando la función gluQuadricNormals(), pasando como parámetro
el puntero al objeto a modificar (en este caso la variable p), y el tipo de normales que se han de calcular:
GLU_FLAT para la iluminación plana, GLU_SMOOTH para iluminación suave o Gouraud, y GLU_NONE si no
queremos que se calcule ninguna normal para esta figura.
Después elegimos el modo en como se ha de desplegar nuestra esfera, para esto se usa la función
gluQuadricDrawStyle(), a la cual también se le pasa como parámetro el puntero a nuestro objeto, y como
segundo parámetro: GLU_FILL si deseamos una esfera sólida, GLU_LINE si solo deseamos que la dibuje a base
de líneas, ó también puede usarse GLU_POINT ó GLU_SILHOUETTE para dibujarla como puntos o solo la silueta
de la esfera respectivamente.
Con gluSphere() construimos propiamente la esfera, indicando el radio que está tendrá, así como el
número de paralelos y meridianos. Cabe señalar que entre más meridianos y paralelos definamos para
nuestra esfera la calidad en la definición será mayor, pero también el tiempo que consumirán los cálculos
para realizarla, como ven siempre se sacrifica algo, ó calidad o velocidad. Y una vez construida podemos
liberar nuestro puntero con gluDeleteQuadric().
También al final construimos una esfera más pequeña que la anterior la cual modificamos con ciertas
transformaciones para que gire alrededor de la primera a manera de “satélite”. Esto es un principio para
los que planeen hacer una representación del sistema solar por ejemplo, al final de cuentas solo son
esferas girando unas alrededor de otras. Esta pequeña esfera no se ve afectada por la luz de nuestra escena
ya que la inhibimos antes de construirla, esto para poder establecer una diferencia entre un objeto
iluminado y otro que no lo es.
En el listado 2 lo que tenemos son 4 eventos asociados a los TrackBars que controlan tanto la
posición en el eje Y de nuestra luz, así como los valores RGB del color de la misma. Jugando un poco con
esto podemos ver como es que funcionan. Noten como a partir de las posiciones de estos TrackBars
modificamos valores en los arreglos correspondientes.

Listado 2

unit Unit2;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ComCtrls, StdCtrls, ExtCtrls;

type
TForm2 = class(TForm)
TrackBar1: TTrackBar;
Label1: TLabel;
RadioGroup1: TRadioGroup;
GroupBox1: TGroupBox;
TrackBar2: TTrackBar;
TrackBar3: TTrackBar;
TrackBar4: TTrackBar;
Label2: TLabel;
Label3: TLabel;
Label4: TLabel;
RadioGroup2: TRadioGroup;
CheckBox1: TCheckBox;
procedure TrackBar1Change(Sender: TObject);
procedure TrackBar2Change(Sender: TObject);
procedure TrackBar3Change(Sender: TObject);
procedure TrackBar4Change(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Form2: TForm2;

42
Iluminación con OpenGL (1)
implementation

uses Unit1;
{$R *.DFM}

procedure TForm2.TrackBar1Change(Sender: TObject);


begin
glfLightPosition[1] := -TrackBar1.Position /10;
end;

procedure TForm2.TrackBar2Change(Sender: TObject);


begin
glfLightDiffuse[0] := TrackBar2.Position /10
end;

procedure TForm2.TrackBar3Change(Sender: TObject);


begin
glfLightDiffuse[1] := TrackBar3.Position /10
end;

procedure TForm2.TrackBar4Change(Sender: TObjec t);


begin
glfLightDiffuse[2] := TrackBar4.Position /10
end;

end.

Bueno, hasta aquí llegamos por ahora, y en el siguiente articulo seguiremos estudiando más sobre
iluminación y empezaremos a trabajar con materiales, porque esto también tiene su chiste. Hasta Pronto.

Figura 2. La forma de despliegue de nuestra escena

Figura 3. La forma de controles para la iluminación

43

También podría gustarte