OpenGL Delphi

87
Programación con Delphi y OpenGL Carlos García Trujillo [email protected] http://glscene.tripod.com Prohibida la reproducción o modificación sin autorización expresa del autor. (Cedida la publicación a el Rinconcito de Delphi) http://www.elrinconcito.com/delphi/

Transcript of OpenGL Delphi

Page 1: OpenGL Delphi

Programación con Delphi y OpenGL

Carlos García Trujillo

[email protected]

http://glscene.tripod.com

Prohibida la reproducción o modificación sin autorización expresa del autor.

(Cedida la publicación a el Rinconcito de Delphi) http://www.elrinconcito.com/delphi/

Page 2: OpenGL Delphi

Delphi en tres dimensiones

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

Quiero hablar un poco acerca de las opciones que tenemos para realizar aplicaciones que involucren gráficos 3D en Delphi, ya sean salvapantallas, juegos, demos multimedia, interfaces 3D, e incluso simulaciones y aplicaciones 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 de acceso transparente 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 (que 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 una interfaz 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 qué 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 ... gozaremos por supuesto 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, Iris, Solaris, Java e incluso lenguajes de programación visuales como Visual Basic, Borland C++ Builder y por supuesto Delphi.En contraste con la antigua IRIS GL-library de SGI, OpenGL es por diseño independiente de plataformas y sistemas operativos como ya lo mencionamos, y esto es un punto que podemos tomar en cuenta los programadores de Delphi, pensando en que próximamente saldrá una versión de Delphi para Linux, en la que obviamente no podremos contar con DirectX. Además es perceptiva a la red, de manera que es posible separar nuestra aplicación OpenGL en un servidor y un cliente que verdaderamente produzca los gráficos. Existe un protocolo para mover por la red los comandos OpenGL entre el servidor y el cliente. Gracias a su independencia del sistema operativo, el servidor y el cliente no tiene porque ejecutarse en el mismo tipo de plataforma, muy a menudo el servidor ser á una supercomputadora ejecutando una compleja simulación y el cliente una simple estación de trabajo mayormente dedicada a la visualización gráfica. OpenGL permite al desarrollador escribir aplicaciones que se puedan desplegar en varias plataformas fácilmente.Por encima de todo, OpenGL es una biblioteca estilizada de trazado de gráficos de alto rendimiento, y hay varias tarjetas gráficas aceleradoras y especializadas en 3D que implementan primitivas OpenGL a nivel del hardware. Hasta hace poco, estas avanzadas bibliotecas gráficas solían ser muy caras y sólo 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 altamente portable y sencilla. 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 Algebra Lineal, Geometría y un poco de

2

Page 3: OpenGL Delphi

Delphi en tres dimensiones

Física, aunque a final de cuentas no es tan difícil como puede parecer.OpenGL provee prácticamente 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 incluyen la DLL OpenGL32, que 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 archivos de ayuda para esta API y sus múltiples funciones.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, a fin de cuentas 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 aparecerán más 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.En posteriores números estudiaremos más a fondo cada una de estas API’s (OpenGL y Direct3D), y presentaremos algunos programas interesantes que podemos realizar con Delphi basándonos en estas tecnologías (como el mostrado en la figura 1), así como algunas cuestiones relacionadas con la representación virtual de objetos.

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

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

3

Page 4: OpenGL Delphi

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 el 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 fin 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 mayoría de las funciones que aquí utilizaremos. Las funciones contenidas en esta librería inician con las letras gl.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 posibilita la libre creación de ventanas, así como el acceso al ratón 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 ratón 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 Unix o Linux, por esto, si se usa GLUT como manejador de ventanas, no se tendría que reescribir 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 de 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 tres 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

Figura 1 Niveles de Abstracción en OpenGL

�4

Page 5: OpenGL Delphi

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

Figura 3. La Matriz Identidad

Tipos en OpenGLDebemos 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.

inicializado pertinentemente esta matriz.Una matriz de Modelado llamada GL_MODELVIEW, donde ésta 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 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 utilizar en las figuras de nuestra escena, cuando estemos más 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 cada matriz se invoca a un procedimiento llamado GLLoadIdentity(), el cual carga la matriz identidad, que 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 cosas. Existen básicamente dos formas de trabajar con vectores en OpenGL, con sus elementos como variables independientes, o 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 tres que determinan el color, codificados en RGB, y un elemento extra en algunos casos, que determina el nivel de transparencia para

Figura 4. Secuencia de Transformación de Coordenadas.

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

5

Page 6: OpenGL Delphi

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

el objeto, cara ó vértice al cual corresponda 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 tres 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 con 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;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;

6

Page 7: OpenGL Delphi

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

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.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 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 éste 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 haga en él lo que nosotros deseamos que se vea.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.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

7

Page 8: OpenGL Delphi

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

Figura 5. Nuestra Primera Aplicación de OpenGL en Ejecución.

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, sólo 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 grandes resultados, 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 el nuestro 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... Hasta Pronto.

8

Page 9: OpenGL Delphi

Figura 1

Líneas con OpenGL en dos dimensiones

Proyección Ortogonal 2D. Construcción de gráficos con Líneas. Antializado.

Algoritmos recursivos de Fractales.

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

Visualización 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 (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

�9

Page 10: OpenGL Delphi

Líneas con OpenGL en dos dimensiones

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.

¡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.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).Ahora aquí vienen unas instrucciones que tal vez les resulten nuevas en este momento glPushMatrix() y glPopMatrix(). Suponemos que todos aquí hemos trabajado alguna vez con estructuras de datos de tipo Pila, y que entendemos los procedimientos de inserción y eliminación de elementos de esta estructura. Bueno, pues estas instrucciones trabajan con una pila de matrices y sirven para hacer respaldos de la matriz de transformaciones que luego podemos recuperar. La finalidad de hacer estos respaldos es para poder hacer ciertas transformaciones sobre figuras específicas sin que éstas afecten al resto de las figuras en nuestra escena. Esto es verdaderamente útil como veremos más adelante.Delimitemos ahora lo que pretendemos realizar con este código. Bien, pues lo que dibujaremos es: “un paisaje en el campo durante una noche de Eclipse de Luna”, ¿qué les parece?, no me negarán que es un título bastante artístico para nuestra “obra de arte”; y todo usando solo líneas.Nuestro paisaje estará compuesto por árboles, estrellas, y la luna eclipsada, por supuesto. As í que necesitamos procedimientos que dibujen cada una de estas figuras. Tanto los árboles como las estrellas las construiremos usando fractales con procedimientos recursivos.Los fractales que usaremos serán del tipo de los sistemas S->eS*, lo que significa que serán sistemas que contendrán

10

Page 11: OpenGL Delphi

Líneas con OpenGL en dos dimensiones

Figura 2

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 aplicación. Push y pop nos evitan este molesto trabajo.

¿Y la luna y el antializado?Bien, pues la luna la construiremos como si fuera un polígono con GL_LINE_LOOP, recuerden que a final de cuentas un circulo podemos definirlo como un polígono que tiene muchos lados, tantos que no se llegan a distinguir. En nuestro caso hacemos uno de 100 lados. Como lo que pretendemos es dibujar una

luna eclipsada, entonces solo debemos preocuparnos de dibujar el contorno de esta luna, y aprovechamos que el color con el que borramos la pizarra es el negro para evitar dibujar el resto.Ahora, como decíamos, dibujar tantas líneas en tan diferentes ángulos ocasiona que nuestro circulo no luzca bien definido, por lo que aquí si es conveniente utilizar el antializado. El antializado para el caso de las líneas se habilita con la instrucción glEnable(GL_LINE_SMOOTH); y se inhibe con

11

Page 12: OpenGL Delphi

Líneas con OpenGL en dos dimensiones

tglDisable(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 de la revista. Hasta Pronto...

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;

�12

Page 13: OpenGL Delphi

Líneas con OpenGL en dos dimensiones

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

�13

Page 14: OpenGL Delphi

Líneas con OpenGL en dos dimensiones

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

�14

Page 15: OpenGL Delphi

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

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

Figura 3. Multiplicación de un Vector por una 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 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 qué?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í 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 aparece 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 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:

�15

Page 16: OpenGL Delphi

Transformaciones Lineales en OpenGL

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

Figura 5. Traslación de un cuerpo

Figura 6. Rotación de un cuerpo

Figura 7. Escalamiento de un cuerpo

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

| X'| = | X |* ( | A |*| B | )Donde X’ representa al vector transformado, X al vector

original, y A y B son 2 matrices de t r a n s f o r m a c i ó n distintas.Y si en el caso anterior tomamos:

| K | = | A |*| B |

entonces podemos sustituir K en la segunda expresión como:

| 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 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, lo hacen 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

t ransformación tanto de T r a s l a c i ó n , R o t a c i ó n , Escalamiento y Reflexión.Ahora, el orden en como se apliquen estas o p e r a c i o n e s importa y m u c h o . . . C o m o 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

Rotación-Traslación Traslación-Rotación

16

Page 17: OpenGL Delphi

Transformaciones Lineales en OpenGL

Figura 9. Algunas primitivas de graficación en OpenGL

bastante distintas.

¿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 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, un 1 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 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étricasHasta 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 éste.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 esta 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 lo que hacemos con este procedimiento.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 éste, y el resto del espacio utilizarlo para colocar controles que manipulen la escena, ¿no?...Bueno, pues esta vez veremos como hacerlo. Para este ejemplo tomaremos 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 hacer de este modo es el formato de pixeles que

�17

Page 18: OpenGL Delphi

Transformaciones Lineales en OpenGL

tusaremos en el control (en este caso el Panel). Para esto hemos utilizado el procedimiento: procedure setupPixelFormat(DC:HDC) el cual nos servirá para 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, más adelante hablaremos de temas 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 encargan 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.

Listado 1

unit Unit1;

interface

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

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

var Form1: TForm1; Angle: integer;implementation

{$R *.DFM}

�18

Page 19: OpenGL Delphi

Transformaciones Lineales en OpenGL

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

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;

�19

Page 20: OpenGL Delphi

Transformaciones Lineales en OpenGL

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

�20

Page 21: OpenGL Delphi

Transformaciones Lineales en OpenGL

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

�21

Page 22: OpenGL Delphi

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

22

Page 23: OpenGL Delphi

Interacción de gráficos con dispositivos

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

Figura 1.- Nuestra aplicación en ejecución

Listado 1unit Unit1;

interface

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

�23

Page 24: OpenGL Delphi

Interacción de gráficos con dispositivos

type {Nuestra nueva clase para controlar el movimiento del ratón sobre una región del formulario} TGLControl=class(TCustomControl) public property OnMouseDown; property OnMouseUp; property OnMouseMove; property OnKeyDown; end;

{Un tipo enumerado para los cuerpos que podemos dibujar} TCuerpo =(cono,cilindro,esfera);

{La definición de nuestro formulario} TForm1 = class(TForm) TrackBar1: TTrackBar; Label1: TLabel; ComboBox1: TComboBox; procedure FormCreate(Sender: TObject); procedure TrackBar1Change(Sender: TObject); procedure FormPaint(Sender: TObject); procedure ComboBox1Change(Sender: TObject); private //Para el movimiento del Ratón usamos estas variables... alpha, betha:GLfloat; //angulo para los eventos del Ratón dx,dy:GLFloat; //Actual desplazamiento de los angulos mStartX,mStartY:integer; //Punto de inicio del movimiento del ratón

cuerpo:TCuerpo; GLC:TGLControl; //Control para controlar la salida OpenGL procedure Draw; //Dibuja la escena... //Nuestro evento de OnMouseDown para el nuevo control... procedure GLMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); //Nuestro evento de OnMouseUp para el nuevo control... procedure GLMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); //Nuestro evento de OnMouseMove para el nuevo control... procedure GLMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); public { Declaraciones públicas} end;

var Form1: TForm1;

implementation

{$R *.DFM}

procedure setupPixelFormat(DC:HDC);const pfd:TPIXELFORMATDESCRIPTOR = ( nSize:sizeof(TPIXELFORMATDESCRIPTOR); // Tamaño nVersion:1; // versión

�24

Page 25: OpenGL Delphi

Interacción de gráficos con dispositivos

dwFlags:PFD_SUPPORT_OPENGL or PFD_DRAW_TO_WINDOW or PFD_DOUBLEBUFFER; // Usamos un soporte para el double-buffering iPixelType:PFD_TYPE_RGBA; // tipo de colores cColorBits:16; cRedBits:0; cRedShift:0; // ignoramos los colores cGreenBits:0; cGreenShift:0; cBlueBits:0; cBlueShift:0; cAlphaBits:0; cAlphaShift:0; // sin buffer de transparencias cAccumBits: 0; cAccumRedBits: 0; cAccumGreenBits: 0; // Ignoramos la acumulación cAccumBlueBits: 0; cAccumAlphaBits: 0; cDepthBits:16; // usamos un buffer de profundidad de 16 bits cStencilBits:0; // ni buffer de Stencil cAuxBuffers:0; // sin buffers auxiliares iLayerType:PFD_MAIN_PLANE; // Solo usamos un plano principal 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 el la elección del formato.', 'Error', MB_ICONERROR or MB_OK); exit; end; if (SetPixelFormat(DC, pixelFormat, @pfd) <> TRUE) then begin MessageBox(WindowFromDC(DC), 'Fallo en la elección del formato.', 'Error', MB_ICONERROR or MB_OK); exit; end;end;

procedure GLInit;const light0_position:array [0..3] of GLfloat=( 4.0, 4.0, 4.0, 0.0); specular: array [0..3] of GLfloat=( 1.0, 1.0, 0.0, 1.0); diffuse: array [0..3] of GLfloat=( 1.0, 1.0, 1.0, 0.7);begin // Asignamos una posición de observación para la escena glMatrixMode(GL_PROJECTION); glFrustum(-0.1, 0.1, -0.1, 0.1, 0.3, 25.0); //Cargamos la matriz de modelado... glMatrixMode(GL_MODELVIEW); //Definimos un nuevo modelo de despliegue... glShadeModel(GL_FLAT); //Habilitamos la prueba de profundidad... glEnable(GL_DEPTH_TEST);

//Habilitamos una luz... glEnable(GL_LIGHTING); glLightfv(GL_LIGHT0, GL_POSITION, @light0_position); glLightfv(GL_LIGHT0, GL_DIFFUSE, @diffuse);

�25

Page 26: OpenGL Delphi

Interacción de gráficos con dispositivos

glLightfv(GL_LIGHT0, GL_SPECULAR, @specular); glEnable(GL_LIGHT0); glEnable(GL_COLOR_MATERIAL);end;

procedure TForm1.FormCreate(Sender: TObject);var DC:HDC; RC:HGLRC;begin //Creamos un comtrol para correr OpenGL sobre él... GLC:=TGLControl.Create(self); GLC.Parent:=self; GLC.Left:=10; GLC.Top:=10; GLC.Width:=300; GLC.Height:=300; //Obtenemos el manejador del control para crear un contexto OpenGL en él... DC:=GetDC(GLC.Handle); //Asignamos un formato de pixelado al contexto... SetupPixelFormat(DC); RC:=wglCreateContext(DC); //creamos propiamente el Contexto... wglMakeCurrent(DC, RC); //y lo activamos GLInit; //Inicializamos las variables OpenGL //Conectamos los eventos de nuestro control con los procedimientos que definimos... GLC.OnMouseDown:=GLMouseDown; GLC.OnMouseMove:=GLMouseMove; GLC.OnMouseUp:=GLMouseUp; //Empezamos dibujando un Cono... ComboBox1.ItemIndex:=0;end;

procedure TForm1.Draw;var quad:GLUquadricObj; //Usamos una variable de este tipo para dibujar figuras complejas...begin // Borramos los buffers... glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); glLoadIdentity; //Trasladamos la escena respecto al TrackBar... glTranslatef(0.0, 0.0, -TrackBar1.Position/10); //Rotamos la escena respecto a nuestras variables de control... glRotatef(alpha+dx,0.0,1.0,0.0); glRotatef(betha+dy,1.0,0.0,0.0); // Ahora dibujamos el cuerpo ... quad:=gluNewQuadric; case cuerpo of cono: gluCylinder(quad,0.5,0.0,1,48,1); cilindro:gluCylinder(quad,0.5,0.5,1,48,1); esfera: gluSphere(quad,0.5,32,32); end; glTranslatef(-2,0,0); gluDeleteQuadric(quad); //Mostramos la escena construida... SwapBuffers(wglGetCurrentDC);end;

procedure TForm1.TrackBar1Change(Sender: TObject);begin Draw;end;

�26

Page 27: OpenGL Delphi

Interacción de gráficos con dispositivos

procedure TForm1.FormPaint(Sender: TObject);begin Draw;end;

procedure TForm1.GLMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);begin Label1.Caption:=Format('abajo: %d, %d',[X,Y]); mStartX:=X; mStartY:=Y;end;

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

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

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

end.

27

Page 28: OpenGL Delphi

Tipos de proyecciones y manejo de la cámara en

OpenGL

Como pasar de coordenadas 3D a coordenadas de dos dimensiones, y como

interactuar con la cámara.

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

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

que finalmente aparecerá en nuestra pantalla.

La proyección OrtográficaEste 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 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!!

Figura 1. Proyección Ortográfica

28

Page 29: OpenGL Delphi

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

La proyección en Perspectiva

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

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

Figura 2. Proyección en Perspectiva

29

Page 30: OpenGL Delphi

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

Figura 3. Las variables de la cámara

el "at". Variando el "up" variamos la orientación, en la figura 3 podemos verlo gráficamente.

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.

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 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.Figura 4. Nuestro mundo virtual

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

type TForm1 = class(TForm) Timer1: TTimer;

Listado 1

30

Page 31: OpenGL Delphi

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

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

31

Page 32: OpenGL Delphi

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

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

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

32

Page 33: OpenGL Delphi

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

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;

33

Page 34: OpenGL Delphi

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

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

34

Page 35: OpenGL Delphi

Figura 1. Calculo de normales para una cara.

Iluminación con OpenGL (I)

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 NormalesPara 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 (ya lo hemos estudiado en artículos anteriores). El resultado de esta operación será un 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 qué 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

35

Page 36: OpenGL Delphi

Iluminación con OpenGL (I)

Figura 2. La forma de despliegue de nuestra escena

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, (¡Vaya, 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ónYa 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 dos 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 en el sentido de 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 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 empieza 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

36

Page 37: OpenGL Delphi

Iluminación con OpenGL (I)

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

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

Listado 1

unit Unit1;

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

37

Page 38: OpenGL Delphi

Iluminación con OpenGL (I)

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;

38

Page 39: OpenGL Delphi

Iluminación con OpenGL (I)

P := gluNewQuadric; Case Form2.RadioGroup1.ItemIndex of 0:gluQuadricNormals(p,GLU_FLAT); 1:gluQuadricNormals(p,GLU_SMOOTH); end; 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.

39

Page 40: OpenGL Delphi

Iluminación con OpenGL (I)

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

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;

40

Page 41: OpenGL Delphi

Iluminación con OpenGL (I)

var Form2: TForm2;

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 /10end;

procedure TForm2.TrackBar3Change(Sender: TObject);begin glfLightDiffuse[1] := TrackBar3.Position /10end;

procedure TForm2.TrackBar4Change(Sender: TObject);begin glfLightDiffuse[2] := TrackBar4.Position /10end;

end.

41

Page 42: OpenGL Delphi

Figura 1

Figura 2

Iluminación con OpenGL (2)Más sobre Luces, Definición de Materiales,

Tipos de Reflexiones.

Bien, continuaremos con la serie sobre iluminación que iniciamos en el número anterior, pero esta vez hablaremos un poco más técnicamente acerca de las luces, y de los modos en como ésta es recibida por diferentes cuerpos.

Más parámetros para las lucesAparte de las características que ya habíamos mencionado en el artículo pasado, existen otras mas que también tienen cierta relevancia en cuanto a los efectos de iluminación de escenas. Vamos a ver estas características hablando un poco de Física para tratar de entenderlas.Primero hablemos de la atenuación con la distancia. Con esto nos estamos refiriendo a la atenuación que sufre la luz a medida que se desplaza. Está claro que a más lejos esté un objeto de una fuente luminosa, menos iluminado resultará. Pues bien, ¡esa es la idea!. Veamos la formula que presentamos en la Figura 1. Esta función sirve para atenuar la luz con la distancia, y como podemos ver involucra 3 parámetros (a, b y c).

El primero de estos (a), se refiere a la atenuación constante que sufre sin importar la distancia. Es el desgaste natural de la energía luminosa. En OpenGL la llamamos GL_CONSTANT_ATTENUATION y por defecto tiene el valor de 1.0 si este no se especifica.El segundo (b), es la atenuación lineal, y la llamamos así porque es el parámetro que se multiplica linealmente por la distancia en nuestra formula. Lo cual nos da un factor que se incrementa linealmente a medida que aumenta la distancia. En OpenGL lo llamamos GL_LINEAR_ATTENUATION y por defecto es igual a 0.El tercero (c), es la atenuación cuadrática, y a ésta la llamamos así porque a este parámetro lo multiplicamos por el cuadrado de la distancia a aplicar. Con esto obtenemos un valor aún mayor que con la atenuación lineal, y por lo mismo una atenuación más severa. En OpenGL hacemos referencia a este parámetro con GL_QUADRATIC_ATTENUATION que por defecto es igual a 0. Y ahora, ¿de qué nos sirve saber esto?... Bien, pues como pueden darse cuenta, la función en realidad representa una fracción de 1 sobre algo... Pues OpenGL reduce la intensidad de la luz que llega a un determinado lugar con esta fracción, evidentemente esto de manera inversamente proporcional a la distancia a la que se encuentre dicho lugar.Para indicar estos parámetros de las luces a OpenGL usamos la ya conocida función glLightf(); a la que, como han de recordar, le pasamos tres argumentos, el primero es la luz que

nos interesa afectar, el segundo es el parámetro a modificar, y el tercero es el valor ó valores que tomará dicho parámetro. Así pues para indicar estos tres parámetros para la luz número 3 de nuestra escena usaríamos:glLightf(GL_LIGHT3,GL_CONSTANT_ATTENUATION,0.8);glLightf(GL_LIGHT3,GL_LINEAR_ATTENUATION,0.5);glLightf( GL_LIGHT3, GL_QUADRATIC_ATTENUATION, 0.1 ) ;Otras características adicionales que se aplican de manera similar a estas que vimos, las encontramos en lo que sería la definición del cono de luz que forma cada una de nuestras fuentes de iluminación.Veamos la Figura 2... en ella encontramos el anglicismo

“Cut-Off”, que se refiere a la línea que corta la región que resultará iluminada por nuestra luz (¡¡Tan sencillo que sería decirlo en Español!!), sin embargo el ambiente de los gráficos está plagado de anglicismos que dicen mucho en pocas letras, y no nos queda más remedio que vivir con eso por ahora.En fin, para definir el cono de nuestra luz usamos los siguientes parámetros:GL_SPOT_CUTOFF para definir el cono. En este parámetro lo que hacemos es definir un ángulo de abertura para el cono, con un valor que puede ir de los 0.0 a los 90.0 grados. Y en caso de que no nos interese definir un ángulo, usando un valor de 180° estaremos desactivando esta opción. GL_SPOT_DIRECTION, el cual nos sirve para restringir la dirección de la luz emitida. Es un vector de tres valores reales que corresponden al vector direccional que indica hacia adonde apunta nuestro foco. Por defecto esa dirección es la de las Z's negativas.Y por último GL_SPOT_EXPONENT, que regula la pérdida de intensidad de la luz a medida que nos alejamos del centro del cono, algo similar a lo que habíamos mencionado sobre la atenuación con la distancia. Los valores que este parámetro puede tomar fluctúan entre 0.0 y 128.0. ¿Intuimos como se le indican estos valores a cada luz en OpenGL?

Los materiales...No es lo mismo iluminar un brillante que un pedazo de madera, como tampoco es lo mismo iluminar un pedazo de metal que un trozo de tela, o de plástico. Esto porque existen materiales en el mundo real que son más opacos que otros, y

42

Page 43: OpenGL Delphi

Iluminación con OpenGL (2)

Tabla 1

GLenum face GLenum pname const GLfloat* params

GL_FRONT GL_DIFFUSE ( R, G, B, 1.0 )

GL_BACK GL_AMBIENT ( R, G, B, 1.0 )

GL_FRONT_AND_BACK GL_AMBIENT_AND_DIFFUSE ( R, G, B, 1.0 )

GL_EMISSION ( R, G, B, 1.0 )

GL_SPECULAR ( R, G, B, 1.0 )

GL_SHININESS [ 0, 128 ]

otros que por si mismos (debido al color, o al propio material) generan cierta luminosidad, por ejemplo los colores fluorescentes. Y OpenGL nos permite definir materiales con todas estas características, tomando propiamente de principios físicos todo lo que hemos estado mencionando.A cada pedazo de la geometría que compone nuestra escena podemos asignarle un material distinto, y así también poder representar objetos complejos compuestos de diversos materiales, como por ejemplo un piano que tenga las teclas blancas de marfil y los bemoles de ébano (Disculpen, soy un gran aficionado a la música); ambos materiales responderán de un modo distinto a una misma luz, y esa diferencia debe ser notoria si deseamos hacer una escena lo suficientemente realista.Para un material se definen cinco caracter ísticas fundamentales. Estos componentes son: Ø� Reflexió�n difusa (diffuse) o color de base que reflejarí�a el objeto si incidiera sobre é�l una luz pura blanca, la mayorí�a de las veces esta denota el color del material que estamos definiendo. Ø� Reflexió�n especular (specular), que se refiere a los "puntitos brillantes" de los objetos iluminados. Ø� Reflexió�n ambiental (ambient) , define como un objeto ó� polí�gono determinado refleja la luz que no viene directamente de una fuente luminosa, sino de la escena en sí�. Ø� Coeficiente de brillo o "shininess". Define la cantidad de puntos luminosos y su concentració�n. Digamos que variando este pará�metro podemos conseguir un objeto má�s o menos cercano al metal, por ejemplo. Ø� Coeficiente de emisió�n (emission) o color de la luz que emite el mismo objeto. Lo que les mencionaba hace un momento de la fosforescencia. Las componentes ambiental y difusa son t í�picamente iguales o muy semejantes. La componente especular suele ser gris o blanca. El brillo nos determinará� el tamañ�o del punto de má�xima reflexió�n de luz y esto nos permite obtener efectos muy variados y llamativos.Se pueden especificar diferentes pará�metros en cuanto a material para cada polí�gono. Es una tarea ardua, pero ló�gicamente a má�s variedad de comportamientos má�s real será� la escena. El funcionamiento es el normal en OpenGL. Cada vez que se llama a la correspondiente funció�n se activan esos valores que no cambiará�n hasta llamarla de nuevo con otros. Por tanto todo lo que se "renderice" (por decir un palabro, como dijeran en Españ�a) a partir de una llamada heredará� esas caracterí�sticas. La funció�n es: glMaterialfv ( GLenum face, GLenum pname, const GLfloat *params ) ;Ahora echemos un vistazo a la Tabla 1. En ella se observan los valores que pueden adoptar los pará�metros de la funció�n. En el caso de face tenemos tres posibilidades dependiendo de si la caracterí�stica en cuestió�n debe

aplicarse al lado visible (Front), al no visible (Back) o a ambos. Aquí� debemos tomar en cuenta que puede no

interesarnos el có�mo se vea la parte no visible de una cara si esta nunca se va a presentar al usuario, por ejemplo en un cubo cerrado cuyas caras interiores nunca se ven. En cuanto a pname se especifica aquí� cuá�l es la caracterí�stica que vamos a definir en concreto. Las posibles son las que hemos comentado para un material. De hecho son bastante obvias si miramos las constantes que podemos usar. Por ú�ltimo *params, donde damos los valores concretos de la caracterí�stica. Son tres valores, de hecho tres nú�meros reales que especifican un color RGB. Ese color define exactamente có�mo debe verse el objeto que se renderice despué�s en cuanto a color ambiente, difusi ó�n, componente especular, etc. Hay una excepció�n en el caso de GL_SHININESS. Si usamos esta constante como segundo pará�metro, el tercero tendrá� que ser un nú�mero entre 0 y 128 que controlará� la concentració�n del brillo. Por defecto este valor vale 0. La misma funció�n tiene tambié�n las formas glMaterialf, glMateriali y glMaterialiv. No suelen usarse, por eso las versiones llamadas escalares (enteras), ya que só�lo son ú�tiles para definir GL_SHININESS. Los Valores tí�picos, son los usados por defecto, son de 0.8 para las tres componentes en GL_DIFFUSE, de 0.2 para GL_AMBIENT y de 0.0 en GL_EMISSION y GL_SPECULAR. Por supuesto tendremos que retocar estos valores hasta conseguir el efecto deseado, ya que si los dejamos como está�n no nos gustará� lo que veremos. Por cierto que el cuarto valor, es decir el 1.0, se refiere al valor del canal alfa del color RGB. Ya llegará� má�s adelante el momento de estudiar esto cuando hablemos de transparencias.

¡�Al Ejemplo!Veamos lo que nos muestra esta vez el Listado 1. Como verá�n, lo que pretendemos hacer en este programa es mostrar có�mo podemos hacer diferentes combinaciones de las caracterí�sticas que hemos mencionado, para obtener variados materiales a partir de prá�cticamente los mismos valores.En realidad, mucho de lo que mostramos en el ejemplo ya lo habí�amos mencionado en artí�culos anteriores, así� que nos centraremos en la parte donde definimos las caracterí�sticas de la luz, y el material a usar. Como ven, la mayor parte de estas caracterí�sticas las definimos como arreglos que representan vectores. Algunos de estos vectores

43

Page 44: OpenGL Delphi

Iluminación con OpenGL (2)

Listado 1

unit Unit1;

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

uses Dialogs;

{$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);const ambient:Array[0..3] of GLfloat =(0.0, 0.0, 0.0, 1.0 ); //Luz ambiental diffuse:Array[0..3] of GLfloat=( 1.0, 1.0, 1.0, 1.0 ); //Luz Difusa specular:Array[0..3] of GLfloat=( 1.0, 1.0, 1.0, 1.0 ); //Luz Especular position:Array[0..3] of GLfloat=(0.0, 3.0, 2.0, 0.0 ); //Posición de la luz no_mat:Array[0..3] of GLfloat=( 0.0, 0.0, 0.0, 1.0 ); //Material negro, o sin color... mat_ambient:Array[0..3] of GLfloat=( 0.7, 0.7, 0.7, 1.0 ); // Nuestro color de material ambiental... mat_ambient_color:Array[0..3] of GLfloat=(0.8, 0.8, 0.2, 1.0 );// Un color de material combinado... mat_diffuse:Array[0..3] of GLfloat=(0.1, 0.5, 0.8, 1.0); //Reflexión difusa... mat_specular:Array[0..3] of GLfloat=( 1.0, 1.0, 1.0, 1.0 ); //Reflexión Especular... no_shininess:GLfloat=0.0; //Sin brillo... low_shininess:GLfloat=5.0; //Bajo Brillo... high_shininess:GLfloat=100.0; //Alto Brillo... mat_emission:Array[0..3] of GLfloat=(0.3, 0.2, 0.2, 0.0); //Luz emitida por el material...begin ActivateRenderingContext(Canvas.Handle,RC); // Hacer el contexto dibujable

44

Page 45: OpenGL Delphi

Iluminación con OpenGL (2)

glClearColor(0.0, 0.1, 0.1, 0.0); // Color de Fondo...

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

glMatrixMode(GL_MODELVIEW); glLoadIdentity; glulookat(0,0,20,0,0,19,0,1,0); //Definimos el puerto de visión....

glEnable(GL_DEPTH_TEST); //Habilitamos la prueba en profundidad...

glLightfv(GL_LIGHT0, GL_AMBIENT, @ambient); //Se definen glLightfv(GL_LIGHT0, GL_DIFFUSE, @diffuse); //Las Cacarterísticas de la luz glLightfv(GL_LIGHT0, GL_POSITION, @position);

//Se habilitan las luces... glEnable(GL_LIGHTING); glenable(GL_LIGHT0);

//Se dibujan las esferas...//* Esfera en primer renglón, primera columna//* solo reflexión difusa ; sin brillo glPushMatrix(); glTranslatef (-3.75, 3.0, 0.0); glMaterialfv(GL_FRONT, GL_AMBIENT, @no_mat); glMaterialfv(GL_FRONT, GL_DIFFUSE, @mat_diffuse); glMaterialfv(GL_FRONT, GL_SPECULAR, @no_mat); glMaterialfv(GL_FRONT, GL_SHININESS, @no_shininess); glMaterialfv(GL_FRONT, GL_EMISSION, @no_mat); glrotatef(360-4*angle,1,1,1); glutSolidSphere(1.0,10,10); glPopMatrix();

//* Esfera en primer renglón, segunda columna//* solo reflexión difusa y especular ; bajo en brillo glPushMatrix(); glTranslatef (-1.25, 3.0, 0.0); glMaterialfv(GL_FRONT, GL_AMBIENT, @no_mat); glMaterialfv(GL_FRONT, GL_DIFFUSE, @mat_diffuse); glMaterialfv(GL_FRONT, GL_SPECULAR, @mat_specular); glMaterialfv(GL_FRONT, GL_SHININESS, @low_shininess); glMaterialfv(GL_FRONT, GL_EMISSION, @no_mat); glrotatef(360-4*angle,1,1,1); glutSolidSphere(1.0,10,10); glPopMatrix();

//* Esfera en primer renglón, tercera columna//* solo reflexión difusa y especular ; alto en brillo

glPushMatrix(); glTranslatef (1.25, 3.0, 0.0); glMaterialfv(GL_FRONT, GL_AMBIENT, @no_mat); glMaterialfv(GL_FRONT, GL_DIFFUSE, @mat_diffuse); glMaterialfv(GL_FRONT, GL_SPECULAR, @mat_specular); glMaterialfv(GL_FRONT, GL_SHININESS, @high_shininess); glMaterialfv(GL_FRONT, GL_EMISSION, @no_mat); glrotatef(360-4*angle,1,1,1); glutSolidSphere(1.0,10,10); glPopMatrix();

//* Esfera en primer renglón, Cuarta columna//* solo reflexión difusa y emisión sin brillo

45

Page 46: OpenGL Delphi

Iluminación con OpenGL (2)

glPushMatrix(); glTranslatef (3.75, 3.0, 0.0); glMaterialfv(GL_FRONT, GL_AMBIENT, @no_mat);

glMaterialfv(GL_FRONT, GL_DIFFUSE, @mat_diffuse); glMaterialfv(GL_FRONT, GL_SPECULAR, @no_mat); glMaterialfv(GL_FRONT, GL_SHININESS, @no_shininess); glMaterialfv(GL_FRONT, GL_EMISSION, @mat_emission); glrotatef(360-4*angle,1,1,1); glutSolidSphere(1.0,10,10); glPopMatrix();

//* Esfera en Segundo renglón, Primera columna//* solo reflexión de ambiente y difusa, sin brillo

glPushMatrix(); glTranslatef (-3.75, 0.0, 0.0); glMaterialfv(GL_FRONT, GL_AMBIENT, @mat_ambient);

glMaterialfv(GL_FRONT, GL_DIFFUSE, @mat_diffuse); glMaterialfv(GL_FRONT, GL_SPECULAR, @no_mat); glMaterialfv(GL_FRONT, GL_SHININESS, @no_shininess); glMaterialfv(GL_FRONT, GL_EMISSION, @no_mat); glrotatef(360-4*angle,1,1,1); glutSolidSphere(1.0,10,10); glPopMatrix();

//* Esfera en Segundo renglón, Segunda columna//* Reflexión de ambiente, difusa y especular, bajo en brillo

glPushMatrix(); glTranslatef (-1.25, 0.0, 0.0); glMaterialfv(GL_FRONT, GL_AMBIENT, @mat_ambient);

glMaterialfv(GL_FRONT, GL_DIFFUSE, @mat_diffuse); glMaterialfv(GL_FRONT, GL_SPECULAR, @mat_specular); glMaterialfv(GL_FRONT, GL_SHININESS, @low_shininess); glMaterialfv(GL_FRONT, GL_EMISSION, @no_mat); glrotatef(360-4*angle,1,1,1); glutSolidSphere(1.0,10,10); glPopMatrix();

//* Esfera en Segundo renglón, Tercera columna//* Reflexión de ambiente, difusa y especular, alto en brillo

glPushMatrix(); glTranslatef (1.25, 0.0, 0.0); glMaterialfv(GL_FRONT, GL_AMBIENT, @mat_ambient);

glMaterialfv(GL_FRONT, GL_DIFFUSE, @mat_diffuse); glMaterialfv(GL_FRONT, GL_SPECULAR, @mat_specular); glMaterialfv(GL_FRONT, GL_SHININESS, @high_shininess); glMaterialfv(GL_FRONT, GL_EMISSION, @no_mat); glrotatef(360-4*angle,1,1,1); glutSolidSphere(1.0,10,10); glPopMatrix();

//* Esfera en Segundo renglón, Cuarta columna//* Reflexión de ambiente y difusa; con emisión, sin brillo

glPushMatrix(); glTranslatef (3.75, 0.0, 0.0);

46

Page 47: OpenGL Delphi

Iluminación con OpenGL (2)

glMaterialfv(GL_FRONT, GL_AMBIENT, @mat_ambient); glMaterialfv(GL_FRONT, GL_DIFFUSE, @mat_diffuse); glMaterialfv(GL_FRONT, GL_SPECULAR, @no_mat); glMaterialfv(GL_FRONT, GL_SHININESS, @no_shininess); glMaterialfv(GL_FRONT, GL_EMISSION, @mat_emission); glrotatef(360-4*angle,1,1,1); glutSolidSphere(1.0,10,10); glPopMatrix();

//* Esfera en Tercer renglón, Primera columna//* Reflexión de ambiente y difusa, sin brillo...

glPushMatrix(); glTranslatef (-3.75, -3.0, 0.0); glMaterialfv(GL_FRONT, GL_AMBIENT, @mat_ambient_color);

glMaterialfv(GL_FRONT, GL_DIFFUSE, @mat_diffuse); glMaterialfv(GL_FRONT, GL_SPECULAR, @no_mat); glMaterialfv(GL_FRONT, GL_SHININESS, @no_shininess); glMaterialfv(GL_FRONT, GL_EMISSION, @no_mat); glrotatef(360-4*angle,1,1,1); glutSolidSphere(1.0,10,10); glPopMatrix();

//* Esfera en Tercer renglón, Segunda columna//* Reflexión de color del ambiente, difusa y especular, bajo en brillo

glPushMatrix(); glTranslatef (-1.25, -3.0, 0.0); glMaterialfv(GL_FRONT, GL_AMBIENT, @mat_ambient_color); glMaterialfv(GL_FRONT, GL_DIFFUSE, @mat_diffuse); glMaterialfv(GL_FRONT, GL_SPECULAR, @mat_specular); glMaterialfv(GL_FRONT, GL_SHININESS, @low_shininess); glMaterialfv(GL_FRONT, GL_EMISSION, @no_mat); glrotatef(360-4*angle,1,1,1); glutSolidSphere(1.0,10,10); glPopMatrix();

//* Esfera en Tercer renglón, Tercera columna//* Reflexión del color del ambiente, difusa y especular; alto en brillo

glPushMatrix(); glTranslatef (1.25, -3.0, 0.0); glMaterialfv(GL_FRONT, GL_AMBIENT, @mat_ambient_color); glMaterialfv(GL_FRONT, GL_DIFFUSE, @mat_diffuse); glMaterialfv(GL_FRONT, GL_SPECULAR, @mat_specular); glMaterialfv(GL_FRONT, GL_SHININESS, @high_shininess); glMaterialfv(GL_FRONT, GL_EMISSION, @no_mat); glrotatef(360-4*angle,1,1,1); glutSolidSphere(1.0,10,10); glPopMatrix();

//* Esfera en Tercer renglón, Cuarta columna//* Reflexión del color del ambiente y difusa; con emisión; sin brillo

glPushMatrix(); glTranslatef (3.75, -3.0, 0.0); glMaterialfv(GL_FRONT, GL_AMBIENT, @mat_ambient_color); glMaterialfv(GL_FRONT, GL_DIFFUSE, @mat_diffuse); glMaterialfv(GL_FRONT, GL_SPECULAR, @no_mat); glMaterialfv(GL_FRONT, GL_SHININESS, @no_shininess); glMaterialfv(GL_FRONT, GL_EMISSION, @mat_emission);

47

Page 48: OpenGL Delphi

Iluminación con OpenGL (2)

glrotatef(360-4*angle,1,1,1); glutSolidSphere(1.0,10,10); glPopMatrix();//Se terminan las esferas

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.

sirven para definir colores, y otros coordenadas.Por ejemplo, para el caso de los arreglos que definen nuestra luz, vemos que usamos una luz que no tiene efecto sobre el ambiente, y que es completamente blanca (¡pura como la nieve!), que está colocada en las coordenadas: (0.0,3.0,2.0). Para el caso del material tenemos m ás arreglos, tenemos un arreglo con valores de 0, para determinar cuando no usaremos color para definir alguna característica del material (ambiente, difusa, especular, brillo), y del mismo modo para el brillo tenemos tres posibles valores, que puede ser un alto coeficiente de brillo, o uno bajo, o de plano ningún brillo.Bien, nuestro demo lo construiremos a partir de esferas usando un procedimiento de la librería GLUT: glutSolidSphere() al cual se le pasan como parámetros el radio que tendrá esa esfera, así como el número de meridianos y paralelos que la definirán. Por el mismo hecho de que estamos definiendo esferas, solo usamos GL_FRONT para las características, ya que al ser éstas objetos cerrados, nunca veremos la cara posterior de los polígonos que las forman.

Dibujaremos las esferas situándolas en 3 líneas y 4 columnas, ordenadas de acuerdo a las características que presenta el material usado para construirla. Así tenemos que en la primera y la cuarta columna no usamos ning ún coeficiente de brillo, en la segunda usamos un brillo alto, y en la tercera un brillo bajo.Y así vamos haciendo combinaciones. Si se fijan un poco verán por ustedes mismos cuales han sido los criterios que hemos aplicado a cada una de las filas y columnas en nuestra escena de esferas. En algunas usamos valores de emisión, en otras no; y en el caso de la tercera fila usamos un color de material un poco diferente. Observen como cada vez que vamos a dibujar, debemos definir de nuevo todas las características del material a utilizar, porque se conservan las anteriores, ¿Recuerdan lo de la máquina de estados que mencionábamos?.Y hasta aquí llegamos por ahora con la iluminación, en lo que se refiere a sus conceptos básicos. Más adelante, en posteriores artículos, y cuando ya estemos más avanzados hablaremos de conceptos que tienen que ver también con el

48

Page 49: OpenGL Delphi

Iluminación con OpenGL (2)

tratamiento de la luz, pero que se estudian desde otros puntos de vista, tales como las superficies que reflejan imágenes (espejos), los cuerpos transparentes, y los efectos de niebla, entre otros. Como ven, aún quedan muchos temas por estudiar. Hasta Pronto.

49

Page 50: OpenGL Delphi

Texturas con OpenGL(I)

Principios básicos de texturización.

Figura 1.- La textura original.

Figura 2.- La misma textura rotada 45 grados.

Figura 3.- La misma textura despu és de haber

sido escalada linealmente.

En esta ocasi ón tocaremos el tema de las texturas. Este es un tema bastante interesante ya que aunque de primera vista pareciera sencilla la idea de poner una imagen sobre una cara de una figura, en realidad es algo que involucra muchas cuestiones adicionales.Las texturas vienen a darle un mayor realismo a las escenas que elaboramos no solo con OpenGL, sino con cualquier motor de gr á ficos. Estas nos pueden ser útiles para representar de manera sencilla la rugosidad ó la finura de una superficie. Bien dicen que “Una imagen dice más que mil palabras”; pues esto tambi é n es muy aplicable a ún propiamente a las cuestiones de los gr á ficos.Y en cuanto a la aplicabilidad suele ser tambi én muy variada. Pueden servir por ejemplo para representar el nivel de altitud de una coordenada sobre un terreno, o simplemente para definir el “estampado” que presenta una pared en una habitaci ón. El caso es que las texturas son algo indispensable a la hora de generar gr á ficos de alto realismo.La texturizaci ón es t ípicamente usada para proveer detalles de color a superficies intrincadas. Por ejemplo, supongamos que deseamos modelar un mueble de madera barnizada, es necesario dibujar sobre las partes de ese mueble para que realmente parezca de madera. Aunque la texturizaci ón tambi é n puede ayudarnos a solucionar algunos otros problemas que resultar ían menos obvios, y que estudiaremos a su tiempo.

Una matriz especial para las texturasOpenGL soporta dos t é cnicas de texturizado: Por Manipulaci ón del Color de la Superficie y por Mapeado del Ambiente. La primera de estas se refiere a manipular directamente los colores de las superficies de las caras a afectar, sin tomar en cuenta nada m ás que el color de la textura a aplicar. Y la segunda consiste en involucrar los colores de los materiales de los objetos a texturizar, as í como las luces y las normales obteniendo una textura combinada con el ambiente.Hasta ahora ya hab íamos estudiado las matrices de modelado y proyecci ón, mencionando cada una de sus caracter ísticas y para qu é nos sirven. Pues es el turno de hablar de la matriz de texturas.Esta matriz, como las anteriores, nos va a servir para definir transformaciones lineales; en este caso para las texturas que utilicemos en nuestra aplicaci ón. Al igual que

a las figuras que defin íamos en la matriz de modelado les pod íamos aplicar escalamientos y rotaciones, a las texturas tambi é n podemos rotarlas y escalarlas respecto a cualquier coordenada, siempre teniendo en cuenta, claro, que estamos trabajando con superficies, y que las im á genes que manejaremos (por lo menos en este número) son de 2 dimensiones. Veamos las Figuras 1, 2 y 3 donde aparecen ejemplos de t r a n s f o r m a c i o n e s lineales aplicadas a una textura.OpenGL soporta imágenes de texturas de una y dos dimensiones (1D y 2D) y cuyas medidas sean una potencia de dos. Por ejemplo: i m á g e n e s bidimensionales de 64 X 64 p íxeles, de 256 X 128, etc. En a l g u n a s implementaciones de OpenGL se han extendido y se soportan texturas de 3 y 4 dimensiones tambi é n (3D y 4D), pero por ahora empezaremos por lo

50

Page 51: OpenGL Delphi

Figura 4.- Los 3 casos posibles de filtrado.

Figura 5.- Los criterios de los filtros.

sencillito.

Un concepto nuevo: Los TexelesUn Texel, vendría siendo como el elemento relativo a un píxel en un objeto Canvas, solo que aplicado a la lógica de una textura. Es decir, se refiere a cada uno de los píxeles de la imagen que forman nuestra textura, (o al menos así podemos nosotros entenderlo)Algo que debemos hacer primeramente es mapear la superficie que vamos a texturizar. Es decir, determinar las esquinas y las orillas de nuestra textura en la cara. Para que nos quede un poco más claro, supongamos que deseamos texturizar un cuadrado, bien, pues tenemos que decirle a nuestro motor de gráficos que esquina de nuestra textura es la que debe aplicar a qué vértice de nuestro cuadro, así podemos texturizar con la imagen invertida, ó en cualquier orientación. Cada vez que una primitiva de las que ya hemos estudiado como un cuadro, un triangulo, un polígono, etc, es mandada a dibujar texturizada se calcula una coordenada de textura para cada uno de los fragmentos de píxeles de esa primitiva. Esa coordenada de textura es usada para buscar un valor entre los texeles para ser aplicado al mapa de textura correspondiente. Las coordenadas del mapa de textura fluctúan entre un rango de 0 a 1. O sea que siempre tratamos al rectángulo de las imágenes como un área que va desde [0,0] hasta [1,1]; aunque también se pueden utilizar valores decimales para determinar una coordenada intermedia dentro de nuestro mapa de textura.

La Minificación y la Magnificación...Al momento de aplicarle una textura a una superficie, puede darse el caso de que esta superficie no sea del mismo tamaño en píxeles y texeles que nuestra imagen. Aquí pueden sucederse tres casos (Ver Figura 4), Que

tanto la superficie como la textura tengan el mismo tamaño, en cuyo caso la relación resulta ser lineal (1 Píxel = 1 Texel), o que la Superficie sea de un tamaño mayor a la imagen, o bien que sea la imagen la que resulte ser de un tamaño mayor.Para solucionar este tipo de problemas OpenGL implementa 2 tipos de filtros. A estos se les llama: Filtros de Minificación y de Magnificación.El Filtro de Minificación se ocupa cuando muchos texeles se mapean a un mismo píxel (Texel < Píxel), y el filtro de Magnificación se ocupa cuando muchos píxeles del despliegue están mapeados por un mismo texel (Texel > Píxel). No es tan complicado, ¿O sí?, Viendo la Figura 4 nos enteraremos mejor.El más simple de estos filtros es conocido como "Point Sampling" (Muestreo de puntos). En OpenGL denominamos a este filtro con la sentencia GL_NEAREST.

Este consiste en encontrar simplemente aquel Texel que resulte ser el más cercano a la coordenada de la textura, este es muy rápido, pero obviamente no el de mejores resultados. Sin embargo si es utilizado, porque funciona para aquellos casos en los que la velocidad es m ás importante, pero son más recomendables aquellos filtros que utilizan la interpolación de texeles.

Para magnificación OpenGL 1.0 soporta interpolación lineal entre cuatro valores de Texeles. Algunas otras versiones soportan además una filtración bicúbica, la cual consiste en hacer una suma ponderada de los valores de un arreglo de Texeles de 4 X 4 (a esta técnica de filtración también se le conoce con el nombre de Filter4) Para definir la interpolación lineal en OpenGL utilizaremos la sentencia GL_LINEAR.Para minificación OpenGL 1.0 soporta varios tipos de Mipmapping. EL mipmapping consiste en tomar diferentes ejemplos de la imagen (a cada nivel de estos

Texturas con OpenGL(I)

51

Page 52: OpenGL Delphi

Figura 6.- Los efectos de las funciones de ambiente.

se le llama mipmap) y a cada uno de estos aplicarle una interpolación distinta para después hacer una interpolación entre cada uno de estos mismos ejemplos, obteniendo así una mejor definición para el mapeado final. El más usado de estos filtros (y también el más caro computacionalmente hablando) es el de mipmapping trilineal, el cual consiste en tomar 4 ejemplos de cada uno de los 2 niveles de mipmaps más cercanos y entonces interpolar los dos conjuntos de ejemplos.OpenGL no provee de comandos para la construcción de mipmaps, pero la librería GLU nos proporciona algunas rutinas simples para generar mipmaps usando un filtro simple. En el ejemplo mostraremos como se construyen mipmaps con esta librería. Pero profundizaremos en su utilización en el siguiente número.En la Figura 5 se muestra gráficamente la aplicación de estos filtros.

Ahora otra cuestión, El ambiente...El proceso por el cual el valor final del fragmento de color es derivado, esta referido a la aplicación de la función de ambiente (TexEnv).Existen diversos métodos para computar el valor final del color a aplicar, cada uno de estos capaz de producir un particular efecto. Uno de los más comúnmente utilizados es el de la función Modulate. Para todos los propósitos prácticos la función modulate puede ser aplicada escalando el color del fragmento de color, con el color del texel co r re spond ien te . Típicamente las aplicaciones generan polígonos blancos, se iluminan y entonces se utiliza ese valor para e f e c t i v a m e n t e producir una superficie iluminada y texturizada. Pero ya veremos la aplicación de las luces a cuerpos texturizados en el siguiente número. Por ahora veremos que cuando no utilizamos luces en nuestra escena la función modulate lo

que hace es escalar el color de la textura a partir del color original de la superficie. La función Modulate se define en OpenGL con la sentencia GL_MODULATE.La función de ambiente Decal, simplemente superpone el color de la textura al color de la superficie, aplicando también efectos de Alpha Blending cuando se trabaja con sistemas RGBA. Existe también una generalización de este filtro que se denomina Replace que trabaja de una manera muy similar. Ya veremos más claramente su efecto en el ejemplo. Estos filtros se definen en OpenGL con las sentencias GL_DECAL y GL_REPLACE respectivamente.Una última función es la denominada Blend, la cual hace una combinación entre el color de la superficie y el color de la textura. En OpenGL esta se determina con la sentencia GL_BLEND.En la Figura 6 tenemos una muestra de los efectos que obtenemos utilizando cada una de las diferentes funciones de ambiente a una figura.

¿Qué esperamos para verlo funcionando?Bien, vemos lo que esta vez nos muestran los listados esta vez:Lo que trataremos de hacer es una librería de clases para manejar de manera práctica las texturas en Delphi. Si una de las principales características que hacen de Delphi un lenguaje tan poderoso es precisamente el que sea un lenguaje orientado a objetos, estaría mal que nosotros no

utilizáramos esa ventaja a la hora de programar gráficos, ¿no les parece?.Así que lo que empezaremos por definir en el Listado 1 es una clase a la que l l a m a r e m o s TTextura, que será la

que nos servirá para encapsular todo el trabajo de definición de texturas en OpenGL. Analicemos cada uno de los campos y métodos que utilizaremos en nuestra clase:

unit Texturas;interfaceuses OpenGL, Graphics;type //Definición de la enumeración para difinir resoluciones de texturas... TDefinicion = (dAlta, dMedia, dBaja); //Declaración de la Clase para manejar las Texturas... TTextura = Class(TObject)

Listado 1

Texturas con OpenGL(I)

52

Page 53: OpenGL Delphi

private IH,IW:integer; //Alto y ancho de la Imágen ... public img: Array [0..196607] of GLUByte; //Arreglo para almacenar la textura... ModeEnv: integer; // La función de ambiente a utilizar... Definicion : TDefinicion; constructor Create; Procedure Cargarde(im:Tbitmap); //Cargar desde un Bitmap... Procedure UsaTextura; //Habilitar y usar una textura... Procedure InhibeTexturas; //Inhabilitar las texturas... end;implementationConstructor TTextura.Create;begin inherited Create; ModeEnv := GL_DECAL; //Esto puede ser GL_DECAL ó GL_MODULATE ó GL_REPLACE ó GL_BLEND; Definicion := dalta;end;Procedure TTextura.CargarDe(im:TBitmap);var i,j:integer;begin IW := im.Width; IH := im.Height; for i:=0 to ih do begin for j:=0 to iW do begin img[i*iW*3+j*3 ]:=byte(im.Canvas.Pixels[j,i] mod 256); img[i*iW*3+j*3+1]:=byte(im.Canvas.Pixels[j,i] shr 8) mod 256; img[i*iW*3+j*3+2]:=byte(im.Canvas.Pixels[j,i] shr 16) mod 256; end; end;end;Procedure TTextura.UsaTextura;begin Case Definicion of dAlta: begin glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST); end; dMedia: begin glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); end ; dBaja: begin glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); end; end; glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, ModeEnv);If (Definicion = dAlta) then gluBuild2DMipmaps(GL_TEXTURE_2D, 3, IW, IH,GL_RGB, GL_UNSIGNED_BYTE, @img) else glTexImage2D(GL_TEXTURE_2D, 0, 3, IW, IH, 0, GL_RGB, GL_UNSIGNED_BYTE, @img); glenable(GL_TEXTURE_2D);end;Procedure TTextura.InhibeTexturas;begingldisable(GL_TEXTURE_2D);end;

end.

Texturas con OpenGL(I)

53

Page 54: OpenGL Delphi

Primeramente, antes de la definición de la clase, nos encontramos con un tipo enumerado TDefinición, el cual nos servirá para determinar con qué calidad deseamos presentar la texturización donde sus elementos son dAlta, dMedia y dBaja, los cuales corresponden a definición alta, de calidad media y calidad baja respectivamente.Ya dentro de la definición de TTextura encontramos 2 campos privados que son IH e IW, que nos servirán para almacenar las dimensiones (Ancho y Alto) de la imagen original que utilizamos para la construcción de la textura.En la sección pública encontramos más campos; el de img es un arreglo unidimensional donde almacenaremos propiamente la imagen, en forma de un buffer. En este arreglo de Bytes acomodaremos los valores RGB de cada uno de los píxeles de nuestra imagen original en forma lineal, para que pueda ser interpretada por los comandos de OpenGL. Las dimensiones del arreglo dependen del tamaño máximo que soportaremos para las imágenes. Hagan cuentas y determinarán si una imagen es soportada o no por este buffer. Nota: Recuerden que trabajamos con imágenes de dimensiones de potencias de dos.El campo ModeEnv nos servirá para definir el tipo de función de ambiente que utilizaremos para nuestra textura, ya sea GL_DECAL, GL_REPLACE, GL_MODULATE ó GL_BLEND. Y el campo Definición que usaremos para determinar el tipo de definición a usar en cada textura, ya sea Alta, Media o Baja.Dentro de los métodos, primero nos encontramos el constructor Create, en el cual lo que hacemos es invocar al constructor de la clase ancestra TObject con la instrucción inherited, y después asignar valores por omisión a los campos ModeEnv y Definición, en este caso usamos por defecto la función de ambiente GL_DECAL y una Definición Alta.El método CargarDe() lo utilizaremos para cargar nuestra textura en el buffer img a partir de un mapa de bits (TBitmap) Como pueden ver lo único que hacemos en este procedimiento es decomponer cada uno de los colores de los píxeles de nuestro mapa de bits en 3 valores que serán el código RBG de dicho color y acomodar estos valores en forma lineal en el buffer. De una manera similar podríamos implementar un método que cargara la textura desde un archivo en cualquier formato (BMP, JPG, WMF, etc.); Eso pueden tomarlo como una tarea para ustedes.El siguiente método UsaTextura() es con el que invocaremos propiamente a cada una de nuestras texturas en la aplicación. En este método es donde nos toca definir los filtros de Magnificación y Minificación que utilizaremos para cada una de las tres definiciones que contemplamos en nuestra clase. Observen que en la definición alta usamos mipmaping para la definición del filtro de minificación (ya que sólo en este caso funciona), y esto nos da una mayor calidad en cuanto a la presentación de la textura. En el caso de la definición media lo que hacemos es utilizar interpolación lineal para ambos filtros, y en la

definición baja usamos point sampling en ambos casos. Para definir el tipo de filtro a aplicar usamos la función glTexParameteri() utilizando como primer parámetro la sentencia GL_TEXTURE_2D para indicar que lo que haremos será modificar un parámetro de una textura bidimensional, el segundo parámetro es el tipo de filtro a definir, aquí usamos GL_TEXTURE_MAG_FILTER para magnificación, y GL_TEXTURE_MIN_FILTER para minificación, y el tercero es propiamente el filtro a aplicar.La función de ambiente se declara usando el procedimiento glTexEnvf() con los par ámetros: GL_TEXTURE_ENV , GL_TEXTURE_ENV_MODE y por último la función de ambiente a aplicar, que en nuestro caso estamos almacenando en el campo ModeEnv.Después definimos el buffer donde se encontrará nuestra textura y sus dimensiones. El caso de las definiciones de MipMaps las estudiaremos en el siguiente número, y en este nos centraremos en la definición de texturas simples usando la función glTexImage2D() a la cual le pasamos como parámetros las dimensiones de la imagen original, el tipo de datos que contiene el buffer (en este caso bytes sin signo), y la dirección de memoria donde se encuentra el buffer que contiene los códigos RBG, entre otros. Por último, solo debemos habilitar la texturización en la máquina de estados finitos de OpenGL, esto lo hacemos con la llamada de procedimiento glEnable(GL_TEXTURE_2D).Del mismo modo para inhibir la texturización desde cualquier instancia de esta clase llamaremos al método InhibeTexturas() el cual lo único que hace es deshabilitar esa característica dentro del autómata de OpenGL con glDisable(GL_TEXTURE_2D).Con esta definición de clase nos simplificaremos en gran medida la utilización de las texturas en nuestras aplicaciones, y la iremos enriqueciendo con el tiempo, ya lo verán.

Y después de la definición de la clase, las coordenadas del mapa de textura...Lo que haremos en esta ocasión es una pequeña animación con un cubo, una esfera y un cono, con texturas diferentes, y con una barra de herramientas para configurar el comportamiento de dichas texturas.El Listado 2 corresponde al código de nuestro formulario principal aquí es donde vemos la definición de 3 variables globales que contendrán las texturas que ocuparemos para

unit Unit1;interfaceuses Windows, Forms, OpenGL, Classes, ExtCtrls, Messages, Controls, StdCtrls,

Graphics, sysUtils, Dialogs, Texturas;

Texturas con OpenGL(I)

54

Page 55: OpenGL Delphi

type TForm1 = class(TForm) Timer1: TTimer; Image1: TImage; Image2: TImage; Image3: TImage; 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; Procedure Cubo; public { Public declarations } end;const DibujaCubo = 1;var Form1: TForm1; RC : HGLRC; Angulo : GLInt; TexBloques, TexRocas, TexTierra: TTextura; P:PGLUQUADRICOBJ;implementationuses Herramientas;{$R *.DFM}Procedure TForm1.Cubo;begin glBegin(GL_POLYGON); gltexcoord2i(0,0); glVertex3f(1.0, 1.0, 1.0); gltexcoord2i(0,1); glVertex3f(-1.0, 1.0, 1.0); gltexcoord2i(1,1); glVertex3f(-1.0, -1.0, 1.0); gltexcoord2i(1,0); glVertex3f(1.0, -1.0, 1.0); glEnd; glBegin(GL_POLYGON); gltexcoord2i(0,0); glVertex3f(1.0, 1.0, -1.0); gltexcoord2i(0,1); glVertex3f(1.0, -1.0, -1.0); gltexcoord2i(1,1); glVertex3f(-1.0, -1.0, -1.0); gltexcoord2i(1,0); glVertex3f(-1.0, 1.0, -1.0); glEnd; glBegin(GL_POLYGON); gltexcoord2i(0,0); glVertex3f(-1.0, 1.0, 1.0); gltexcoord2i(0,1); glVertex3f(-1.0, 1.0, -1.0); gltexcoord2i(1,1); glVertex3f(-1.0, -1.0, -1.0); gltexcoord2i(1,0); glVertex3f(-1.0, -1.0, 1.0); glEnd;

Texturas con OpenGL(I)

55

Page 56: OpenGL Delphi

glBegin(GL_POLYGON); glNormal3f(1.0, 0.0, 0.0); gltexcoord2i(0,0); glVertex3f(1.0, 1.0, 1.0); gltexcoord2i(0,1); glVertex3f(1.0, -1.0, 1.0); gltexcoord2i(1,1); glVertex3f(1.0, -1.0, -1.0); gltexcoord2i(1,0); glVertex3f(1.0, 1.0, -1.0); glEnd; glBegin(GL_POLYGON); gltexcoord2i(0,0); glVertex3f(-1.0, 1.0, -1.0); gltexcoord2i(0,1); glVertex3f(-1.0, 1.0, 1.0); gltexcoord2i(1,1); glVertex3f(1.0, 1.0, 1.0); gltexcoord2i(1,0); glVertex3f(1.0, 1.0, -1.0); glEnd; glBegin(GL_POLYGON); gltexcoord2i(0,0); glVertex3f(-1.0, -1.0, -1.0); gltexcoord2i(0,1); glVertex3f(1.0, -1.0, -1.0); gltexcoord2i(1,1); glVertex3f(1.0, -1.0, 1.0); gltexcoord2i(1,0); glVertex3f(-1.0, -1.0, 1.0); glEnd;end;procedure TForm1.FormCreate(Sender: TObject);begin RC:=CreateRenderingContext(canvas.Handle,[opDoubleBuffered],32,0); //Primero Creamos un contexto... TexBloques := TTextura.Create; TexBloques.CargarDe(Image2.Picture.Bitmap); TexRocas := TTextura.Create; TexRocas.CargarDe(Image1.Picture.Bitmap); TexTierra := TTextura.Create; TexTierra.CargarDe(Image3.Picture.Bitmap); TexTierra.UsaTextura;end;procedure TForm1.FormDestroy(Sender: TObject);begin DestroyRenderingContext(RC); //Se libera el Contexto... TexBloques.Free; // Y liberamos TexRocas.Free; // nuestras TexTierra.Free; // Texturas...end;procedure TForm1.FormPaint(Sender: TObject);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... glenable(GL_DEPTH_TEST); //usamoe el Z-Buffer para darle realismo a la escena...

Texturas con OpenGL(I)

56

Page 57: OpenGL Delphi

//Ajustamos las resoluciones... Case Form2.RadioGroupDefinicion.ItemIndex of 0: begin TexTierra.Definicion := dAlta; TexBloques.Definicion := dAlta; TexRocas.Definicion := dAlta; end; 1: Begin TexTierra.Definicion := dMedia; TexBloques.Definicion := dMedia; TexRocas.Definicion := dMedia; end; 2: Begin TexTierra.Definicion := dBaja; TexBloques.Definicion := dBaja; TexRocas.Definicion := dBaja; end; end; //Ajustamos los ambientes... Case Form2.RadioGroupAmbiente.ItemIndex of 0: begin TexTierra.ModeEnv := GL_DECAL; TexBloques.ModeEnv := GL_DECAL; TexRocas.ModeEnv := GL_DECAL; end; 1: Begin TexTierra.ModeEnv := GL_MODULATE; TexBloques.ModeEnv := GL_MODULATE; TexRocas.ModeEnv := GL_MODULATE; end; 2: Begin TexTierra.ModeEnv := GL_BLEND; TexBloques.ModeEnv := GL_BLEND; TexRocas.ModeEnv := GL_BLEND; end; 3: Begin TexTierra.ModeEnv := GL_REPLACE; TexBloques.ModeEnv := GL_REPLACE; TexRocas.ModeEnv := GL_REPLACE; end; end; //Y empezamos a dibujar... glMatrixMode(GL_MODELVIEW); glLoadIdentity; glTranslatef(0.0, -1.0, -11.0); GLPushMatrix; glRotatef(30.0, 1.0, 0.0, 0.0); glRotatef(Angulo, 1.0, 1.0, 0.0); TexRocas.UsaTextura; Cubo; //Dibujamos un cubo... GLPopMatrix; glMatrixMode(GL_TEXTURE); glLoadIdentity; //Rotamos la textura de la tierra... glrotatef(180,0,1,0); glMatrixMode(GL_MODELVIEW); TexTierra.UsaTextura; //Dibujamos la textura de la tierra... GLPushMatrix; glTranslatef(1.8,2,0); GlRotatef(90,1,0,0); GLRotatef(-30,0,1,1); glRotatef(-Angulo, 0.0, 0.0, 1.0); P := gluNewQuadric;

Texturas con OpenGL(I)

57

Page 58: OpenGL Delphi

gluQuadricTexture(p,1); gluSphere(p,1,20,20); gluDeleteQuadric(p); GLPopMatrix; TexBloques.UsaTextura; GLPushMatrix; glTranslatef(-1.9,2,0); GlRotatef(90,1,0,0); GLRotatef(-30,0,1,1); glRotatef(-Angulo, 1.0, 0.5, 1.0); glColor3f(1,0,1); P := gluNewQuadric; gluQuadricTexture(p,1); gluCylinder(p,1,0.5,1,20,20); gluDeleteQuadric(p); 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 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.

modelar las tres figuras que mostraremos en nuestra escena. La Figura 7 nos muestra las imágenes que usamos para texturizar nuestras figuras, como se dan cuenta todas son imágenes de diferentes dimensiones en cuanto a altura y anchura.Dentro de la clase del formulario hemos agregado un método extra para dibujar el cubo texturizado (TForm1.Cubo), como ven dibujamos cada una de las caras de nuestro cubo usando polígonos. Aquí lo interesante es que noten como antes de definir cada v értice le determinamos cual será la coordenada del mapa de textura que le corresponderá a dicho vértice; para esto utilizamos la función gltexcoord2i(), pasando como

parámetros los dos valores que le tocarán a este vértice dentro del mapa de textura. Como recordarán, estos son valores que fluctúan de 0 a 1, en este caso usamos solo los valores enteros porque declaramos los extremos de la imagen en cada cara, teniendo la misma imagen en cada lado, pero bien podríamos usar valores decimales, para indicar que una sola imagen envolverá a todo nuestro cubo.En el evento OnCreate() del formulario instanciamos nuestras variables a partir de la clase TTextura que hemos definido y cargamos cada textura a partir de cada una de las imágenes que mantenemos en componentes TImage, cargando con el método CargarDe() a partir de sus

Texturas con OpenGL(I)

58

Page 59: OpenGL Delphi

Figura 7.- Las texturas que usamos en el ejemplo.

Figura 8.- Nuestra ventana principal.

Figura 9.- La ventana auxiliar de herramientas.

propiedades Picture.BitMap.En el evento OnPaint() lo primero relevante que observamos es cómo ajustamos las propiedades de ModeEnv y Definición a partir de los Grupos de Radio que tenemos en el segundo formulario. Después tenemos un ejemplo de cómo aplicamos la textura de las rocas al cubo. Un ejemplo de una transformación lineal a la textura de la tierra es lo que nos encontramos enseguida,

como ven, lo que hay que hacer en ese caso es cargar la matriz de texturas, y aplicar la transformaci ón directamente como si a cualquier geometría se tratara. Esta rotación a esta textura la hacemos debido a que el método que usamos para dibujar la esfera nos

proporciona un mapeado de textura invertido para la figura, así que si no queremos ver la tierra “al reves“ tenemos que voltear nosotros la textura.Ahora, por último, vemos que tanto la esfera como el cono lo dibujamos a partir de métodos de la librería GLU, con gluSphere() y gluCylinder(), como en este caso nosotros no controlamos el mapeado de la textura, porque no definimos cada uno de sus vértices, esta misma librería nos facilita un método para calcular automáticamente las coordenadas de la textura en el cuerpo, esta es la función gluQuadricTexture() a la cual se le pasa como parámetro el puntero que señala la ubicación del objeto Quadric, y un valor lógico, que es un 1 si es que queremos que el calculo de coordenadas sea automático, o un 0 en caso contrario. Con todo esto ya tenemos nuestra aplicación terminada, ¡así de fácil! (Figuras 8 y 9).

Por ahora hasta aquí llegamos, pero en el próximo número seguiremos estudiando las texturas.

Hasta Pronto

Texturas con OpenGL(I)

59

Page 60: OpenGL Delphi

Texturas con OpenGL(II)

Mipmapping, Generación automática de coordenadas de texturas, Criterios de Repetición y Principios de la Programación Basada en GLUT.

¡Saludos!, Ahora continuaremos con nuestra serie dedicada a las texturas en OpenGL, esta vez estudiaremos como podemos texturizar cuerpos para los cuales no tenemos definido nuestro mapa de textura y algunas ventajas que podemos tener de esto mismo.También conoceremos como hacerlas en forma de mosaico, y para terminar algo sobre como basar nuestras aplicaciones gráficas en GLUT, del que tanto hemos hablado, y que hasta ahora solo conocíamos en teoría.

La generación automática de coordenadas de texturasEn el mes pasado habíamos visto que para poder aplicar una textura a un objeto gráfico, era necesario que este objeto estuviera mapeado, es decir que tuviera señaladas las coordenadas de texturas que corresponderían a los texeles a aplicar a cada uno de los vértices de nuestra figura.También vimos que este proceso de mapeado lo podía hacer nuestra propia aplicación generando nosotros mismos las coordenadas a aplicar a nuestros modelos (esto cuando nosotros los dibujamos con primitivas básicas), ó bien, invocando a algún método que lo hiciera para objetos predefinidos, como es el caso de gluQuadricTexture(), la cual genera el mapa de textura para objetos creados con gluNewQuadric(), pero ¿Qué hacemos si tenemos un cuerpo en nuestra escena que nos interesa texturizar, pero no tenemos modo de generarle su mapa de textura?...O bien, ¿Qué hacemos si quisiéramos que este cuerpo representara una reflexión de la textura del ambiente?Una manera muy simple de tener animación en cuanto a las texturas es variando las coordenadas asociadas a nuestra figura, por ejemplo, con:

glBegin(GL_QUADS) ; glTexCoord2f(0.0,0.0) ; glVertex2f(-1.0,-1.0) ; glTexCoord2f(0.0,1.0) ; glVertex2f(-1.0, 1.0) ; glTexCoord2f(1.0,1.0) ; glVertex2f( 1.0, 1.0) ; glTexCoord2f(1.0,0.0) ; glVertex2f( 1.0,-1.0) ; glEnd() ;

Obtenemos un cuadrado texturizado, ya que como habíamos visto declaramos una coordenada de textura por

cada vértice del cuadrado usando glTextCoord2f(); pero con esto obtenemos una textura estática que siempre se aplica del mismo modo a esta figura. Esto puede resultarnos útil, por ejemplo, para definir una pintura en una galería, ó los interiores de una nave espacial, por citar algunos casos.Ahora veamos como podemos hacer que tengamos un poco de animación variando las coordenadas de texturas a aplicar, por ejemplo, como podríamos programar de una manera muy simple una corriente ó caída de agua variando muy poco este código con:

glBegin(GL_QUADS) ; glTexCoord2f(0.0,down) ; glVertex2f(-1.0,-1.0) ; glTexCoord2f(0.0,up) ; glVertex2f(-1.0, 1.0) ; glTexCoord2f(1.0,up) ; glVertex2f( 1.0, 1.0) ; glTexCoord2f(1.0,down) ; glVertex2f( 1.0,-1.0) ; glEnd() ;down := down + 0.1 ;up := up + 0.1 ;

Como ven lo único que hicimos fue introducir dos variables que nos sirvieran como controladores del mapa de textura (up y down) y que se modificarán con cada iteración de las escenas, y así se logra la magia. Esta es una manera muy simple y hasta cierto punto lógica de animar una superficie texturizada.Pero no siempre tenemos la oportunidad de definir nosotros mismos de manera tan directa las coordenadas de texturas a utilizar, ya que algunas veces usaremos objetos predefinidos por alguna librería gráfica ó importados desde algún programa de diseño, en el cual no se haya definido el mapa de textura para ese cuerpo; y en esos casos lo que nos conviene es utilizar la generación automática de coordenadas de texturas que proporciona OpenGL.

Generación Lineal y EsféricaTenemos dos formas o criterios para generar coordenadas de texturas de manera dinámica en OpenGL, de forma Lineal (Linear), y mapeando de manera esférica (Sphere Map). Ambas técnicas consisten en generar las coordenadas de texturas como una función a partir de

60

Page 61: OpenGL Delphi

Texturas con OpenGL(II)

Figura 1 .- Ejemplos de generación de coordenadas de texturas.

Figura 2 .-Planos en generación de coordenadas lineales.

Figura 3 .- Una aplicación de la generación de coordenadas lineales al observador la tenemos al mapear terrenos.

otros parámetros, como la distancia a un plano, las normales y vectores de reflexión. La Figura 1 nos muestra algunos ejemplos de aplicación de estos criterios.

Respecto a la generación lineal, tenemos dos tipos de criterios; una es una generación Lineal al Objeto, y otra es una generación Lineal al Observador.La generación lineal al objeto, consiste en calcular el mapa de textura con coordenadas fijas para cada vértice del objeto, lo cual hace que el mapa generado resulte independiente de las transformaciones lineales que sufra el objeto, tales como: Traslaciones, Rotaciones o Escalamientos, este funciona de una manera muy similar a como trabaja el gluQuadricTexture() para objetos de GLU, que estudiamos el mes pasado, ya que las coordenadas se calculan de manera fija para cada uno de los vértices de la figura.La generación lineal al observador, por otra parte, consiste en generar coordenadas de texturas que s í sufren mutaciones con las transformaciones lineales, ya que en este caso lo que se pretende es hacer que las coordenadas de texturas que se muestran al observador no var íen,

aunque el objeto cambie de posición, ó de tamaño. Por este motivo, estas coordenadas son generadas después de que se han aplicado las transformaciones de modelado (Model-View). Aunque por ahora no nos quede esto del todo claro, en el ejemplo nos enteraremos mejor.La generación lineal, en general, tiene ciertas aplicaciones, puede servirnos para computar la distancia entre un objeto y un plano, como se muestra en la Figura 2, ó bien entre un objeto y el observador, lo cual puede sernos útil para generar también mapas de atenuación, de elevación, ó de sombreados.La Figura 3 nos muestra como una generación lineal al observador puede servirnos para texturizar un terreno, ya que a cada vértice de este se le asigna un texel correspondiente a la altitud de éste respecto al resto, y así es como lo vería el observador. Como vemos esto tiene sus ventajas en ciertos casos.

Y vamos con las texturas esféricas...Ahora hablemos de cómo se generan teóricamente las texturas esféricas. A esta técnica también se le conoce como Sphere Mapping, y generalmente suele utilizarse para definir Objetos Reflexivos, es decir, objetos que reflejan el ambiente. La Figura 4 nos muestra algunos ejemplos de objetos texturizados usando esta técnica.Podríamos definir una textura esférica como una imagen de una esfera reflexiva infinitamente pequeña, ó como una tabla de mapeo de la dirección de los colores. La Figura 5 nos muestra como se vería una textura esférica.Veamos, ¿Qué parámetros necesitaríamos para generar una textura esférica?... pues bien, necesitamos partir de un

61

Page 62: OpenGL Delphi

Texturas con OpenGL(II)

Figura 4 .- Objetos texturizados con Texturas Esféricas.

Figura 5 .- Como se ve una textura esférica.

Figura 6 .- Como se construye una textura esférica a partir de las caras de un cubo.

tmodelo poligonal, y ver la textura original como si fuera parte de las caras de un cubo. Para enterarnos mejor de esto veamos la Figura 6, donde mostramos como se

construye un cubo a partir de la textura original, y como se obtiene al final la textura esférica.Lo de objetos reflexivos lo mencionamos, porque esta misma técnica puede servirnos para simular que un objeto refleja el ambiente en el que se encuentra. Para eso se usan los vértices, las coordenadas del observador, y las normales para computar la dirección incidente de los efectos del ambiente, usando así esa dirección incidente para calcular las coordenadas de textura correspondientes. La Figura 7 muestra un ejemplo de cómo intervienen las normales y la dirección de incidencia en el cálculo de la textura.

Los criterios de repetición y dos coordenadas para las texturas Como ya vimos, cuando definimos el mapa de texturas, definimos coordenadas que van de 0 a 1 en ambos ejes. Como resultaría complicado hablar de los ejes X e Y para las texturas (ya que se confundirían con los ejes del propio objeto), para este caso se utiliza una nomenclatura diferente para llamar a los ejes: llamamos S al que correspondería al eje X de la textura, y T al eje Y.Pero, ¿Qué pasa cuando definimos una coordenada de textura que resulta superior a 1 ó inferior a 0?... ¡pues resulta que se aplica un criterio de repetición basado en la fracción decimal o entera de esa coordenada!Estos criterios pueden ser de dos tipos: Trasformar la coordenada al rango de [0..1] para así repetir la textura ordenadamente(GL_REPEAT), o bien, simplemente repetir la coordenada que resulte más cercana en el rango de [0..1] (GL_CLAMP). Estos dos criterios son los valores con los cuales se modifican los parámetros GL_WRAP_S y GL_WRAP_T de las texturas 2D, esto es porque se pueden combinar para cada

uno de los ejes S y T que mencionábamos hace un momento.La Figura 8 nos muestra las posibles combinaciones que podemos efectuar con estos dos criterios para ambos ejes, y los efectos que se tienen en la presentación final.

Un vistazo al método constructor de las texturas y al mipmappingCuando definimos las texturas en OpenGL utilizamos el método: glTextImage2D(), el cual explicamos un poco en el mes pasado. Bien, esta vez veremos que son esos parámetros tan raros que se le pasan a este método para construir la textura.

62

Page 63: OpenGL Delphi

Texturas con OpenGL(II)

Figura 4 .- Objetos texturizados con Texturas Esféricas.

Figura 5 .- Como se ve una textura esférica.

Figura 6 .- Como se construye una textura esférica a partir de las caras de un cubo.

Figura 7 .- Incidencia del ambiente sobre texturas esféricas.

tmodelo poligonal, y ver la textura original como si fuera parte de las caras de un cubo. Para enterarnos mejor de esto veamos la Figura 6, donde mostramos como se

construye un cubo a partir de la textura original, y como se obtiene al final la textura esférica.Lo de objetos reflexivos lo mencionamos, porque esta misma técnica puede servirnos para simular que un objeto refleja el ambiente en el que se encuentra. Para eso se usan los vértices, las coordenadas del observador, y las normales para computar la dirección incidente de los efectos del ambiente, usando así esa dirección incidente para calcular las coordenadas de textura correspondientes. La Figura 7 muestra un ejemplo de cómo intervienen las normales y la dirección de incidencia en el cálculo de la textura.

Los criterios de repetición y dos coordenadas para las texturas Como ya vimos, cuando definimos el mapa de texturas, definimos coordenadas que van de 0 a 1 en ambos ejes. Como resultaría complicado hablar de los ejes X e Y para

las texturas (ya que se confundirían con los ejes del propio objeto), para este caso se utiliza una nomenclatura diferente para llamar a los ejes: llamamos S al que correspondería al eje X de la textura, y T al eje Y.Pero, ¿Qué pasa cuando definimos una coordenada de textura que resulta superior a 1 ó inferior a 0?... ¡pues resulta que se aplica un criterio de repetición basado en la fracción decimal o entera de esa coordenada!Estos criterios pueden ser de dos tipos: Trasformar la coordenada al rango de [0..1] para as í repetir la textura ordenadamente(GL_REPEAT), o bien, simplemente repetir la coordenada que resulte más cercana en el rango de [0..1] (GL_CLAMP).

63

Page 64: OpenGL Delphi

Texturas con OpenGL(II)

Figura 8 .- Los casos en los criterios de repetición de coordenadas.

Estos dos criterios son los valores con los cuales se modifican los parámetros GL_WRAP_S y GL_WRAP_T de las texturas 2D, esto es porque se pueden combinar para cada uno de los ejes S y T que mencionábamos hace un momento.La Figura 8 nos muestra las posibles combinaciones que podemos efectuar con estos dos criterios para ambos ejes, y los efectos que se tienen en la presentación final.

Un vistazo al método constructor de las texturas y al mipmappingCuando definimos las texturas en OpenGL utilizamos el método: glTextImage2D(), el cual explicamos un poco en el mes pasado. Bien, esta vez veremos que son esos parámetros tan raros que se le pasan a este método para construir la textura.El primer parámetro puede ser GL_TEXTURE_2D o GL_PROXY_TEXTURE_2D. En esta ocasión no discutiremos GL_PROXY_TEXTURE_2D, pero en vez de eso hablaremos de GL_TEXTURE_2D; el segundo parámetro es usado cuando utilizamos texturas de múltiples resoluciones. Estas son utilizadas para el Mipmapping, algo de lo que hablaremos un poco después, cuando ponemos este parámetro en 0 indicamos que trabajaremos con sólo una resolución.El siguiente parámetro indica que valores de componentes

R, G, B y Alpha , o valores de iluminación o intensidad son seleccionados para usarlos en la descripción de los Texeles de la imagen. Este puede ser uno de treinta y ocho símbolos de constantes ya predefinidos. El único que hemos usado aquí (GL_RGB) especifica que los componentes Rojo, Verde y Azul son usados para describir un texel.Los siguientes dos parámetros especifican el alto y el

ancho de la textura. El siguiente parámetro es el bordo de la textura, y puede ser 0 (sin borde) ó 1. Los dos siguientes describen el formato y tipo de los datos de la imagen de textura, y finalmente el último dato es un apuntador al buffer donde se encuentra nuestra imagen.La técnica de mipmapping, es una técnica que consiste en crear varios niveles de nuestra imagen, como si fueran subtexturas, y luego al aplicar el filtro de minificaci ón una interpolación de dichas imágenes. Esto nos da resultados de mucha calidad, ya que podemos hacer que una imagen aunque no lo sea, parezca una imagen de alta definición al utilizar esta técnica; aunque por otro lado como deben suponerse esto tambi én es computacionalmente muy exigente.El constructor que usamos para construir los niveles de mipmapping: gluBuild2DMipmaps(); es muy similar al constructor de texturas glTexImage2D() que ya estudiamos, de hecho solo cambia un parámetro que es donde definimos cuántos niveles deseamos trabajar.Como comentario adicional, OpenGL también permite definir “objetos” para trabajar con texturas, y para ello utiliza el procedimiento glBindTexture(); el cual almacena la textura en alguna localidad de memoria previamente inicializada con el procedimiento glGenTextures(),

que genera el espacio donde se almacenará dicha textura. En el ejemplo mostramos como se puede hacer para utilizar esta característica, aunque en realidad en Delphi podríamos sustituirla por nuestra propia definición de clases.

Al Ejemplo...Bien, revisemos los Listados, y veamos que cosas nuevas nos muestran esta vez...El Listado 1 nos enseña como adecuar la anterior definición de la clase TTextura para trabajar con el método glBindTexture(), para lo cual definimos un arreglo llamado Texts, el cual nos servirá para almacenar hasta cien texturas. Como ven glBindTexture() nos sirve tanto para definir la textura a utilizar al crearla como al utilizarla, ya que se asigna un ID a cada objeto que se crea.

64

Page 65: OpenGL Delphi

Texturas con OpenGL(II)

//Definición de la enumeración para difinir resoluciones de texturas... TDefinicion = (dAlta, dMedia, dBaja); //Declaración de la Clase para manejar las Texturas... TTextura = Class(TObject) private TexID:GLuInt; IH,IW:integer; //Alto y ancho de la Imágen ... public img: Array [0..196607] of GLUByte; //Arreglo para almacenar la textura... ModeEnv: integer; // La función de ambiente a utilizar... Definicion : TDefinicion; constructor Create(PTexID:GLuInt); Procedure Cargarde(im:Tbitmap); //Cargar desde un Bitmap... Procedure UsaTextura; //Habilitar y usar una textura... Procedure InhibeTexturas; //Inhabilitar las texturas... end;Var Texts: Array[1..100] of GLuInt; //Soportamos hasta 100 texturas...implementationConstructor TTextura.Create(PTexID:GLuInt);begin inherited Create; TexID := PTexID; ModeEnv := GL_DECAL; //Esto puede ser GL_DECAL ó GL_MODULATE ó GL_REPLACE ó GL_BLEND; Definicion := dalta;end;Procedure TTextura.CargarDe(im:TBitmap);var i,j:integer;begin IW := im.Width; IH := im.Height; for i:=0 to ih do begin for j:=0 to iW do begin img[i*iW*3+j*3 ]:=byte(im.Canvas.Pixels[j,i] mod 256); img[i*iW*3+j*3+1]:=byte(im.Canvas.Pixels[j,i] shr 8) mod 256; img[i*iW*3+j*3+2]:=byte(im.Canvas.Pixels[j,i] shr 16) mod 256; end; end; //generamos el espacio para la textura... glGenTextures(1, @texts[texID]); glBindTexture(GL_TEXTURE_2D, texts[texID]);If (Definicion = dAlta) then gluBuild2DMipmaps(GL_TEXTURE_2D, 3, IW, IH,GL_RGB, GL_UNSIGNED_BYTE, @img) else glTexImage2D(GL_TEXTURE_2D, 0, 3, IW, IH, 0, GL_RGB, GL_UNSIGNED_BYTE, @img);end;Procedure TTextura.UsaTextura;begin Case Definicion of dAlta: begin glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR); end;

65

Page 66: OpenGL Delphi

Texturas con OpenGL(II)

dMedia: begin glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); end ; dBaja: begin glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); end; end; glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, ModeEnv); glBindTexture(GL_TEXTURE_2D, texts[TexID]); glenable(GL_TEXTURE_2D);end;Procedure TTextura.InhibeTexturas;begingldisable(GL_TEXTURE_2D);

end;

En este ejemplo utilizamos la librería GLUT, por lo tanto, la ventana en la cual mostraremos la animación la crearemos en tiempo de ejecución, y ese código lo escribiremos directamente en el código de nuestro proyecto DPR.

El Listado 2 nos muestra el código de nuestro programa principal, y en el vemos que tenemos declarados distintos procedimientos, el primero de ellos es Display(), que corresponde al procedimiento que dibujará la escena, en este podemos observar que ajustamos los

Var Angulo: Integer = 0; Texespacio,TexFuego: TTextura; angulo2:GLInt = 0; tex0:Real = 0.0; tex1:Real = 0.33; Tex2:Real= 0.66; tex3:REal = 1.0 ;procedure Display;begin glClearColor(0,0,0,1); // Ponemos un color negro Oscuro de Fondo... glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); // Algo así como borrar la Pizarra... glenable(GL_DEPTH_TEST); //usamoe el Z-Buffer para darle realismo a la escena... //Y empezamos a dibujar... glMatrixMode(GL_MODELVIEW); glLoadIdentity; glColor3f(1,1,0); glTranslatef(0.0, -1.0, -31.0); Case Herramientas.Form2.RadioGroupRepet.Itemindex of 0: begin GLTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT); GLTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT); end; 1: begin GLTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP); GLTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT); end;

66

Page 67: OpenGL Delphi

Texturas con OpenGL(II)

2:begin GLTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT); GLTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP); end; 3: begin GLTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP); GLTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP); end; end; Case Herramientas.Form2.RadioGroupDefinicion.itemindex of 0: begin TexFuego.Definicion := dAlta; TexEspacio.Definicion := dAlta; end; 1: begin TexFuego.Definicion := dMedia; TexESpacio.Definicion :=dMedia end; 2: begin TexFuego.Definicion := dBaja; TexEspacio.Definicion := dBaja;end; end; Case Herramientas.Form2.RadioGroupambiente.itemindex of 0: TexFuego.ModeEnv := GL_DECAL; 1: TexFuego.ModeEnv := GL_MODULATE; 2: TexFuego.ModeEnv := GL_BLEND; 3: TexFuego.ModeEnv := GL_REPLACE; end;TExESpacio.USaTextura; glPushMatrix() ; glRotatef(angulo2,0.0,0.0,1.0) ; glBegin(GL_QUADS) ; glTexCoord2f(0.0,tex1) ; glVertex3f(-20.0,-10.0,-20.0) ; glTexCoord2f(1.0,tex1) ; glVertex3f( 20.0,-10.0,-20.0) ; glTexCoord2f(1.0,tex2) ; glVertex3f( 20.0, 10.0,-20.0) ; glTexCoord2f(0.0,tex2) ; glVertex3f(-20.0, 10.0,-20.0) ; glEnd() ; glBegin(GL_QUADS) ; glTexCoord2f(0.0,tex2) ; glVertex3f(-20.0, 10.0,-20.0) ; glTexCoord2f(1.0,tex2) ; glVertex3f( 20.0, 10.0,-20.0) ; glTexCoord2f(1.0,tex3) ; glVertex3f( 20.0, 10.0, 20.0) ; glTexCoord2f(0.0,tex3) ; glVertex3f(-20.0, 10.0, 20.0) ; glEnd() ; glBegin(GL_QUADS) ; glTexCoord2f(0.0,tex1) ; glVertex3f(-20.0, -10.0,-20.0) ; glTexCoord2f(1.0,tex1) ; glVertex3f( 20.0, -10.0,-20.0) ; glTexCoord2f(1.0,tex0) ; glVertex3f( 20.0, -10.0, 20.0) ; glTexCoord2f(0.0,tex0) ; glVertex3f(-20.0, -10.0, 20.0) ; glEnd() ; glPopMatrix() ; TexFuego.UsaTextura; Case Herramientas.Form2.RadioGroupMapeo.ItemIndex of 0:begin glTexGeni(GL_S,GL_TEXTURE_GEN_MODE,GL_SPHERE_MAP) ; glTexGeni(GL_T,GL_TEXTURE_GEN_MODE,GL_SPHERE_MAP) ; end; 1:begin glTexGeni(GL_S,GL_TEXTURE_GEN_MODE,GL_OBJECT_LINEAR) ; glTexGeni(GL_T,GL_TEXTURE_GEN_MODE,GL_OBJECT_LINEAR) ; end; 2:begin glTexGeni(GL_S,GL_TEXTURE_GEN_MODE,GL_EYE_LINEAR) ; glTexGeni(GL_T,GL_TEXTURE_GEN_MODE,GL_EYE_LINEAR) ; end; end;

67

Page 68: OpenGL Delphi

Texturas con OpenGL(II)

glPushMatrix() ; glEnable(GL_TEXTURE_GEN_S) ; glEnable(GL_TEXTURE_GEN_T) ; glRotatef(angulo,2.0,1.0,3.0) ; glEnable(GL_CULL_FACE) ; glutSolidTorus(2.0,5.0,20,20) ; glDisable(GL_TEXTURE_GEN_S) ; glDisable(GL_TEXTURE_GEN_T) ; glDisable(GL_CULL_FACE) ; glPopMatrix() ; GlutSwapBuffers(); //Copiar el Back Buffer en el canvas del formulario...end;Procedure Animacion;begin inc(angulo2); tex0 := tex0+ 0.01 ; tex1 := tex1 +0.01 ; tex2 := tex2 +0.01 ; tex3 := tex2 +0.01 ; Inc(Angulo,6); //Rotamos el angulo de observación de la escena... Angulo := Angulo mod 360; glutPostRedisplay();end;procedure Init;begin TexFuego := TTextura.Create(1); TexFuego.CargarDe(Form2.Image1.Picture.Bitmap); TexEspacio := TTextura.Create(2); TexEspacio.CargarDe(Form2.Image2.Picture.Bitmap); glEnable(GL_DEPTH_TEST); // Definir como va a ser la proyección glViewport(0,0,300,300); // Especificar un puerto de visión.... glMatrixMode(GL_PROJECTION); // Activar matriz de proyección... glLoadIdentity; // Poner estado inicial en esta matriz... gluPerspective(35,300/300,1,100); // Especificar Perspectiva ...end;begin Application.CreateForm(TForm2,Form2); // Inicialización glutInitDisplayMode(GLUT_DOUBLE or GLUT_RGB or GLUT_DEPTH); glutInitWindowSize(300, 300); // Tamaño de ventana glutInitWindowPosition(143, 260); glutCreateWindow('Texturas'); // Crear ventana con 'caption' "Tetera" glutDisplayFunc(display); // Registrar la función de dibujar glutIdleFunc(Animacion); //Registramos la función para mantener animación... Init; glutMainLoop; // Ciclo de tratamiento de mensajes TexFuego.Free; TexEspacio.Free;end.

68

Page 69: OpenGL Delphi

Texturas con OpenGL(II)

Figura 9 .- La aplicación corriendo con un modo de ambiente de GL_BLEND

En este ejemplo utilizamos la librería GLUT, por lo tanto, la ventana en la cual mostraremos la animación la crearemos en tiempo de ejecución, y ese código lo escribiremos directamente en el código de nuestro proyecto DPR.El Listado 2 nos muestra el código de nuestro programa principal, y en el vemos que tenemos declarados distintos procedimientos, el primero de ellos es Display(), que corresponde al procedimiento que dibujará la escena, en este podemos observar que ajustamos los criterios de repetición para las coordenadas S y T de acuerdo a la selección de un RadioGroup en la ventana de herramientas, esto usando el método glTexParameterf(), del mismo modo como ya lo habíamos utilizado antes, así que no debe resultarnos muy nuevo que digamos. Y también, como podrán darse cuenta, estos solo funcionan cuando no usamos la generación esférica de coordenadas, ya que en ese caso no existe la repetición, porque todo el objeto se mapea de 0 a 1.Lo que si nos resulta nuevo es el uso de glTexGeni(), que nos sirve para declarar el modo en cómo se generarán las coordenadas de textura para el cuerpo posteriormente definido, el primer parámetro de este método puede ser GL_S ó GL_T que se refiere a la coordenada que deseamos afectar; el segundo puede ser GL_TEXTURE_GEN_MODE, o bien algún parámetro de ajuste para el plano de referencia cuando usamos generación lineal; y por último el tercero es el tipo de generación que deseamos producir, y esta puede ser: GL_SPHERE_MAP para texturas esféricas, GL_OBJECT_LINEAR para coordenadas lineales a los objetos, o GL_EYE_LINEAR para coordenadas lineales al observador.Una vez determinado el modo de generación, se debe habilitar el proceso de cálculo de coordenadas usando glEnable(), con GL_TEXTURE_GEN_S, y GL_TEXTURE_GEN_T como parámetros para generarlas en ambos ejes, o si se desea en solo uno de ellos. Una vez construido el objeto se puede volver a inhibir este cálculo con glDisable() con los mismos parámetros. Como estamos trabajando con GLUT debemos llamar al método GlutSwapBuffers() para vaciar el contenido del frame buffer en nuestra ventana.El procedimiento Animación() será esta vez el que coordine las animaciones en nuestra escena; como vemos, este modifica algunos parámetros que usamos para definir las coordenadas de textura de la figura de fondo, lo que nos produce un efecto bastante interesante en el detrás de la escena. Una vez que tenemos modificados todos los parámetros llamamos a glutPostRedisplay() para re-dibujar todo de nuevo.El procedimiento Init() nos servirá para inicializar la perspectiva a utilizar, y para definir los parámetros iniciales de la escena, aquí mismo aprovechamos para crear nuestras texturas.Por último, en el cuerpo del programa tenemos las

llamadas a los procedimientos que crearán nuestra ventana de despliegue, y a las funciones de dibujo y animación: glutInitDisplayMode() sirve para inicializar el contexto y los modos de despliegue a utilizar, glutInitWindowSize() sirve para determinar las dimensiones de alto y ancho de nuestra ventana; glutInitWindowPosition() para la posición de esta (Obvio, ¿no?); glutCreateWindow() para crear propiamente la ventana pasando como parámetro el titulo de esta; glutDisplayFunc() para definir que procedimiento se encargará de dibujar la escena; glutIdleFunc() para definir la función de animación, y por último glutMainLoop(); ¡para empezar con el show! La Figura 9 muestra nuestra aplicación en ejecución.

Tengamos en cuenta que ahora estamos aprovechando las características de cada uno de nuestros equipos, ya que no usamos un TTimer para controlar la animación y así hacemos que esta vaya a la velocidad que el mismo equipo soporte (que puede ser a más de un cuadro por milisegundo como con el Timer). Saludos y hasta pronto.

69

Page 70: OpenGL Delphi

Luces y Texturas con OpenGL

Como podemos combinar estos dos conceptos para obtener imágenes más realistas, y como trabajar con eventos en GLUT

¡Qué tal! Este mes lo dedicaremos a estudiar la combinación de dos conceptos muy interesantes en el desarrollo de gráficos: las luces, y las texturas. Ciertamente aún tenemos mucho que decir respecto a todas las posibilidades que nos ofrece el amplísimo tema de la texturización, pero ya las iremos mencionando a su tiempo, por ahora repasaremos algo de lo que ya hemos venido hablando, y evaluaremos de lo que somos capaces de hacer a estas alturas.Aprovecharemos igual, para seguir profundizando en el estudio de GLUT, que como ya hemos visto puede solucionarnos muchos problemas a la hora de programar gráficos si sabemos usar la librería pertinentemente.

Antes de empezar: Carga dinámica de bitmaps como texturas.En los primeros artículos de esta serie hablábamos de las librerías que componen a OpenGL, y hab íamos mencionado a GLU y a GLUT, ¿recuerdan?; pues bien, existe una más que no es tan conocida llamada GLAUX, en esta librería al igual que las otras lo que encontramos son algunos procedimientos para crear figuras prediseñadas, como esferas, cubos, etc. Pero una peculiaridad con la que cuenta, es que tiene un método que permite cargar bitmaps directamente desde archivos BMP. OpenGL provee de un tipo para trabajar con bitmaps llamado GLBitmap, y GLAUX nos proporciona métodos para crear objetos GLBitmap directamente desde archivos, para ser utilizados en texturas. Ahora como todo, el uso de esta librería tiene sus ventajas y también sus desventajas; una de las ventajas sería que usando las funciones que nos provee podemos desentendernos de la adecuación en memoria de la carga de imágenes para las texturas llamando simplemente a un método que hace todo el trabajo por nosotros; pero una desventaja (que sí resulta significativa) es que para usarla necesitamos incluir la librería GLAUX.DLL en nuestro proyecto, con el consabido incremento en tamaño y espacio del mismo, donde además irán incluidas otras funciones, aparte de las utilizadas, que tal vez nunca lleguemos a requerir.Por lo mismo, después de haber analizado el problema en

cuestión, y después de haber sido influenciado por haber visto repetidas veces los manuales de “Apague la tele, no sea holgazán, y hágalo usted mismo”; en este número veremos como podemos agregar un método que haga esto a la clase que hemos venido manejando para el trabajo con texturas. Tendremos oportunidad de ver que, en realidad, este trabajo no resulta tan complicado y que podemos hacerlo sin mayores problemas, y con esto mismo elevaremos la capacidad en cuanto a tamaño de imágenes soportadas para la clase, usando un manejo dinámico de la memoria, en vez de usar arreglos estáticos para hacer las veces de buffer de pixeles.

Las texturas iluminadas...Aunque esto pudiera sonarnos a “Imágenes en el Nirvana” la realidad es que es muy posible combinar luces, brillos y texturas haciendo una remembranza a través de lo que ya hemos estudiado hasta ahora.Como recordarán, para iluminar un objeto se necesita una fuente de luz, y definir las características de reflexión del material del cual estará compuesto el objeto, y después de eso, las normales hacen todo el trabajo. Pero ¿Qué hacemos si además queremos poner una imagen sobre el material, pero queremos conservar las propiedades del material original del objeto?; pues bien, el preservar las características originales del material dependerá de la función de ambiente que utilicemos para la textura.Si usásemos GL_DECAL o GL_REPLACE definitivamente se perderían los valores asignados al material, y obtendríamos de resultado en la escena un objeto que parece no ser afectado por las luces, ya que la textura, simplemente, reemplaza los píxeles del material con los píxeles de la textura sin tomar ninguna otra clase de consideraciones, así que en nuestro caso eso no nos serviría.En cambio, si usamos GL_MODULATE o GL_BLEND ¡los valores se conservan y se respetan!, veamos por qué: En lo que es el esquema de secuencia de operaciones del motor gráfico de OpenGL, las operaciones de texturización son las últimas que se aplican a los cuerpos antes de ser presentados, lo cual significa que antes de aplicar la textura ya se hicieron los cálculos necesarios de iluminación, y con esto ya tenemos el cuerpo con su

70

Page 71: OpenGL Delphi

Luces y Texturas con OpenGL

material pertinentemente iluminado. Por lo mismo, cuando aplicamos las texturas usando alguna de estas funciones de ambiente, lo que hacemos es interpolar los valores de los píxeles ya calculados, con los valores de los texeles correspondientes, obteniendo así la ilusión de que los efectos de iluminación también han sido aplicados a la textura. Si combinamos la utilización de estas funciones de ambiente, podemos obtener diversos efectos en cuanto a la manera en como se ven afectados los colores originales de la textura, combinándose con los colores de los materiales.

Más sobre GLUT...El mes pasado empezamos a estudiar la librería GLUT y cómo se utilizan y para qué sirven algunas de las

funciones que esta nos proporciona, ahora mencionaremos algunas otras funciones y empezaremos a ver como podemos responder a eventos del usuario, como el pulso de una tecla o de algún botón del ratón. En esta ocasión abordaremos las funciones de interacción con el teclado.En el Listado 1 tenemos el código fuente de nuestro programa principal y en el podemos apreciar que se utilizan algunas funciones de GLUT que pueden resultar un tanto nuevas para nosotros; como por ejemplo es el caso de: glutFullscreen(); esta función convierte nuestro programa en una aplicación de pantalla completa. Lo que hace es adecuar el área de despliegue a la resolución activa al momento de ejecutar el programa, por lo que no nos permite tener pleno control del modo de despliegue a usar, y si la resolución es muy alta, veremos las animaciones de manera más lenta.

Listado 1program OGL10;uses Windows, Forms, Dialogs, SysUtils, OpenGL, GLUT, Texturas in 'Texturas.pas';const//Valores para la luz... LightAmbient: Array[0..3]of GLFloat= (0.0, 0.0, 0.0, 0.0); LightDiffuse: Array[0..3]of GLFloat= (0.1, 0.1, 0.3, 1.0); LightPosition:Array[0..3]of GLFloat= (0.0, 5.0, 1.0, 1.0); LightEspecular:Array[0..3] of GLFloat= (1.0,1.0,1.0,1.0);//Valores para el Material... MaterialDiffuse: Array[0..3]of GLFloat= (1.0, 0.5, 1.0, 1.0); MaterialEmission: Array[0..3]of GLFloat= (0.0, 0.2, 0.0, 0.0); MaterialEspecular:Array[0..3] of GLFloat= (1.0,1.0,1.0,1.0);var rot : Integer = 0; T:TTextura; Ruta: String = '';procedure glInit();begin glClearColor(0.0, 0.0, 0.0, 0.0); glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE);//Creamos una luz... glLightfv(GL_LIGHT1, GL_AMBIENT, @LightAmbient); glLightfv(GL_LIGHT1, GL_DIFFUSE, @LightDiffuse); glLightfv(GL_LIGHT1, GL_POSITION,@LightPosition); glLightfv(GL_LIGHT1, GL_SPECULAR,@LightEspecular); glEnable(GL_LIGHT1); glEnable(GL_LIGHTING);//Y definimos un Material... glMaterialfv(GL_FRONT,GL_AMBIENT_AND_DIFFUSE,@MaterialDiffuse); glMaterialfv(GL_FRONT,GL_EMISSION,@MaterialEmission); glMaterialfv(GL_FRONT, GL_SPECULAR,@MaterialEspecular); glMaterialf(GL_FRONT,GL_SHININESS,20.0);

71

Page 72: OpenGL Delphi

Luces y Texturas con OpenGL

T := TTextura.Create(Application); T.CargadeArchivo(Ruta+'madera.bmp');end;procedure glDraw(); cdecl;begin // Limpiemos el buffer de color y de prueba de Profundidad... glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); T.Definicion := dbaja; T.ModeEnv := GL_MODULATE; T.UsaTextura; glTexGeni(GL_S,GL_TEXTURE_GEN_MODE,GL_OBJECT_LINEAR) ; glTexGeni(GL_T,GL_TEXTURE_GEN_MODE,GL_OBJECT_LINEAR) ; glEnable(GL_TEXTURE_GEN_S) ; glEnable(GL_TEXTURE_GEN_T) ; glPushMatrix; glTranslatef(0.0, 0.0, -5.0); glRotatef(rot, 1.0, 1.0, 1.0); glutSolidTorus(0.5,1,5,5); glutSolidSphere(0.5,10,10); glPopMatrix; Inc(rot,5); //Usamos este método para mostrar la frame ya construida //debe usarse siempre que se utilice un doble buffer... glutSwapBuffers;end;//para cuando cambia el tamaño de la ventanaprocedure glResizeWnd(Width, Height : Integer); cdecl;var fAspect : GLfloat;begin // Checamos que no vayamos a dividir entre cero... if (Height = 0) then Height := 1; glViewport(0, 0, Width, Height); fAspect := Width/Height; glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(45.0, fAspect, 1.0, 400.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity();end;//para checar por teclas presionadas...las coordenadas (x,y)//de los parámetros indican la posición del ratón al momento//de la pulsación...procedure glKeyPress(Key : Byte; x, y : Integer); cdecl;

72

Page 73: OpenGL Delphi

Luces y Texturas con OpenGL

begin case Key of VK_ESCAPE:begin T.Free; Application.Terminate; end; end;end;//Nuestra función de animación...procedure glIdle(); cdecl;begin glutPostRedisplay();end;//Para saber si nuestra ventana es visible...procedure glVis(Visible : Integer); cdecl;begin if (Visible = GLUT_VISIBLE) then glutIdleFunc(glIdle) else // Si no es visible no tiene caso mostrar nada, así que reasignamos la función "idle" glutIdleFunc(nil);end;//Para checar por teclas especiales...procedure glSpecialKey(Key : Integer; x, y : Integer); cdecl;begin case Key of // Por ejemplo GLUT_KEY_F1: begin showMessage('Esto sería la ayuda del programa'); end; {También podríamos preguntar por cualquiera de estas teclas... // GLUT_KEY_F1 // GLUT_KEY_F2 // GLUT_KEY_F3 // GLUT_KEY_F4 // GLUT_KEY_F5 // GLUT_KEY_F6 // GLUT_KEY_F7 // GLUT_KEY_F8 // GLUT_KEY_F9 // GLUT_KEY_F10 // GLUT_KEY_F11 // GLUT_KEY_F12 // GLUT_KEY_LEFT // GLUT_KEY_UP // GLUT_KEY_RIGHT // GLUT_KEY_DOWN // GLUT_KEY_PAGE_UP // GLUT_KEY_PAGE_DOWN // GLUT_KEY_HOME // GLUT_KEY_END // GLUT_KEY_INSERT} end;end;begin //Obtenemos la ruta donde se encuentra nuestro programa... Ruta := ExtractFilePath(Application.ExeName); If Ruta[length(Ruta)] <>'\' then Ruta := Ruta + '\'; // Le decimos a GLUT que use las DLL's de OpenGL de Microsoft ...

73

Page 74: OpenGL Delphi

Luces y Texturas con OpenGL

glutInit(MS_LIB); // Definimos una ventana cuadrada de 300 pixeles... glutInitWindowSize(300, 300); //Que se desplegará en las coordenadas (100,100) de la pantalla glutInitWindowPosition(100,100); // Definimos el modo de despliegua glutInitDisplayMode(GLUT_RGB or // Color en formato RGB GLUT_DOUBLE or // con Doble Buffer GLUT_DEPTH); // Y un buffer para la prueba de profundidad... // Y creamos la ventana... glutCreateWindow('VentanaGL'); // inicializamos el ambiente de OpenGL... glInit(); // Preguntamos si queremos mostralo a pantalla completa if (MessageBox(0, '¿A pantalla completa?', 'VentanaGL', MB_YESNO or MB_ICONQUESTION) = IDYES) then begin glutFullscreen(); end; // Definimos las funciones de ajuste de tamaño, glutReshapeFunc(glResizeWnd); // de teclas presionadas, glutKeyboardFunc(glKeyPress); // para teclas especiales, glutSpecialFunc(glSpecialKey); // para la función de dibujo glutDisplayFunc(glDraw); // y una más para saber si nuestra ventana está visible o no... glutVisibilityFunc(glVis); //Y empezamos con el Show... glutMainLoop;end.

En futuros artículos estudiaremos más a fondo la manera de hacer aplicaciones de pantalla completa con más cuidado y procurando acelerar los gráficos, por lo pronto esta queda como una premisa y una manera exageradamente sencilla de hacerlo.Siguiendo con las funciones nuevas nos encontramos con los procedimientos glutKeyboardFunc() y glutSpecialFunc() los cuales se utilizan para definir otros procedimientos que respondan a los eventos del teclado. El primero de estos (glutKeyboardFunc) funciona y es aplicable para todos aquellas teclas presionadas que devuelvan algún byte como valor, como por ejemplo todas aquellas que entran dentro de las constantes VK_* por todos conocidas, incluso en el ejemplo vemos como preguntando si la tecla ha sido la de Escape (VK_SCAPE) terminamos la aplicación. El segundo procedimiento (glutSpecialFunc), que asociamos a nuestro

propio método glSpecialKey(), sirve para aquellos casos en los que la tecla pulsada lo que devuelve es un código de rastreo, pero no un valor específico, para estos casos GLUT cuenta con su propia definición de constantes, y así podemos preguntar si la tecla presionada ha sido alguna tecla de función, o de manejo de cursor, entre otras. Observen como ambos métodos devuelven tres parámetros, ya que además, como información adicional, nos informa de la coordenada del ratón al momento de presionar dicha tecla, esto puede resultar de mucha utilidad en la práctica, ya lo veremos.Y por último, entre la nuevas funciones que mostramos aquí está glutVisibilityFunc(), la cual nos devuelve un valor booleano, que nos informa si la ventana creada con GLUT es visible o no lo es. Uno de los casos es los que no es visible una ventana, es por ejemplo cuando esta est á

74

Page 75: OpenGL Delphi

Luces y Texturas con OpenGL

minimizada; entonces no tiene sentido seguir molestando al procesador con cálculos de los que el usuario ni siquiera se va a enterar, por lo tanto cuando eso suceda lo que nos conviene es reasignar (o más bien inhibir) la función encargada de las animaciones, ¿no les parece?. Esa es una de las características que hacen fuerte el sistema de asignación de procedimientos de GLUT, la facilidad con la que se puede cambiar el comportamiento de la aplicación en general.

¿Y qué cambiamos en la definición de nuestra clase?El Listado 2 nos muestra las adecuaciones que debemos hacer a nuestra clase TTextura para poder cargar de manera dinámica las texturas directamente desde archivos de Mapas de Bits.La manera más simple de hacerlo, hubiera sido utilizar un objeto de la clase TBitmap y a partir de este objeto cargar

la imagen y solo transformarla y agregar la característica de almacenamiento dinámico, pero por lo menos en esta ocasión lo haremos de la manera “difícil”, a fin de enterarnos también como está constituido un archivo de Mapa de Bits físicamente.Como ustedes saben “Hay mas de una manera de pelar un gato”...¿Alguien sabe quién dijo eso?... seguramente alguien que tenía preferencia gastronómica por los felinos, ¡que raras costumbres!, ¿no les parece?, ¡pero en fin!, creo que estoy divagando... el caso es que siempre hay más de una forma de hacer las cosas, y es por eso que aqu í estaremos presentando diversas variaciones que pueden ser aplicables a la definición de nuestra clase, ya sea para adecuarla a nuevas necesidades, o bien para simplemente mostrar una manera diferente de hacer lo mismo, para que cada quien decida como es que le conviene más hacer su propio trabajo.A través de los artículos de esta serie hemos hecho diferentes versiones de nuestra clase TTextura, y en este mes

Listado 2unit Texturas;interfaceuses OpenGL, windows, Graphics, SysUtils, Dialogs,Classes;type //Definición de la enumeración para difinir resoluciones de texturas... TDefinicion = (dAlta, dMedia, dBaja); //Declaración de la Clase para manejar las Texturas... TTextura = Class(TComponent) private ID:GLuInt; public IH,IW:Cardinal; //Alto y ancho de la Imágen ... pdata:Pointer; BitmapLength: Cardinal; ModeEnv: integer; // La función de ambiente a utilizar... Definicion : TDefinicion; constructor Create(AOwner:TComponent); Destructor Free; Function CargadeArchivo(Archivo:String):Boolean; Procedure UsaTextura; //Habilitar y usar una textura... Procedure InhibeTexturas; //Inhabilitar las texturas... procedure LoadBitmap(Filename: String; out Width: Cardinal; out Height: Cardinal; out pData: Pointer); end;

75

Page 76: OpenGL Delphi

Luces y Texturas con OpenGL

//Correcciones a las definiciones Genericas en OpenGL.pas de Delphi...procedure glGenTextures(n: GLsizei; var textures: GLuint); stdcall; external opengl32;procedure glBindTexture(target: GLenum; texture: GLuint); stdcall; external opengl32;function gluBuild2DMipmaps(Target: GLenum; Components, Width, Height: GLint;

Format, atype: GLenum; Data: Pointer): GLint; stdcall; external glu32;implementationConstructor TTextura.Create(AOwner:TComponent);begin inherited Create(Owner); ModeEnv := GL_DECAL; //Esto puede ser GL_DECAL ó GL_MODULATE ó GL_REPLACE ó GL_BLEND; Definicion := dAlta; BitmapLength := 0;end;Function TTextura.CargadeArchivo(Archivo:String):Boolean;label fin;begin LoadBitmap(Archivo, IW, IH, pData); if (Assigned(pData)) then Result := True else begin Result := False; Goto fin; end; glGenTextures(1, ID); glBindTexture(GL_TEXTURE_2D, ID); If (Definicion = dAlta) then gluBuild2DMipmaps(GL_TEXTURE_2D, 3, IW, IH,GL_RGB, GL_UNSIGNED_BYTE, pData) else glTexImage2D(GL_TEXTURE_2D, 0, 3, IW, IH, 0, GL_RGB, GL_UNSIGNED_BYTE, pData);fin:end;procedure TTextura.LoadBitmap(Filename: String; out Width: Cardinal; out Height: Cardinal;

out pData: Pointer);var FileHeader: BITMAPFILEHEADER; InfoHeader: BITMAPINFOHEADER; Palette: array of RGBQUAD; BitmapFile: THandle; PaletteLength: Cardinal; ReadBytes: Cardinal; Front: ^Byte; Back: ^Byte; Temp: Byte; I : Integer;begin BitmapFile := CreateFile(PChar(Filename), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0); if (BitmapFile = INVALID_HANDLE_VALUE) then begin MessageBox(0, PChar('Error abriendo "' + Filename + '"' + #13#10 + 'código de Error '

+ IntToStr(GetLastError)), PChar('Texturas'), MB_OK); Exit; end; // Leemos el encabezado del archivo ReadFile(BitmapFile, FileHeader, SizeOf(FileHeader), ReadBytes, nil);

76

Page 77: OpenGL Delphi

Luces y Texturas con OpenGL

if (ReadBytes = 0) then begin MessageBox(0, PChar('Error leyendo el encabezado del archivo: ' +

IntToStr(GetLastError)), PChar('Texturas'), MB_OK); Exit; end; // Leemos la información sobre el encabezado.. ReadFile(BitmapFile, InfoHeader, SizeOf(InfoHeader), ReadBytes, nil); if (ReadBytes = 0) then begin MessageBox(0, PChar('Error leyendo información del encabezado: ' +

IntToStr(GetLastError)), PChar('Texturas'), MB_OK); Exit; end; Width := InfoHeader.biWidth; Height := InfoHeader.biHeight; // leemos la paleta de color PaletteLength := InfoHeader.biClrUsed; SetLength(Palette, PaletteLength); ReadFile(BitmapFile, Palette, PaletteLength, ReadBytes, nil); if (ReadBytes <> PaletteLength) then begin MessageBox(0, PChar('Error leyendo la paleta: ' +

IntToStr(GetLastError)), PChar('BMP Unit'), MB_OK); Exit; end; // y el tamaño que declara el encabezado... BitmapLength := InfoHeader.biSizeImage; //Si no obtenemos éxito con eso, lo calculamos nosotros... if BitmapLength = 0 then BitmapLength := Width * Height * InfoHeader.biBitCount Div 8; GetMem(pData, BitmapLength); ReadFile(BitmapFile, pData^, BitmapLength, ReadBytes, nil); if (ReadBytes <> BitmapLength) then begin MessageBox(0, PChar('Error leyendo el bitmap: ' +

IntToStr(GetLastError)), PChar('BMP Unit'), MB_OK); Exit; end; CloseHandle(BitmapFile); //Los Bitmaps se encuentran en sistema BGR por lo que hay que intercambiar los //bytes correspondientes a R y B para convertir los colores a RGB for I :=0 to Width * Height - 1 do begin Front := Pointer(Cardinal(pData) + I*3); Back := Pointer(Cardinal(pData) + I*3 + 2); Temp := Front^; Front^ := Back^; Back^ := Temp; end;end;Procedure TTextura.UsaTextura;begin Case Definicion of dAlta: begin glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR); end;

77

Page 78: OpenGL Delphi

Luces y Texturas con OpenGL

dMedia: begin glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); end ; dBaja: begin glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); end; end; glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, ModeEnv); glBindTexture(GL_TEXTURE_2D, ID); glenable(GL_TEXTURE_2D);end;Procedure TTextura.InhibeTexturas;begingldisable(GL_TEXTURE_2D);end;Destructor TTextura.Free;beginIF Assigned(PData) then FreeMem(PData,BitmapLength);end;end.

mostraremos una combinación de algunas versiones anteriores con nuevas características como la carga dinámica, y el acceso a archivos de mapas de bits.Observando el Listado 2 podemos notar que la primera diferencia significativa resulta ser que hemos cambiado la clase padre de TTextura, y ahora usamos TComponent en vez de TObject , pero esto tiene su justificación, y ahora la explicamos:Como recordarán, ahora estamos trabajando la aplicación a través de GLUT, y por lo tanto estamos un tanto restringidos a los eventos que son soportados por esta librería, y por lo mismo, ya que esta no maneja algún evento para la finalización de la aplicación, puesto que esto es manejado directamente por la instrucci ón glutMainLoop(), no tenemos modo de enterarnos de cuando la aplicación termina. Como pueden observar en el Listado 2, para esta definición de la clase ya implementamos un método Destructor (Free), en el cual liberamos cierto sector de la memoria reservada para el buffer de texeles, y que es necesario llamar antes de que la aplicación termine, para que no quede esa región como “laguna perdida”. Para evitar esto lo que podemos hacer es derivar de la Clase TComponent y, al momento de crear nuestro objeto de textura, “colgarlo” de la misma aplicación (es decir, hacer

a la aplicación el objeto responsable de la nueva instancia) desde el propio constructor, para que sea la misma aplicación quien lo libere cuando ésta termine, con eso “santo remedio”.En cuanto a las propiedades, hemos eliminado el arreglo estático que usábamos como buffer y en vez de eso utilizamos un puntero (pdata) y un valor numérico, que indique el tamaño del buffer dinámico (bitmaplength). Con esto hacemos un tanto más eficiente el uso del espacio en memoria requerido para el objeto, ya que no desperdiciamos bytes con texturas pequeñas.La estructura de un archivo BMP es en realidad bastante simple, ya que basta con leer estructuras predefinidas para obtener la información de la imagen. En la clase TTextura definimos un método para acceder exclusivamente a un archivo BMP (LoadBitmap), en este podemos ver como se abre el archivo solo para lectura, y se empiezan a leer estructuras, como fileheader de tipo BITMAPFILEHEADER y después infoheader de tipo BITMAPINFOHEADER, donde de este último obtenemos el ancho y alto en píxeles de la imagen contenida en el archivo.Justo después de haber leído la paleta de colores usada en el archivo, empezamos a leer propiamente los píxeles de la imagen, y leemos tantos valores como hayamos

78

Page 79: OpenGL Delphi

Luces y Texturas con OpenGL

Figura 1.- La ventana creada por GLUT

calculado que existen en BitmapLength, y aprovechamos que el procedimiento ReadFile() carga directamente esos valores en el buffer que señala pdata. Por el mismo hecho de que este procedimiento (LoadBitmap) es llamado por otro más general (CargadeArchivo) que es el que convierte propiamente el buffer en una textura, este bien podría ser declarado como un método privado de la clase; pero en este caso lo hemos puesto como público pensando que tal vez pudiera ser deseable cargar más de un buffer por entidad, pero eso lo abordaremos en otra ocasión.La Figura 1 nos muestra una pantalla de lo que vemos con

nuestra aplicación funcionando a “ventana simple”, y en ella podemos apreciar una figura modelada usando la función de ambiente GL_MODULATE. Y la Figura 2 muestra la textura utilizada en la animación, vean como los colores originales de la textura se ven modificados por los efectos de la luz en la escena. Jugando con los parámetros que aquí

hemos aprendido podrán hacer cosas aún más interesantes, todo es cuestión de tener imaginación.Hasta el próximo mes.

Figura 2.- La textura usada en el programa

79

Page 80: OpenGL Delphi

Más sobre Texturas y Listas de Despliegue con OpenGLCómo podemos utilizar esta poderosa herramienta de OpenGL, y algunos de sus usos más comunes.

¡Saludos!, este mes estudiaremos un concepto muy interesante sobre los gráficos 3D con OpenGL: Las Listas de Despliegue, algo que es muy propio de OpenGL, y que brinda muchas ventajas si se sabe utilizar pertinentemente.También hablaremos acerca de la librería GLAUX que tanto mencionamos el mes pasado, y que en vista del interés despertado entre la comunidad estudiaremos esta vez con mayor profundidad, haciendo una nueva adecuación a nuestra clase de manejo de texturas: TTextura, para poder interactuar con dicha librería.

¿Una Arquitectura Cliente-Servidor en OpenGL?...En una red contamos con computadores servidores (servers) que llevan a cabo acciones demandadas por computadores clientes (clients). Si yo quiero imprimir un documento y la impresora se encuentra f ísicamente ubicada en otro edificio, tendré que dialogar usando mi computador (cliente), con otro computador (servidor) para que me permita mandarle mi documento y lo imprima por mí. El hecho de contar con diversos servidores y muchos clientes configura una red (network) donde todos los esfuerzos se comparten, se distribuyen y así se consigue un comportamiento óptimo, veloz y ¡¡barato !! Una estación de trabajo que cuente con una pantalla, un teclado, un ratón ... puede actuar perfectamente como un servidor de gráficos. Esta máquina nos provee de servicios de salida gráfica en pantalla, y de entrada gracias al teclado y al ratón. Estos servicios podrán ser requeridos por cualquier cliente que pertenezca a la red. Nuestras aplicaciones OpenGL son clientes que usan al servidor de gráficos para ejecutarse y visualizarse. Pensemos que deberíamos ser siempre capaces de ejecutar la misma aplicación en diferentes servidores de la red. Esta es la filosofía interna de trabajo de nuestra querida librería. Para la realización de grandes producciones cinematográficas plagadas de gráficos (simulación física) como Titanic, Spawn o Perdidos en el Espacio, se necesitaron decenas de computadores conectados en paralelo, compartiendo memoria y procesadores, y calculando como locos 24 horas al d ía. Estaciones

Silicon, Alpha o Sun, sistemas operativos Irix, Motif, Linux... y todo a la vez. Y es que la unión hace la fuerza ¿¿no??

Listas de Despliegue ó "Display lists" en OpenGL.Las listas de despliegue de OpenGL ilustran muy claramente como podemos utilizar la filosofía cliente-servidor para nuestros gráficos. Supongamos que tenemos un computador dedicado única y exclusivamente a dibujar primitivas en el tubo de rayos catódicos (CRT) de nuestro monitor. Supongamos que esta máquina cuenta con un muy limitado juego de instrucciones de programación que sabe ejecutar. Tan sólo sabe dibujar lo que le manden. Otro computador compila y ejecuta el programa, genera unos resultados y le manda las correspondientes instrucciones para dibujarlos al Display Processor (computador dedicado en exclusiva a renderizar por pantalla ). Estas instrucciones se almacenan en una memoria de render (display memory) en forma de fichero/lista de primitivas (display file o display list). Así cada vez que le mandemos la lista de despliegue al procesador de despliegue (display processor), éste ya se encargará de dibujar cuando lo crea conveniente y nosotros quedamos libres para dedicarnos a otros quehaceres. El Procesador de despliegue (display processor) mandará dibujar la lista de despliegue a una frecuencia lo suficientemente alta como para evitar parpadeo en pantalla, y lo hará solito. Así nosotros le decimos lo que tiene que dibujar una sola vez y él ya lo hace repetidamente. ¿No les parece que nos ahorramos así mucho trabajo? Veamos la Figura 1. Tal como decía, nosotros asumimos el papel cliente ejecutando el programa, generamos una lista de despliegue con lo que hay que dibujar, se lo enviamos a la DPU (Display Processor Unit - Unidad de Proceso de Renderizado) ¡¡y que dibuje!! Hoy por hoy lo que se llamaba DPU se ha sustituido por un computador servidor de gráficos mientras que lo que en la figura mencionamos como host es nuestro computador cliente. Los problemas que nos encontramos con esta arquitectura se refieren a la velocidad a la que

80

Page 81: OpenGL Delphi

Más sobre Texturas y Listas de Despliegue con OpenGL

Figura 1. Casos de arquitecturas gráficas.

podemos trabajar, ya sabemos que las redes no "corren" a veces a la velocidad que deberían. Por otra parte y gracias a utilizar hardware específico gráfico, conseguimos equilibrar la balanza, al aumentar la velocidad a la que trabajamos. Pues bien, usaremos entonces las listas de despliegue en OpenGL para todo aquello que tengamos que dibujar siempre, de forma continua, y no queramos ir recalculando cada vez. Lo calcularemos al principio, lo mandaremos en forma de lista y llamaremos a una función de tanto en tanto para recordarle al servidor que debe dibujar. Defino una lista de despliegue con OpenGL:

glNewList(CUADRADO, GL_COMPILE); glBegin(GL_POLYGON); glColor3f(0.0, 0.0, 1.0);

glVertex3f(-10.0, -10.0, 0.0); glVertex3f(10.0, -10.0, 0.0); glVertex3f(10.0, 10.0, 0.0); glVertex3f(-10.0, 10.0, 0.0);

glEnd( ); glEndList( );

Es un polígono que consiste en un cuadrado de lado 20 y de color azul que se encuentra situado sobre el plano Z=0. Iniciamos la lista con glNewList() pasándole el nombre que tendrá esta lista, en nuestro caso usamos CUADRADO, y también el parámetro GL_COMPILE. Éste se encargará de que se envíe la lista al servidor para que la guarde pero que no la dibuje hasta que se lo ordenemos.

Finalizamos la lista con glEndList(), de manera que OpenGL ya sabe que el código que escribamos a continuación no forma parte de la lista de despliegue.Ahora cada vez que deseemos que se dibuje de nuevo nuestro cuadrado, sólo tendremos que ejecutar esta línea: glCallList(CUADRADO); que avisa al servidor y le obliga a dibujar lo que contenga la lista llamada CUADRADO que ya se le envió anteriormente. Así nosotros ya no tendremos que dibujar por nosotros mismos, el servidor lo hará cuando se lo mandemos. Resultado: un considerable aumento de la velocidad de ejecución de nuestro programa en tiempo real. Y si trabajamos localmente y no contamos con red ni con servidor de gráficos, también notaremos un gran aumento de velocidad pues la lista queda ya almacenada en el pipeline gráfico, justo antes de su salida por pantalla.

No vuelve a pasar por todo el pipeline nunca más, que es lo que ocurriría si mandáramos a dibujar el cuadrado nosotros, cada vez. Uno de los grandes ejemplos de uso de listas de despliegue en OpenGL es la generación de un alfabeto, de una fuente (font). Para escribir texto en OpenGL, se suelen crear las letras 3D con polígonos, y después se dibujan en pantalla. Lo que puede hacerse es crear cada letra (trabajo duro sin duda), almacenarla en una lista de despliegue y después tan sólo llamarla cuando queramos dibujarla.

Las texturas con GLAUX El mes pasado hacíamos mención de que una forma práctica de importar texturas en OpenGL a partir de archivos de mapas de Bits (BMP) era usar la librería GLAUX. Como en esa ocasión comentábamos una de las desventajas que la utilización de esta librería conlleva es la dependencia de una nueva DLL en nuestros proyectos (GLAUX.DLL), con el consabido aumento en espacio que esto representa.Sin embargo, como no nos gusta aquí dejar nada a medias, también vamos a ver un ejemplo de cómo se utilizaría esta librería para cargar texturas en nuestras aplicaciones. Para eso, veamos el Listado 1.

unit Texturas;interfaceuses OpenGL, windows, Graphics, SysUtils, Dialogs,Classes, GLAux;

81

Page 82: OpenGL Delphi

Más sobre Texturas y Listas de Despliegue con OpenGL

type //Definición de la enumeración para difinir resoluciones de texturas... TDefinicion = (dAlta, dMedia, dBaja); //Declaración de la Clase para manejar las Texturas... TTextura = Class(TObject) private ID:GLuInt; public IH,IW:Cardinal; //Alto y ancho de la Imágen ... ModeEnv: integer; // La función de ambiente a utilizar... Definicion : TDefinicion; constructor Create; Function CargadeArchivo(Archivo:String):Boolean; Procedure UsaTextura; //Habilitar y usar una textura... Procedure InhibeTexturas; //Inhabilitar las texturas... end;//Correcciones a las definiciones Genericas en OpenGL.pas de Delphi...procedure glGenTextures(n: GLsizei; var textures: GLuint); stdcall; external opengl32;procedure glBindTexture(target: GLenum; texture: GLuint); stdcall; external opengl32;function gluBuild2DMipmaps(Target: GLenum; Components, Width, Height: GLint; Format, atype: GLenum; Data: Pointer): GLint; stdcall; external glu32;implementationConstructor TTextura.Create;begin inherited Create; ModeEnv := GL_DECAL; //Esto puede ser GL_DECAL ó GL_MODULATE ó GL_REPLACE ó GL_BLEND; Definicion := dAlta;end;function LoadBMP(Filename : string) : PTAUX_RGBImageRec;begin if Filename = '' then begin result := nil; exit; end; if fileexists(FileName) then begin result := auxDIBImageLoadA(pchar(Filename)); exit; end; loadBMP := nil;end;Function TTextura.CargadeArchivo(Archivo:String):Boolean;var Status : boolean; TextureImage : PTAUX_RGBImageRec;begin Status := FALSE; TextureImage := LoadBMP(Archivo); if (TextureImage <> nil) then begin Status := TRUE; glGenTextures(1, ID); glBindTexture(GL_TEXTURE_2D, ID); If (Definicion = dAlta) then gluBuild2DMipmaps(GL_TEXTURE_2D, 3, TextureImage^.sizeX, TextureImage^.sizeY,GL_RGB, GL_UNSIGNED_BYTE, TextureImage^.data) else glTexImage2D(GL_TEXTURE_2D, 0, 3,TextureImage^.sizeX, TextureImage^.sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE,

82

Page 83: OpenGL Delphi

Más sobre Texturas y Listas de Despliegue con OpenGL

TextureImage^.data); end; CargadeArchivo := Status;end;Procedure TTextura.UsaTextura;begin Case Definicion of dAlta: begin glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR); end; dMedia: begin glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); end ; dBaja: begin glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); end; end; glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, ModeEnv); glBindTexture(GL_TEXTURE_2D, ID); glenable(GL_TEXTURE_2D);end;Procedure TTextura.InhibeTexturas;begingldisable(GL_TEXTURE_2D);end;end.

Listado 1

Como podemos ver en este listado, lo que hemos hecho en esta ocasión a nuestra clase TTextura, es solo modificar un poco el método CargadeArchivo() el cual hemos transformado en una función booleana, que nos indicará si es que la carga de la imagen en la textura se ha realizado de manera exitosa o no. El resto de la definición de la clase es prácticamente la que todos ya conocemos.También, como una función adicional hemos declarado LoadBMP(), la cual es una función que nos devuelve un apuntador a una estructura, de tipo PTAUX_RGBImageRec, que es la estructura que almacenará toda la información de la imagen que hallamos pasado como referencia en el único parámetro que definimos para esta función.Aunque esta función solo la utilizamos (entre otras cosas) para validar que el archivo exista, puesto que la función que realmente hace el trabajo pesado es auxDIBImageLoadA(), esta es la que se encarga de cargar la imagen en la estructura.

Ahora dentro de la definición del método CargadeArchivo(), hemos declarado una variable llamada TextureImage, también del tipo PTAUX_RGBImageRec y como se imaginarán lo que hacemos en el cuerpo de la función lo que hacemos es cargar la estructura de la imagen con la función que acabamos de estudiar: LoadBMP(). ¡Tan simple como eso!Una vez hecho esto, ya podemos acceder a las propiedades de la imagen como lo son su ancho, alto y los píxeles de los que está compuesta. Como nuestra variable en realidad es un puntero para acceder a los elementos de la estructura que contiene a la imagen tenemos que utilizar el símbolo ^. Así, para referenciar al ancho de la imagen debemos usar: TextureImage^.sizeX; Y si recordamos en el procedimiento glTexImage2D() debemos pasar como parámetros tanto el ancho (SizeX en la estructura), como el alto (SizeY), y los píxeles en sí de la imagen (Data).Tanto la librería GLAUX.DLL como su unidad de

83

Page 84: OpenGL Delphi

Más sobre Texturas y Listas de Despliegue con OpenGL

interfaz para Delphi están incluidas en el código fuente correspondiente a este artículo disponible en el mismo sitio de esta revista.

Las “Display Lists” y sus excepcionales desventajas... no todo es miel sobre hojuelas...

Ya hemos visto todas las virtudes de las que podemos colmar a las listas de despliegue, pero ahora en el Listado 2 veremos algunos ejemplos donde estas son aplicables, y otros donde no resultan serlo tanto.De entrada lo que vemos es que hemos definido en este listado tres formas de hacer “lo mismo”, una de la manera tradicional, otra usando procedimientos auxiliares, y una

unit Unit1;interfaceuses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls, OpenGL,Texturas;type TForm1 = class(TForm) Panel1: TPanel; Timer1: TTimer; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure Timer1Timer(Sender: TObject); procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); private { Private declarations } public { Public declarations } end;var Form1: TForm1; up:GLFloat = 1.0; down: GLFloat; Roca:GLInt; Angle: integer; T,T2,T3:TTextura;implementation{$R *.DFM}procedure setupPixelFormat(DC:HDC);const pfd:TPIXELFORMATDESCRIPTOR = ( nSize:sizeof(TPIXELFORMATDESCRIPTOR); // size nVersion:1; // version dwFlags:PFD_SUPPORT_OPENGL or PFD_DRAW_TO_WINDOW or PFD_DOUBLEBUFFER; // support double-buffering iPixelType:PFD_TYPE_RGBA; // color type cColorBits:16; // prefered color depth cRedBits:0; cRedShift:0; // color bits (ignored) cGreenBits:0; cGreenShift:0; cBlueBits:0; cBlueShift:0; cAlphaBits:0; cAlphaShift:0; // no alpha buffer cAccumBits: 0; cAccumRedBits: 0; // no accumulation buffer, cAccumGreenBits: 0; // accum bits (ignored) cAccumBlueBits: 0; cAccumAlphaBits: 0; cDepthBits:16; // depth buffer cStencilBits:0; // no stencil buffer cAuxBuffers:0; // no auxiliary buffers iLayerType:PFD_MAIN_PLANE; // main layer

84

Page 85: OpenGL Delphi

Más sobre Texturas y Listas de Despliegue con OpenGL

bReserved: 0; dwLayerMask: 0; dwVisibleMask: 0; dwDamageMask: 0; // no layer, visible, damage masks */ );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); 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); RC:=wglCreateContext(DC); wglMakeCurrent(DC, RC); glViewport(0, 0, Form1.Panel1.Width, Form1.Panel1.Height); glMatrixMode(GL_PROJECTION); glLoadIdentity; gluPerspective(35,Form1.Panel1.Width/Form1.Panel1.Height,1,100); T := TTextura.Create; T.CargadeArchivo('water.bmp'); T2 := TTextura.Create; T2.CargadeArchivo('cielo.bmp'); T3 := TTextura.Create; T3.CargadeArchivo('piedras.bmp'); roca := glGenLists(1); glNewList(roca, GL_COMPILE); glBegin(GL_QUADS); glTexCoord2f(3.0,3.0); glVertex3f( 1, 1, 0.0); glTexCoord2f(0.0,3.0); glVertex3f(-1, 1, 0.0); glTexCoord2f(0.0,0.0); glVertex3f(-1,-1, 0.0); glTexCoord2f(3.0,0.0); glVertex3f( 1,-1, 0.0); glEnd(); glEndList();end;procedure TForm1.FormDestroy(Sender: TObject);var DC:HDC; RC:HGLRC;begin DC := wglGetCurrentDC; RC := wglGetCurrentContext; wglMakeCurrent(0, 0); if (RC<>0) then wglDeleteContext(RC); if (DC<>0) then ReleaseDC(Panel1.Handle, DC); T.Free; T2.Free; T3.Free;end;Procedure Agua;begin glBegin(GL_QUADS); glTexCoord2f(0.0,down) ; glVertex2f(-1.0,-1.0) ; glTexCoord2f(0.0,up) ; glVertex2f(-1.0, 1.0) ;

85

Page 86: OpenGL Delphi

Más sobre Texturas y Listas de Despliegue con OpenGL

glTexCoord2f(1.0,up) ; glVertex2f( 1.0, 1.0) ; glTexCoord2f(1.0,down) ; glVertex2f( 1.0,-1.0) ; glEnd() ;end;procedure TForm1.Timer1Timer(Sender: TObject);begininc(angle); glClearColor(0,0,0,1); glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); glEnable(GL_DEPTH_TEST); glMatrixMode(GL_MODELVIEW); glLoadIdentity; glTranslatef(0,0,-5); glRotatef(20,1,5,0); //Dibujamos el Cielo... glPushMatrix; T2.Definicion := dMedia; T2.UsaTextura; glRotatef(270,1,0,0); glTranslatef(6,10,3); glBegin(GL_QUADS); glTexCoord2f(0,down); glVertex3f( 10, 10, 0.0); glTexCoord2f(0.0,up); glVertex3f(-10, 10, 0.0); glTexCoord2f(1,down); glVertex3f(-10,-10, 0.0); glTexCoord2f(1,up); glVertex3f( 10,-10, 0.0); glEnd(); glPopMatrix; T.Definicion := dAlta; T.UsaTextura; //Mostramos la Caida de Agua... Agua; glPushMatrix; glRotatef(270,1,0,0); glTranslatef(0,-1,-1); Agua; glPopMatrix; down := down +0.1; up := up + 0.1; //Y mostramos las Paredes.... T3.Definicion := dBaja; T3.UsaTextura; glPushMatrix; glPushMatrix; glTranslatef(-2.01,0,0); glCallList(roca); glRotatef(270,1,0,0); glTranslatef(0,-1,-1); glCallList(roca); glPopMatrix; glTranslatef(2.01,0,0); glCallList(roca); glRotatef(270,1,0,0); glTranslatef(0,-1,-1); glCallList(roca); glPopMatrix; SwapBuffers(wglGetCurrentDC);end;

86

Page 87: OpenGL Delphi

Más sobre Texturas y Listas de Despliegue con OpenGL

procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);begin If Key = VK_ESCAPE Then Application.Terminate;end;end.

más usando listas de despliegue. En todos los casos lo que hacemos es definir un cuadrado texturizado.En el evento OnCreate del formulario hemos puesto una definición de lo que sería una lista de despliegue, con un elemento llamado Roca, en el cual hemos empotrado las instrucciones para dibujar un cuadrado de lado 2 con su respectivo mapa de textura. Esta lista de despliegue la utilizamos para dibujar las paredes que aparecerán en nuestra escena. Como estas son figuras estáticas no hay problema y podemos dibujarlas usando las “display lists”Siguiendo en orden descendente encontramos el segmento de código con el que dibujamos el cielo en nuestra escena. Como para dibujarlo solo necesitamos un solo cuadrado, podemos escribirlo directamente sin mayores problemas. El hecho de no haber utilizado una llamada a la lista de despliegue que ya dibuja un cuadrado texturizado, obedece a que en nuestra escena el cielo aparecerá en movimiento, y esto lo conseguimos cambiando dinámicamente las coordenadas de textura del cuadrado; y como los elementos de la lista de despliegue en realidad son elementos estáticos, no podríamos representar la dinámica de la textura si utilizáramos listas de despliegue para esto. En realidad no es difícil de entender si pensamos que la definición de los elementos de la lista de despliegue se hace en el evento OnCreate (en nuestro caso), por lo que ya no es posible modificar las coordenadas de textura en otro evento (OnTimer).Otro poco más abajo en el listado encontramos como podemos utilizar métodos auxiliares para simular Listas de Despliegue (por lo menos para nosotros los programadores). Aquí tenemos que declaramos el procedimiento Agua() el cual dibuja un cuadrado de lado 2 usando coordenadas de textura dinámicas. Ahora, ¿Por qué para este caso usamos un método auxiliar?... pues la respuesta es simple, porque para dibujar la caída de agua necesitamos dibujar 2 cuadros uno en posición vertical y el otro horizontal; y si tenemos un procedimiento que dibuja un cuadro en alguna posición resulta sencillo usar glPushMatrix() y glPopMatrix() para modificar la geometría del espacio antes de dibujar el otro cuadro en la nueva

posición.Como ven esto de usar procedimientos algunas veces es más conveniente que usar listas de despliegue, todo depende de las necesidades que tengamos al momento de dibujar. Como en este caso que aunque necesitábamos dibujar un cuerpo varias veces en la escena, no podíamos usar listas de despliegue porque estas no soportaban el mapeo dinámico de texturas para simular el flujo de agua.Saludos y como dirían al norte de mi pueblo: Happy Programming

Figura 2.- Nuestra aplicación en ejecución

87