Tutorial 4 - Programación Gráfica SIS-2430

Transcripción

Tutorial 4 - Programación Gráfica SIS-2430
Programación con Delphi y OpenGL
Carlos García Trujillo
[email protected]
http://glscene.tripod.com
(Cedida la publicación a el Rinconcito de Delphi)
http://www.elrinconcito.com/delphi/
Prohibida la reproducción o modificación sin autorización
expresa del autor.
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.
Direct3D
Direct3D es el API 3D de Microsoft. Es un completo conjunto
de servicios de gráficos 3D tanto de transformaciones,
iluminación y renderizado de escenas, así como 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.
OpenGL
OpenGL es una librería gráfica escrita originalmente en C que
permite la manipulación de gráficos 3D a todos los niveles. Esta
librería se concibió para programar en máquinas nativas Silicon
Graphics bajo el nombre de GL (Graphics Library).
Posteriormente se consideró la posibilidad de extenderla a
cualquier tipo de plataforma y asegurar así su portabilidad y
extensibilidad de uso con lo que se llegó al término Open
Graphics Library, es decir, OpenGL.
La librería se ejecuta a la par con nuestro programa
independientemente de la capacidad gráfica de la máquina que
usamos. Esto significa que la ejecución se dará por software a no
ser que contemos con hardware gráfico específico en nuestra
máquina. Si contamos con tarjetas aceleradoras de v ídeo,
tecnología MMX, aceleradoras 3D, pipelines gráficos
implementados en placa, etc ... 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
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.
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.
Otras consideraciones adicionales
Figura 1. Un ejemplo de una Aplicación 3D hecha en Delphi
usando tecnología OpenGL.
Está claro que en un futuro cercano las tarjetas aceleradoras 3D
de hardware reemplazarán por completo las actuales,
Figura 1. Un ejemplo de una Aplicación 3D hecha en Delphi usando tecnología OpenGL.
3
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.
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.
¿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
Figura 1 Niveles de Abstracción en OpenGL
Las Piedras angulares: las matrices y los
vectores
Aquí es donde empieza el trabajo sucio de todo este asunto.
Toda la geometría que se despliega en las aplicaciones OpenGL
está basada en los conceptos de Matrices y Vectores y las
operaciones aritméticas aplicables a estas estructuras.
En OpenGL existen básicamente 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
4
Introducción a la Programación de Gráficos con OpenGL
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
de los vectores, y como se transforman las coordenadas hasta
llegar a las coordenadas reales de la ventana.
Vectores de diversos tipos
Los vectores son un conjunto de valores que nos permiten
definir dentro de nuestra escena, desde los vértices de las
figuras, hasta los colores, los materiales y las luces, entre otras
muchas 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 2. Matrices Genéricas de Transformación Lineal para Sistemas de
3 Dimensiones.
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
Figura 4. Secuencia de Transformación de Coordenadas.
Tipos en OpenGL
Figura 3. La Matriz Identidad
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
Debemos recordar que las librerías de OpenGL fueron escritas originalmente en C, así que
conservan muchas características de este lenguaje, entre ellas, la estructura de sus
procedimientos, y los tipos que se utilizan.
Aunque en los tipos de OpenGL se anteponen las siglas GL, estos guardan una marcada
equivalencia con los tipos genéricos de C, así GLFloat sería el equivalente al tipo float de C, y
GLint, al tipo int. Y los rangos del dominio alcanzado por cada uno de estos tipos depende de
su correspondiente equivalencia en C.
Aunque OpenGL también provee de ciertos tipos de datos estructurados para el manejo
tanto de matrices como de vectores, y generalmente estos tipos ya vienen en las librerías de
OpenGL para Delphi con su respectiva equivalencia en tipos de Object Pascal.
5
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;
interface
uses
Windows, Forms, OpenGL, Classes, ExtCtrls, Messages, Controls, StdCtrls,Graphics,
sysUtils, Dialogs;
type
TForm1 = class(TForm)
Timer1: TTimer;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormPaint(Sender: TObject);
procedure FormResize(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
{ Private declarations }
procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message
WM_ERASEBKGND;
public
{ Public declarations }
end;
var
Form1: TForm1;
RC : HGLRC;
Angulo : GLInt;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
RC:=CreateRenderingContext(canvas.Handle,[opDoubleBuffered],32,0); //Primero
Creamos un contexto...
end;
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
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
Introducción a la Programación de Gráficos con OpenGL
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),
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.
Figura 5. Nuestra Primera Aplicación de OpenGL en Ejecución.
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
8
Líneas con OpenGL en dos
dimensiones
Proyección Ortogonal 2D. Construcción de
gráficos con Líneas. Antializado.
Algoritmos recursivos de Fractales.
OpenGL no se utiliza solamente para generar impresionantes
gráficos 3D, sino como veremos a continuación también
puede utilizarse para hacer gráficas en 2 dimensiones. En esta
ocasión trabajaremos con los diferentes tipos de líneas que
nos ofrece OpenGL; y a fin de hacer este tema todavía mas
interesante veremos un extra bastante útil e impresionante:
Los Fractales, un concepto matemático muy utilizado en la
graficación por computadora.
Visualización 2D
Aunque aquí tratemos el tema de la visualización 2D en
OpenGL, es importante recordar que este es un API gráfico
de 3 dimensiones; así que si queremos utilizarlo para hacer
representaciones 2D tenemos que hacer un pequeño truco.
Como ustedes han de suponer lo que hemos de hacer es
graficar todo lo que deseemos en un plano, para obtener así la
apariencia de que estamos trabajando con 2 dimensiones
aunque realmente sigamos trabajando en 3.
Entonces la visualización 2D se basa en tomar un área
rectangular de nuestro mundo 3D y transferir su contenido a
nuestro despliegue. El rectángulo de visualización está en el
plano z=0. Por default si no se específica el volumen de
visualización se construye un rectángulo de 2x2, donde las
coordenadas de este rectángulo quedan como: ((-1.0,-1.0),(1.0,1.0),(1.0,-1.0),(1.0,1.0)), estas coordenadas, claro, en el
plano z=0.
Figura 1
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
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.
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.
Tipos de Lineas en OpenGL
Existen básicamente 3 tipos de líneas en OpenGL: Líneas
sencillas, Líneas en ciclo y Líneas con patrones. Como
habíamos visto en el número anterior la definición de figuras
en OpenGL se hace con las instrucciones GLBegin() y GLEnd(),
donde a GLBegin() se le pasa como parámetro el tipo de figura
que nos interesa dibujar con los vértices definidos entre estas
instrucciones (el ejemplo pasado lo hicimos solo con puntos
usando GL_POINTS), así que ahora estudiaremos como hacer
líneas con ellas.
Para hacer líneas sencillas usamos GL_LINES, con esta
instrucción como parámetro para GLBegin() indicamos a OpenGL
que dibuje una línea que una cada dos vértices que nosotros
definamos. Algo muy similar a cuando trabajábamos con el
Objeto Canvas y usábamos las instrucciones MoveTo(x,y) y
LineTo(x,y), ¿lo recuerdan?... ¡ah... que tiempos aquellos! ...
Las líneas en ciclo nos sirven para definir contornos. Para
hacer este tipo de líneas usamos GL_LINE_LOOP, cuando
utilizamos esta instrucción unimos todos los vértices que
definimos con una misma línea en el orden en que hayan sido
estos definidos, y al mismo tiempo se unen el último vértice
definido con el primero, cerrando así la figura. Esto es muy útil
para dibujar contornos de polígonos (ya sean regulares o
irregulares), por ejemplo.
¿Recuerdan cuando trabajábamos con el Canvas que podíamos
cambiar el estilo de la pluma para dibujar líneas punteadas o
con diversos patrones?. Pues en OpenGL también se pueden
declarar varios patrones para dibujar líneas. Para ello usamos
en nuestro ejemplo la instrucci ón GL_LINE_STRIP como
parámetro, éste permite escribir una secuencia de líneas unidas
vértice a vértice, pero a diferencia de GL_LINE_LOOP el último
vértice no se une con el primero. Ya veremos sobre la marcha
como declaramos los patrones a utilizar.
Calidad en el trazado de líneas
También, como en el número anterior vimos la instrucción
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
GLPointSize()
¡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
Líneas con OpenGL en dos dimensiones
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.
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
Las líneas punteadas
Siguiendo con el análisis sobre el
procedimiento Star(), que dibuja las estrellas,
vemos que aquí es donde usamos los
patrones con las líneas.
La función glLineStipple() especifica el patrón
usado para el punteado, por ejemplo, si
usáramos el patrón $AAAA (este es un
número en hexadecimal), en binario este
número es 1000100010001000, OpenGL
interpreta esto dibujando 3 bits apagados, 1
bit encendido, 3 bits apagados, 1 bit
encendido, 3 bits apagados, 1 bit encendido
y por último 3 bits apagados y 1 encendido.
El patrón se lee hacia atrás porque los bits
de menor orden se usan primero.
GlLineStipple() tiene dos parámetros, el patrón
de punteado que debe ser un n úmero
hexadecimal y un factor entero que sirve para escalar este
patrón; con un factor de 3 nuestra línea punteada mostraría 9
bits apagados, 3 bits encendidos, 9 bits apagados, 3 bits
encendidos, 9 bits apagados, 3 bits encendidos y por último 9
bits apagados y 3 bits encendidos. Jugando con factores y
patrones binarios, uno puede dibujar todo tipo de l íneas
punteadas complicadas. En nuestro ejemplo usamos el patrón
$5555, con un factor de 1, lo que nos da un patrón uniforme de
1 pixel apagado por 1 encendido. ¡Hagan cuentas!.
Figura 2
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
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ón
Por último, puesto que ya hemos utilizado los diferentes tipos
de líneas disponibles en OpenGL, vamos a agregar un poco de
animación a nuestra escena. Si mal no recordamos las estrellas
“titilan”, por lo que usamos una variable para simular este
efecto, así como una variable adicional para mostrar con 2
árboles pequeños, como es que se transforman las figuras
fractales para generar los diferentes tipos de árboles que
tenemos en nuestra escena, modificando el parámetro w que
habíamos mencionado. Estas animaciones están controladas
nuevamente por un componente Timer.
Por ahora eso es todo, ya tenemos nuestra escena construida
(Ver Figura 2); que se diviertan experimentando con las líneas
y los fractales. Recuerden que pueden descargar el código
fuente de este programa y las unidades extras que utilizamos
directamente desde el sitio web de la revista. Hasta Pronto...
unit Unit1;
interface
uses
Windows, Forms, OpenGL, Classes, ExtCtrls, Messages, Controls, StdCtrls,Graphics,
sysUtils, Dialogs;
type
TForm1 = class(TForm)
Timer1: TTimer;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormPaint(Sender: TObject);
procedure FormResize(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
{ Private declarations }
procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
public
{ Public declarations }
end;
var
Form1: TForm1;
RC
: HGLRC;
Angulo : GLInt;
Radio : GLFloat = 0;
implementation
{$R *.DFM}
procedure Arbol(x,y,t,teta,w:real);
var x1,y1,t1:real;
begin
if t > 0.01 then {condición de paro}
begin
glbegin(GL_LINES);
glVertex2f(x,y);
x1 := x -t * cos(teta);
y1 := y -t * sin(teta);
glVertex2f(x1,y1);
glend;
t1 := t /1.7;
{Se llama recursivamente al sistema}
Arbol(x1,y1,t1,teta-w,w);
Arbol(x1,y1,t1,teta,w);
Arbol(x1,y1,t1,teta+w,w);
end;
end;
12
Líneas con OpenGL en dos dimensiones
Procedure Star(X,Y,t,Teta:Real);
var X1,Y1:real;
begin
if Teta < 10 then {Nuestra condición de paro}
begin
glEnable (GL_LINE_STIPPLE); //Habilitamos el uso de los patrones...
glPushAttrib (GL_LINE_BIT); //Salvamos el estado de las lineas...
glLineStipple (1, $5555); //Declaramos el patrón a utilizar...
glbegin(GL_LINE_STRIP);
glVertex2f(x,y); //Todas las lineas tienen el mismo centro...
x1 := x -t * cos(teta);
y1 := y -t * sin(teta);
glVertex2f(x1,y1);
glend;
glPopAttrib (); //Recuperamos el estado anterior...
{Llamamos recusivamente al sistema}
Star(X,Y,t,Teta+0.25);
glDisable (GL_LINE_STIPPLE); //Inhibimos los patrones...
end;
end;
Procedure Luna;
var i:integer;
Coseno,Seno:GLFloat;
begin
GLBegin(GL_LINE_LOOP);
For i := 0 to 100 do
begin
Coseno := Cos(i*2*PI/100);
Seno := Sin(i*2*PI/100);
GLVertex2f(Coseno,Seno);
end;
GLEnd;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
RC:=CreateRenderingContext(canvas.Handle,[opDoubleBuffered],32,0); //Primero Creamos un
contexto...
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
DestroyRenderingContext(RC); //Se libera el Contexto...
end;
procedure TForm1.FormPaint(Sender: TObject);
begin
ActivateRenderingContext(canvas.Handle,RC); // Se asocia el contexto con el manejador del
Canvas...
glClearColor(0,0,0,0); // Ponemos un color Verde Oscuro de Fondo...
glClear(GL_COLOR_BUFFER_BIT); // Algo así como borrar la Pizarra...
glMatrixMode(GL_MODELVIEW);
glLoadIdentity;
gluOrtho2D (-1.0, 1.0, 0.0, 1.5); //Para trabajar en 2 dimensiones...
{Dibujamos algunos arbolitos felices...}
glPushMatrix();
//Salvamos la matriz del ambiente...
glLineWidth (1);
glcolor3f(0,0,1);
glrotatef(270,0,0,1); //Se incorporan los arboles...
Arbol(0,0,0.3,0,0.5);
Arbol(0,0.2,0.2,0,0.25);
Arbol(0,-0.15,0.15,0,radio);
Arbol(0,-0.65,0.25,0,2.1);
13
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
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?).
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
Empecemos con la
teoría...
Bueno, creo que deberemos
empezar explicando el escabroso
Figura 3. Multiplicación de un Vector por una matriz
procedimiento mediante el cual se
aplican operaciones sobre matrices
por una matriz, lo que obtenemos es un nuevo vector
y vectores.¿Recuerdan cuando hace algunos meses hablábamos “transformado”.
de que toda la geometría que se despliega en las aplicaciones
de OpenGL está basada en los conceptos de Matrices y
Bueno, ¿y eso qué?
Vectores y las operaciones aritméticas aplicables a estas
Ahora, a nosotros lo que nos interesa es multiplicar vectores
estructuras?, bien, pues ahora vamos a sumergirnos un poco en
de 3 valores por matrices de 3*3 para obtener las
lo que respecta a estas operaciones.
Primeramente vamos a entender una matriz como una tabla transformaciones en tres dimensiones. Recordemos un articulo
que contiene valores, y a un vector como un caso especial que pasado donde mostrábamos las diferentes matrices de
puede representar un renglón o una columna de dicha tabla. transformación lineal para sistemas de 3 dimensiones, así en la
Cuando multiplicamos un vector renglón por un vector figura 4 podemos ver como podemos obtener vectores
columna lo que obtenemos es un valor al cual le llamamos transformados a partir de la multiplicación del vector original
“escalar”. La Figura 1 muestra como obtenemos el valor 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
Figura 1. Multiplicación de un Vector por otro vector
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:
Figura 2. Multiplicación de una matriz por otra matriz
15
Transformaciones Lineales en OpenGL
Figura 4. Matrices de rotación y escalamiento por vectores
| X'| = | X |* ( | A |*| B | )
Donde X’ representa al vector transformado, X al vector
original, y A y B son
2
matrices
de
transformación
distintas.
Y si en el caso
anterior tomamos:
|
K | = | A |*| B |
entonces podemos
sustituir K en la
segunda expresión
como:
|
X'| = | X |*| K |
Figura 5. Traslación de un cuerpo
¿Lo ven?... Podemos
aplicar primero todas
Figura 6. Rotación de un cuerpo
transformación
tanto
de
Traslación,
R o t a c i ó n ,
Escalamiento y
Reflexión.
Ahora, el orden
en
como
se
apliquen
estas
operaciones
importa
y
mucho...Como
bien sabemos en
Figura 7. Escalamiento de un cuerpo
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
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
Rotación-Traslación
Traslación-Rotación
cada uno de los vértices que
nosotros definimos con
glvertex...();
¿No es tan
Figura 8. Diferencias en el orden de la aplicación de las transformaciones
complicado o si?
Las figuras 5 a la 7 muestran gráficamente los efectos que se transformaciones de Rotación y Traslación a un mismo
ocasionan sobre un cuerpo al aplicarle las matrices de cuerpo en diferente orden. Y como podemos observar son
16
Transformaciones Lineales 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|
Figura 9. Algunas primitivas de graficación en OpenGL
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étricas
Hasta ahora en los ejemplos que habíamos visto solo habíamos
usado líneas y puntos para crear nuestras figuras, pero en
OpenGL existen muchísimas primitivas de figuras que ya
iremos viendo a su tiempo. La figura 9 nos muestra algunas de
estas primitivas y cual es su comportamiento.
Hay algunos apuntes que hacer también al respecto pero eso lo
reservaremos para otra ocasión, por ahora basta con saber que
existen y que podemos hacer uso de ellas.
Ahora veamos un ejemplo para estudiar de manera práctica las
transformaciones lineales junto con algunas cosas nuevas.
Vamos al ejemplo!...
Demos un vistazo al Listado 1, y veamos que podemos
aprender de é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
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
Transformaciones Lineales en OpenGL
procedure cubo; // Para dibujar el cubo de nuestra escena ...
begin
glbegin(GL_QUADS);
glcolor3f(10,10,10);
glVertex3f(1.0, 1.0, 1.0);
glcolor3f(-10,10,10);
glVertex3f(-1.0, 1.0, 1.0);
glcolor3f(-10,-10,10);
glVertex3f(-1.0, -1.0, 1.0);
glcolor3f(10,-10,10);
glVertex3f(1.0, -1.0, 1.0);
glcolor3f(10,10,-10);
glVertex3f(1.0, 1.0, -1.0);
glcolor3f(10,-10,-10);
glVertex3f(1.0, -1.0, -1.0);
glcolor3f(-10,-10,-10);
glVertex3f(-1.0, -1.0, -1.0);
glcolor3f(-10,10,-10);
glVertex3f(-1.0, 1.0, -1.0);
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
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 perspectiva
end;
20
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
Interacción de gráficos con
dispositivos
tridimensionales sin demasiado esfuerzo, por ahora nos
limitaremos a usar los predefinidos de OpenGL.
Veamos el ejemplo, ¿cómo respondemos a
Cómo podemos obtener programas gráficos los eventos?
Bien, en el ejemplo del número anterior veíamos como
interactivos de manera simple usando
podíamos crear un contexto de OpenGL sobre un componente
componentes creados en tiempo de Tpanel, ¿recuerdan?, pero bueno, en realidad podemos crear
ejecución este tipo de contextos en cualquier componente que est é
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
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
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.
Figura 1.- Nuestra aplicación en ejecución
Consideraciones adicionales con otros
dispositivos
Hasta aquí ya hemos visto como podemos interactuar de una
manera muy simple con el ratón y el teclado, sin embargo
existen en la red diversos componentes que permiten también
recibir eventos desde un Joystick por ejemplo, en cuyo caso, no
existe mucha diferencia con lo visto hasta ahora.
Y en caso de que nos interesara guardar una escena como un
archivo de imagen, ó bien imprimirla tampoco hay mayor
problema, ya que basta con usar la famosa función CopyRect() del
objeto Canvas asociado con el control sobre el cual
desplegamos nuestra escena y con ese rectángulo podemos
hacer lo que se nos pegue en gana.
Bueno, hasta aquí llegamos por esta vez, que tengan suerte, y
que aprendan mucho de esto
Hasta Pronto.
Listado 1
unit Unit1;
interface
uses
OpenGL, Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, ExtCtrls, ComCtrls;
23
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
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
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
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
Tipos de proyecciones y
manejo de la cámara en
OpenGL
Como pasar de coordenadas 3D a
coordenadas de dos dimensiones, y como
interactuar con la cámara.
Bien, en esta ocasión vamos a tratar de dar continuidad al
artículo del mes pasado hablando de c ómo podemos
interactuar en la escena que mostramos a traves de los
dispositivos de entrada como el teclado y el ratón, y esta vez
daremos un ejemplo un poco más completo al respecto.
También hablaremos más específicamente sobre las técnicas
de proyección que nos brinda OpenGL, y como es que
trabajan en teoría cada una de ellas. Este es un concepto que
de algún modo resulta interesante conocer, ya que puede
llegar a ser sumamente útil a la hora de crear nuestras
propias aplicaciones gráficas, pues de esto puede depender
la calidad de nuestra presentación.
¿Cómo pasar de coordenadas 3D a
coordenadas 2D en nuestra ventana?
Esta es una muy buena pregunta, ¿Cómo hacer que
aparezcan en nuestra ventana, que es 2D, puntos que en
realidad deberán tener una relación de profundidad entre
sí?... Bien, pues la respuesta no es tan compleja si la
pensamos un poquito, lo que hay que hacer es proyectar toda
nuestra geometría en un plano, al que llamaremos Plano de
Proyección.
Como todos los planos, este representa una región 2D, y por
convención suele situarse en Z = 0, es decir en el plano XY.
Y primero debemos hacer la proyección para después hacer
las conversiones de tipos de coordenadas. Entendamos esto
de las conversiones de tipos:
Cuando trabajamos con coordenadas 3D, es común que las
definamos como números de punto flotante, y es
perfectamente válido hacer esto, si no ¡imagínense como
funcionarían programas de diseño como por ejemplo
Autocad usando solo aritmética entera!...Sin embargo,
nuestro monitor está mapeado en pixeles que tienen cada
uno dos coordenadas enteras, por esta razón es que debemos
hacer una conversión que elimine los decimales sin que
estos dejen de ser significativos en la proyección. Por eso es
que hay que convertir forzosamente al final a enteros.
Existen muchos tipos de proyecciones y podríamos escribir
libros completos al respecto, pero aquí nos limitaremos a ver
solo dos que resultan algo significativas para nuestros
propósitos, la proyección Ortográfica, y la proyección en
Perspectiva. Ambas están clasificadas dentro de las
proyecciones planares.
Y básicamente ambas consisten en definir una línea de
visión que va desde el observador hasta el objeto observado,
y trazar una serie de líneas que cortarán el plano de
proyección que hayamos definido generando así la figura 2D
que finalmente aparecerá en nuestra pantalla.
La proyección Ortográfica
Este tipo de proyección está clasificada como un tipo de
proyección paralela, y esto es porque consiste en trazar
líneas perpendiculares al plano de proyección que resultan
ser paralelas entre sí. Este es una tipo de proyección
relativamente sencilla, que suele ser bastante útil en ciertos
casos. La figura 1 muestra gráficamente como se construyen
las líneas paralelas en esta proyección, podemos ver como se
Figura 1. Proyección Ortográfica
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!!
28
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.
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...
Figura 2. Proyección en Perspectiva
En la figura tenemos una proyección perspectiva con un sólo
COP o punto de fuga. Todas las líneas ó proyectores
emanan de él y se dirigen hasta el objeto intersectando el
plano de proyección. Como podemos observar en este caso
los proyectores no son paralelos entre ellos.
En OpenGL definimos esta proyección cargando la matriz de
proyección, inicializandola, y usando gluPerspective( FOV en grados,
Relación de Aspecto, z_near, z_far); donde los parámetros que hay
que pasarle a este método son los siguientes:
El FOV se refiere al ángulo de abertura inicial, que
determinará nuestro campo de visión. La relación de aspecto
es el cociente entre la anchura y la altura del plano de
proyección deseado, es decir 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
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
29
Tipos de proyecciones y manejo de la cámara en OpenGL
el "at". Variando el "up" variamos la orientación, en la figura
3 podemos verlo gráficamente.
Figura 3. Las variables de la cámara
En Delphi!!, En Delphi!!...
Bien, veremos a continuación un ejemplo hecho en
Delphi en el que dibujaremos un pequeño escenario virtual,
en el cual podremos desplazarnos y observarlo
detalladamente con ayuda del ratón y del teclado. Veamos el
listado 1 y la figura 4 para ver que pretendemos hacer.
Figura 4. Nuestro mundo virtual
A fin de hacer esto, que resulta ya de por sí un tanto
complicado, más digerible, volvamos a la librería de OpenGL
de Litschke que nos facilitaba tanto la vida, para no perder
tiempo en cuestiones más vanales...
Como ven hemos definido variables globales para controlar la
cámara, tanto su posición, como su orientación. ¿Recuerdan
el pequeño procedimiento recursivo que hicimos en el
número 2 para generar arboles fractales en dos dimensiones?,
bien, pues aquí tenemos una generalización para hacer
arbolitos en 3D; el procedimiento S() se encarga de este
trabajo.
Como ven, hemos definido como modelo de despliegue a
GL_SMOOTH, lo cual hace que podamos tener colores
degradados en nuestra escena, bueno en realidad esto es
porque forzamos a que se calcule un color para cada uno de
los pixeles específicamente, y además hacemos una prueba de
profundidad para los cuerpos.
La base de nuestro árbol en realidad es un cilindro, y noten
como debemos transformar la geometría del árbol antes de
mostrarlo, esto para ajustarlo a nuestro ambiente virtual.
Ahora aquí es donde empieza lo verdaderamente interesante,
ahora sí podemos entender que eran los números que
poníamos en gluperspective(), aquí vemos como hemos definido
el ángulo de visualización a 35, y como pasamos el parámetro
de la relación de aspecto, y los valores near y far para este
método.
Observemos como con los eventos del teclado y el
ratón vamos modificando los valores asociados a la
cámara. Para el teclado es muy simple, ya que los
cambios se hacen directamente sobre las variables de
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.
Listado 1
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, OpenGL,
ExtCtrls;
type
TForm1 = class(TForm)
Timer1: TTimer;
30
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;
begin
if t > 0.01 then {condición de paro}
begin
{Se elije el color en que se deplegará la linea}
if t < 0.05 then
glcolor3f(0,1,0) else
if t < 0.1 then glcolor3f(1,1,1)
else
glcolor3f(1,0,0);
31
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);
var
p: PGLUquadricObj;
begin
ActivateRenderingContext(Canvas.Handle,RC); // Hacer el contexto dibujable
glClearColor(0.2,0.3,0,1); // Color de Fondo...
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); // Borra el Fondo y
Depth-Buffer
glMatrixMode(GL_MODELVIEW); // Activamos la matriz de modelado
glLoadIdentity;
// la inicializamos
gluLookAt(eyex,eyey,eyez,centerx,centery,centerz,upx,upy,upz); // Definimos
un punto de visión...
glEnable(GL_DEPTH_TEST);
glShadeModel ( GL_SMOOTH) ;
gltranslatef(0,0,-3);
glrotatef(25,0,1,0);
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
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);
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;
//Le ponemos una base feliz.
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
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
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 Normales
Para iluminar una superficie o plano, necesitamos
información sobre su vector normal asociado. Primero
veamos que es una normal: la normal de un plano es en
realidad un vector perpendicular a este, as í de fácil.
¡Volvemos otra vez a la tediosa Álgebra Lineal!... Bueno, no
todo es miel sobre hojuelas en este ambiente, ni modo... aquí
necesitamos entonces saber como calcular este vector y como
especificárselo a OpenGL, así que debemos repasar un poco
lo que ya habíamos estudiado sobre aritmética de vectores ¿lo
recuerdan?.
Tratemos de ver esto con un caso práctico: veamos la "figura
1"... Supongamos que tenemos el objeto que se presenta en la
figura y deseamos calcular el vector normal a la cara superior
de este objeto, la formada por los vértices A, B, C y D.
Todos estos vértices son coplanares (adoro estos términos _)
es decir, que pertenecen a un mismo plano, as í que si
queremos encontrar un vector normal a este plano solo
tenemos que encontrar un vector que sea perpendicular a
cualquiera de estos vértices... Pues bien, para esto solo
tenemos que calcular dos vectores pertenecientes a la cara y
hacer su producto vectorial (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.
Figura 1. Calculo de normales para una cara.
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
Iluminación con OpenGL (I)
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.
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
Tipos de iluminación
Ya sabemos que será necesaria la especificación de una
normal por vértice. Ahora vamos a ver que tipos de
iluminación soporta OpenGL, con lo cual empezaremos a
ver más claro el uso de estos vectores. Existen múltiples
tipos de iluminación, pero aquí nos dedicaremos a estudiar
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.
Figura 2. La forma de despliegue de nuestra escena
refiriendo (GL_LIGHT0, GL_LIGHT1, GL_LIGHT2, etc.) En cuanto a
*params, le pasamos un arreglo de valores RGBA reales que
36
Iluminación con OpenGL (I)
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!!!
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”.
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
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);
glLightfv(GL_LIGHT0, GL_DIFFUSE, @glfLightDiffuse);
glLightfv(GL_LIGHT0, GL_SPECULAR,@glfLightSpecular);
// Se definen las
//carácterísticas de la
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
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
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:
procedure TrackBar2Change(Sender:
procedure TrackBar3Change(Sender:
procedure TrackBar4Change(Sender:
private
{ Private declarations }
public
{ Public declarations }
end;
TObject);
TObject);
TObject);
TObject);
40
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 /10
end;
procedure TForm2.TrackBar3Change(Sender: TObject);
begin
glfLightDiffuse[1] := TrackBar3.Position /10
end;
procedure TForm2.TrackBar4Change(Sender: TObject);
begin
glfLightDiffuse[2] := TrackBar4.Position /10
end;
end.
41
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 luces
Aparte 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).
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
Figura 2
Figura 1
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
“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
Iluminación con OpenGL (2)
otros que por si mismos (debido al color, o al propio aplicarse al lado visible (Front), al no visible (Back) o a
material) generan cierta luminosidad, por ejemplo los colores ambos. Aquí debemos tomar en cuenta que puede no
fluorescentes. Y OpenGL nos
permite definir materiales con todas
Tabla 1
estas características, tomando
const GLfloat
GLenum face
GLenum pname
* params
propiamente de principios físicos
GL_FRONT
GL_DIFFUSE
( R, G, B, 1.0 )
todo lo que hemos estado
GL_BACK
GL_AMBIENT
( R, G, B, 1.0 )
mencionando.
GL_FRONT_AND_BACK
GL_AMBIENT_AND_DIFFUSE
( R, G, B, 1.0 )
A cada pedazo de la geometría que
GL_EMISSION
( R, G, B, 1.0 )
compone nuestra escena podemos
GL_SPECULAR
( R, G, B, 1.0 )
asignarle un material distinto, y así
GL_SHININESS
[ 0,
128 ]
también poder representar objetos
complejos compuestos de diversos
materiales, como por ejemplo un piano que tenga las teclas interesarnos el có mo se vea la parte no visible de una cara
blancas de marfil y los bemoles de ébano (Disculpen, soy un si esta nunca se va a presentar al usuario, por ejemplo en un
gran aficionado a la música); ambos materiales responderán cubo cerrado cuyas caras interiores nunca se ven. En cuanto a
de un modo distinto a una misma luz, y esa diferencia debe pname se especifica aquí cuá l es la caracterí stica que
ser notoria si deseamos hacer una escena lo suficientemente vamos a definir en concreto. Las posibles son las que hemos
realista.
comentado para un material. De hecho son bastante obvias si
Para un material se definen cinco caracter ísticas miramos las constantes que podemos usar. Por ú ltimo
fundamentales. Estos componentes son:
*params, donde damos los valores concretos de la
Ø
Reflexió n difusa (diffuse) o color de base que caracterí stica. Son tres valores, de hecho tres nú meros
reflejarí a el objeto si incidiera sobre é l una luz pura reales que especifican un color RGB. Ese color define
blanca, la mayorí a de las veces esta denota el color del exactamente có mo debe verse el objeto que se renderice
material que estamos definiendo.
despué s en cuanto a color ambiente, difusi ó n,
Ø
Reflexió n especular (specular), que se refiere a los componente especular, etc.
"puntitos brillantes" de los objetos iluminados.
Hay una excepció n en el caso de GL_SHININESS. Si usamos
Ø
Reflexió n ambiental (ambient) , define como un objeto esta constante como segundo pará metro, el tercero tendrá
ó polí gono determinado refleja la luz que no viene que ser un nú mero entre 0 y 128 que controlará la
directamente de una fuente luminosa, sino de la escena en concentració n del brillo. Por defecto este valor vale 0.
sí .
La misma funció n tiene tambié n las formas glMaterialf,
Ø
Coeficiente de brillo o "shininess". Define la cantidad de glMateriali y glMaterialiv. No suelen usarse, por eso las versiones
puntos luminosos y su concentració n. Digamos que llamadas escalares (enteras), ya que só lo son ú tiles para
variando este pará metro podemos conseguir un objeto definir GL_SHININESS.
má s o menos cercano al metal, por ejemplo.
Los Valores tí picos, son los usados por defecto, son de 0.8
Ø
Coeficiente de emisió n (emission) o color de la luz que para las tres componentes en GL_DIFFUSE, de 0.2 para
emite el mismo objeto. Lo que les mencionaba hace un GL_AMBIENT y de 0.0 en GL_EMISSION y GL_SPECULAR. Por
momento de la fosforescencia.
supuesto tendremos que retocar estos valores hasta conseguir
Las componentes ambiental y difusa son tí picamente el efecto deseado, ya que si los dejamos como está n no nos
iguales o muy semejantes. La componente especular suele ser gustará lo que veremos.
gris o blanca. El brillo nos determinará el tamañ o del
Por cierto que el cuarto valor, es decir el 1.0, se refiere al
punto de má xima reflexió n de luz y esto nos permite valor del canal alfa del color RGB. Ya llegará má s
obtener efectos muy variados y llamativos.
adelante el momento de estudiar esto cuando hablemos de
Se pueden especificar diferentes pará metros en cuanto a transparencias.
material para cada polí gono. Es una tarea ardua, pero
ló gicamente a má s variedad de comportamientos má s ¡ Al Ejemplo!
real será la escena. El funcionamiento es el normal en
Veamos lo que nos muestra esta vez el Listado 1. Como
OpenGL. Cada vez que se llama a la correspondiente verá n, lo que pretendemos hacer en este programa es
funció n se activan esos valores que no cambiará n hasta mostrar có mo podemos hacer diferentes combinaciones de
llamarla de nuevo con otros. Por tanto todo lo que se las caracterí sticas que hemos mencionado, para obtener
"renderice" (por decir un palabro, como dijeran en Españ a) variados materiales a partir de prá cticamente los mismos
a partir de una llamada heredará esas caracterí sticas. La valores.
funció n es: glMaterialfv ( GLenum face, GLenum pname, const GLfloat En realidad, mucho de lo que mostramos en el ejemplo ya lo
*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
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
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
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 DepthBuffer
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);
glLightfv(GL_LIGHT0, GL_DIFFUSE, @diffuse);
glLightfv(GL_LIGHT0, GL_POSITION, @position);
//Se definen
//Las Cacarterísticas de la luz
//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
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
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
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
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
Texturas con OpenGL(I)
Principios b
ásicos de texturizaci
ón.
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
transformaciones
lineales aplicadas a
una textura.
OpenGL
soporta
imá genes de texturas
de una y dos
dimensiones (1D y
Una matriz especial para las texturas
2D) y cuyas medidas
OpenGL soporta dos t é cnicas de texturizado: Por
sean una potencia de
Manipulaci ón del Color de la Superficie y por Mapeado
dos. Por ejemplo:
del Ambiente. La primera de estas se refiere a manipular
i m á g e n e s
directamente los colores de las superficies de las caras a
bidimensionales de
afectar, sin tomar en cuenta nada m
á s que el color de la
64 X 64 p íxeles, de
textura a aplicar. Y la segunda consiste en involucrar los
256 X 128, etc. En
colores de los materiales de los objetos a texturizar, as
í
a l g u n a s
como las luces y las normales obteniendo una textura
implementaciones de
combinada con el ambiente.
OpenGL se han
Hasta ahora ya hab íamos estudiado las matrices de
extendido
y
se
modelado y proyecci ón, mencionando cada una de sus
soportan
texturas
de
3
Figura 3.caracter ísticas y para qu é nos sirven. Pues es el turno de
y
4
dimensiones
La misma textura despu é s de haber
hablar de la matriz de texturas.
tambi é n (3D y 4D),
sido escalada linealmente.
Esta matriz, como las anteriores, nos va a servir para
pero
por
ahora
definir transformaciones lineales; en este caso para las
empezaremos por lo
texturas que utilicemos en nuestra aplicaci
ón. Al igual que
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
Figura 1.- La textura original.
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.
Figura 2.La misma textura rotada 45 grados.
50
Texturas con OpenGL(I)
sencillito.
Un concepto nuevo: Los Texeles
Un 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
Figura 4.- Los 3 casos posibles de filtrado.
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.
Figura 5.- Los criterios de los filtros.
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
51
Texturas con OpenGL(I)
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.
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.
Ahora otra cuestión, El ambiente...
El proceso por el cual el valor final del fragmento de color ¿Qué esperamos para verlo
es derivado, esta referido a la aplicación de la función de funcionando?
ambiente (TexEnv).
Bien, vemos lo que esta vez nos muestran los listados esta
Existen diversos métodos para computar el valor final del vez:
color a aplicar, cada uno de estos capaz de producir un Lo que trataremos de hacer es una librería de clases para
particular efecto. Uno de los más comúnmente utilizados manejar de manera práctica las texturas en Delphi. Si una
es el de la función Modulate. Para todos los propósitos de las principales características que hacen de Delphi un
prácticos la función modulate puede ser aplicada escalando lenguaje tan poderoso es precisamente el que sea un
el color del fragmento de color, con el color del texel lenguaje orientado a objetos, estaría mal que nosotros no
correspondiente.
utilizáramos esa
Típicamente
las
ventaja a la hora
aplicaciones generan
de
programar
polígonos blancos,
gráficos, ¿no les
se
iluminan
y
parece?.
entonces se utiliza
Así que lo que
ese valor para
empezaremos por
efectivamente
definir en el
producir
una
Listado 1 es una
superficie iluminada
clase a la que
y texturizada. Pero
llamaremos
Figura 6.- Los efectos de las funciones de ambiente.
ya veremos la
TTextura, que será la
aplicación de las luces a cuerpos texturizados en el que nos servirá para encapsular todo el trabajo de
siguiente número. Por ahora veremos que cuando no definición de texturas en OpenGL. Analicemos cada uno
utilizamos luces en nuestra escena la función modulate lo de los campos y métodos que utilizaremos en nuestra clase:
Listado 1
unit Texturas;
interface
uses 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)
52
Texturas con OpenGL(I)
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;
implementation
Constructor 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;
begin
gldisable(GL_TEXTURE_2D);
end;
end.
53
Texturas con OpenGL(I)
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;
interface
uses
Windows, Forms, OpenGL, Classes,
ExtCtrls, Messages, Controls, StdCtrls,
Graphics, sysUtils, Dialogs, Texturas;
54
Texturas con OpenGL(I)
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;
implementation
uses 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;
55
Texturas con OpenGL(I)
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...
56
Texturas con OpenGL(I)
//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;
57
Texturas con OpenGL(I)
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
58
Texturas con OpenGL(I)
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).
Figura 7.- Las texturas que usamos en el ejemplo.
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,
Figura 9.La ventana auxiliar de herramientas.
Por ahora hasta aquí llegamos, pero en el próximo
número seguiremos estudiando las texturas.
Hasta Pronto
Figura 8.- Nuestra ventana principal.
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
59
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 texturas
En 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)
glTexCoord2f(0.0,1.0)
glTexCoord2f(1.0,1.0)
glTexCoord2f(1.0,0.0)
glEnd() ;
;
;
;
;
glVertex2f(-1.0,-1.0)
glVertex2f(-1.0, 1.0)
glVertex2f( 1.0, 1.0)
glVertex2f( 1.0,-1.0)
;
;
;
;
Obtene mos 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)
;
glTexCoord2f(0.0,up) ;
glTexCoord2f(1.0,up) ;
glTexCoord2f(1.0,down)
;
glEnd() ;
; glVertex2f(-1.0,-1.0)
glVertex2f(-1.0, 1.0) ;
glVertex2f( 1.0, 1.0) ;
; glVertex2f( 1.0,-1.0)
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érica
Tenemos 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
Texturas con OpenGL(II)
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.
Figura 1 .Ejemplos de generación de coordenadas de texturas.
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.
Figura 3 .Una aplicación de la generación de coordenadas
lineales al observador la tenemos al mapear terrenos.
Y vamos con las texturas esféricas...
Figura 2 .Planos en generación de coordenadas lineales.
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
Texturas con OpenGL(II)
Figura 4 .- Objetos texturizados con Texturas Esféricas.
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
Figura 5 .- Como se ve una textura esférica.
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
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
mipmapping
Cuando 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
Figura 6 .Como se construye una textura esférica a partir de las caras de un cubo. raros que se le pasan a este m étodo para
construir la textura.
62
Texturas con OpenGL(II)
Figura 4 .- Objetos texturizados con Texturas Esféricas.
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.
Figura 7 .- Incidencia del ambiente sobre texturas esféricas.
Figura 5 .- Como se ve una textura esférica.
Los criterios de repetición y dos
coordenadas para las texturas
Como ya vimos, cuando definimos el mapa de texturas,
tmodelo poligonal, y ver la textura original como si fuera definimos coordenadas que van de 0 a 1 en ambos ejes.
parte de las caras de un cubo. Para enterarnos mejor de
esto veamos la Figura 6, donde mostramos como se
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
Figura 6 .Como se construye una textura esférica a partir de las caras de un cubo. más cercana en el rango de [0..1] (GL_CLAMP).
63
Texturas con OpenGL(II)
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.
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
Figura 8 .- Los casos en los criterios de repetición de coordenadas.
inicializada con el procedimiento glGenTextures(),
Un vistazo al método constructor de las
que genera el espacio donde se almacenará dicha textura.
En el ejemplo mostramos como se puede hacer para
texturas y al mipmapping
Cuando definimos las texturas en OpenGL utilizamos el utilizar esta característica, aunque en realidad en Delphi
método: glTextImage2D(), el cual explicamos un poco en el podríamos sustituirla por nuestra propia definición de
mes pasado. Bien, esta vez veremos que son esos clases.
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
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
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...
implementation
Constructor 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
Texturas con OpenGL(II)
dMedia:
begin
glTexParameteri(GL_TEXTURE_2D,
glTexParameteri(GL_TEXTURE_2D,
end ;
dBaja:
begin
glTexParameteri(GL_TEXTURE_2D,
glTexParameteri(GL_TEXTURE_2D,
end;
GL_TEXTURE_MAG_FILTER, GL_LINEAR);
GL_TEXTURE_MIN_FILTER, GL_LINEAR);
GL_TEXTURE_MAG_FILTER, GL_NEAREST);
GL_TEXTURE_MIN_FILTER, GL_NEAREST);
end;
glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, ModeEnv);
glBindTexture(GL_TEXTURE_2D, texts[TexID]);
glenable(GL_TEXTURE_2D);
end;
Procedure TTextura.InhibeTexturas;
begin
gldisable(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
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
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
Texturas con OpenGL(II)
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.
Figura 9 .- La aplicación corriendo con un modo de
ambiente de GL_BLEND
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
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.
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...
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
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
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 1
program 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
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 ventana
procedure 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
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
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
GLUT_DOUBLE or
GLUT_DEPTH);
// Y creamos la ventana...
glutCreateWindow('VentanaGL');
// Color en formato RGB
// con Doble Buffer
// Y un buffer para la prueba de profundidad...
// 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
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 2
unit Texturas;
interface
uses 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
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;
implementation
Constructor 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
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
Luces y Texturas con OpenGL
dMedia:
begin
glTexParameteri(GL_TEXTURE_2D,
glTexParameteri(GL_TEXTURE_2D,
end ;
dBaja:
begin
glTexParameteri(GL_TEXTURE_2D,
glTexParameteri(GL_TEXTURE_2D,
end;
GL_TEXTURE_MAG_FILTER, GL_LINEAR);
GL_TEXTURE_MIN_FILTER, GL_LINEAR);
GL_TEXTURE_MAG_FILTER, GL_NEAREST);
GL_TEXTURE_MIN_FILTER, GL_NEAREST);
end;
glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, ModeEnv);
glBindTexture(GL_TEXTURE_2D, ID);
glenable(GL_TEXTURE_2D);
end;
Procedure TTextura.InhibeTexturas;
begin
gldisable(GL_TEXTURE_2D);
end;
Destructor TTextura.Free;
begin
IF 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
Luces y Texturas con OpenGL
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
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
Figura 1.- La ventana creada por GLUT
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í
79
Más sobre Texturas y Listas de Despliegue con OpenGL
Có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 clienteservidor 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
Más sobre Texturas y Listas de Despliegue con OpenGL
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
Figura 1. Casos de arquitecturas gráficas.
almacenada en el pipeline gráfico,
justo antes de su salida por pantalla.
podemos trabajar, ya sabemos que las redes no "corren" a No vuelve a pasar por todo el pipeline nunca más, que es
veces a la velocidad que deberían. Por otra parte y gracias lo que ocurriría si mandáramos a dibujar el cuadrado
a utilizar hardware específico gráfico, conseguimos nosotros, cada vez.
equilibrar la balanza, al aumentar la velocidad a la que Uno de los grandes ejemplos de uso de listas de
trabajamos.
despliegue en OpenGL es la generación de un alfabeto, de
Pues bien, usaremos entonces las listas de despliegue en una fuente (font). Para escribir texto en OpenGL, se
OpenGL para todo aquello que tengamos que dibujar suelen crear las letras 3D con polígonos, y después se
siempre, de forma continua, y no queramos ir dibujan en pantalla. Lo que puede hacerse es crear cada
recalculando cada vez. Lo calcularemos al principio, lo letra (trabajo duro sin duda), almacenarla en una lista de
mandaremos en forma de lista y llamaremos a una función despliegue y después tan sólo llamarla cuando queramos
de tanto en tanto para recordarle al servidor que debe dibujarla.
dibujar.
Las texturas con GLAUX
Defino una lista de despliegue con OpenGL:
El mes pasado hacíamos mención de que una forma
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.
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;
interface
uses OpenGL, windows, Graphics, SysUtils, Dialogs,Classes, GLAux;
81
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;
implementation
Constructor 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
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;
begin
gldisable(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
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;
interface
uses
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
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
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);
begin
inc(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
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

Documentos relacionados