opengl

44
Delphi en 3 Dimensiones OpenGL Autor: Carlos García Trujillo [email protected] Prohibida la reproducción total o parcial sin permiso explícito del autor.

Upload: juarez-elieser

Post on 28-Jun-2015

846 views

Category:

Documents


4 download

TRANSCRIPT

Page 1: opengl

Delphi en 3 DimensionesOpenGL

Autor:

Carlos García [email protected]

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

Page 2: opengl

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.

Direct3DDirect3D 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.

OpenGLOpenGL 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

Page 3: opengl

Delphi en tres dimensionesIris, 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 adicionalesEstá 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

Page 4: opengl

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.

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.

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

3

Page 5: opengl

4

Figura 1. Niveles de Abstracción en OpenGL

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 abstracción de OpenGL.

Las Piedras angulares: las matrices y los vectoresAquí 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

Page 6: opengl

5

Figura 2. Matrices Genéricas de Transformación Lineal para Sistemas de 3 Dimensiones.

Figura 3.La Matriz Identidad.

Figura 4. Secuencia de Transformación de Coordenadas.

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 texturas a aplicar a las figuras de 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 cual carga la 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.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 tiposLos 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

Page 7: opengl

6

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;interfaceuses 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;

Page 8: opengl

7

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.

Page 9: opengl

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

Page 10: opengl

9

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...

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.

Figura 5. Nuestra primera aplicación de OpenGL en Ejecución

Page 11: opengl

10

Figura 1. Proyección Ortogonal en 2D en el plano Z=0

Líneas con OpenGL en dos dimensionesProyecció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 2DAunque 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 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

Page 12: opengl

11

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 OpenGLExisten 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íneasTambié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.

Page 13: opengl

12

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;interfaceuses 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;beginif 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;beginif 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...

Page 14: opengl

13

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;beginGLBegin(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...}

Page 15: opengl

14

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).

Page 16: opengl

15

Líneas con OpenGL en dos dimensionesAhora 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 punteadasSiguiendo 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

Page 17: opengl

16

Líneas con OpenGL en dos dimensionesaplicació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ónPor ú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

Page 18: opengl

17

Figura 1. Multiplicación de un Vector por otro vector

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

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 vamos 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 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

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

Page 19: opengl

18

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

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

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”.

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

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:

Page 20: opengl

19

Figura 5. Traslación de un cuerpo

Figura 6. Rotación de un cuerpo

Figura 7. Escalamiento de un cuerpo

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 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...(); 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.Y el escalamiento se define con: glScale...(); este también recibe 3 parámetros que definen el grado de

Page 21: opengl

20

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

Figura 9. Algunas primitivas de graficación en OpenGL

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.

Las primitivas geométricas

Page 22: opengl

21

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;interfaceuses 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 ...beginglbegin(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);

Page 23: opengl

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

Page 24: opengl

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 perspectivaend;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

Page 25: opengl

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

Page 26: opengl

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

Page 27: opengl

26

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

Interacción de gráficos con dispositivos

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.

Page 28: opengl

27

Figura 10

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

Page 29: opengl

28

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 dispositivosHasta 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.

Page 30: opengl

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

29

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

Page 31: opengl

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

30

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, Relac ió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

Page 32: opengl

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

31

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.

Page 33: opengl

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

32

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;

Page 34: opengl

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

33

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);

Page 35: opengl

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

34

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;

Page 36: opengl

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

35

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

Page 37: opengl

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

36

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.

Page 38: opengl

Iluminación con OpenGL (1)

37

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.

Page 39: opengl

Iluminación con OpenGL (1)

38

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

Page 40: opengl

Iluminación con OpenGL (1)

39

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;

Page 41: opengl

Iluminación con OpenGL (1)

40

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;

Page 42: opengl

Iluminación con OpenGL (1)

41

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.

Page 43: opengl

Iluminación con OpenGL (1)

42

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;

Page 44: opengl

Iluminación con OpenGL (1)

43

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