Opengl
Opengl
OpenGL
Autor:
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.
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.
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.
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.
4
Introducción a la Programación de Gráficos con OpenGL
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.
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}
6
Introducción a la Programación de Gráficos con OpenGL
//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;
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.
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.
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...
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...
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;
glMatrixMode(GL_MODELVIEW);
glLoadIdentity;
gluOrtho2D (-1.0, 1.0, 0.0, 1.5); //Para trabajar en 2 dimensiones...
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;
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.
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.
16
Transformaciones Lineales en OpenGL
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?).
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”.
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.
19
Transformaciones Lineales en OpenGL
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.
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}
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;
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;
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
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.
25
Interacción de gráficos con dispositivos
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.
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.
28
Tipos de proyecciones y manejo de la cámara en
OpenGL
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.
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
29
Tipos de proyecciones y manejo de la cámara en
OpenGL
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.
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.
31
Tipos de proyecciones y manejo de la cámara en
OpenGL
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;
end;
end;
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;
34
Tipos de proyecciones y manejo de la cámara en
OpenGL
37: eyex := eyex +0.1;
end;
end;
end.
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)
¿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?.
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.
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í:
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
{$R *.DFM}
glMatrixMode(GL_MODELVIEW);
glLoadIdentity;
glTranslatef(0,0,-3);
glEnable(GL_DEPTH_TEST);
glrotatef(270,1,0,0);
glrotatef(360-4*angle,0,0,1);
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);
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;
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}
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.
43