Вы находитесь на странице: 1из 87

Programación con Delphi y OpenGL

Carlos García Trujillo


cgar1136@yahoo.com
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.
gratuita y es una muy sencilla y práctica interfaz entre Delphi y
Delphi en tres dimensiones la gran mayoría de funcionalidades de DirectX.

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


OpenGL es una librería gráfica escrita originalmente en C que
Quiero hablar un poco acerca de las opciones que tenemos para permite la manipulación de gráficos 3D a todos los niveles. Esta
realizar aplicaciones que involucren gráficos 3D en Delphi, ya librería se concibió para programar en máquinas nativas Silicon
sean salvapantallas, juegos, demos multimedia, interfaces 3D, e Graphics bajo el nombre de GL (Graphics Library).
incluso simulaciones y aplicaciones de realidad virtual. Posteriormente se consideró la posibilidad de extenderla a
Así que mencionaremos las características mas relevantes de las cualquier tipo de plataforma y asegurar así su portabilidad y
dos API’s gráficas de mayor importancia en el mundo de las extensibilidad de uso con lo que se llegó al término Open
computadoras personales: Direct3D y OpenGL. Graphics Library, es decir, OpenGL.
¿Que por qué usar una de estas API’s? Los motivos son varios: La librería se ejecuta a la par con nuestro programa
ahorrarnos tiempo de trabajo, permitir un amplio soporte de independientemente de la capacidad gráfica de la máquina que
hardware y lograr código fácilmente portable. usamos. Esto significa que la ejecución se dará por software a no
ser que contemos con hardware gráfico específico en nuestra
Direct3D máquina. Si contamos con tarjetas aceleradoras de v ídeo,
Direct3D es el API 3D de Microsoft. Es un completo conjunto tecnología MMX, aceleradoras 3D, pipelines gráficos
de servicios de gráficos 3D tanto de transformaciones, implementados en placa, etc ... gozaremos por supuesto de una
iluminación y renderizado de escenas, así como de acceso ejecución muchísimo más rápida en tiempo real.
transparente a la aceleración por hardware y una comprensible Así esta librería puede usarse bajo todo tipo de sistemas
solución 3D para la nueva generación de PC’s. operativos e incluso usando una gran variedad de lenguajes de
Es la última adición al altamente popular conjunto de API’s programación. Podemos encontrar variantes de OpenGL para
Microsoft® DirectX™ de tecnología multimedia. Este conjunto Windows 95/NT, Unix, Linux, Iris, Solaris, Java e incluso
incluye las API’s DirectDraw™, DirectSound™, DirectInput™ lenguajes de programación visuales como Visual Basic, Borland
y DirectPlay™. C++ Builder y por supuesto Delphi.
Direct3D proporciona acceso a avanzadas capacidades gráficas En contraste con la antigua IRIS GL-library de SGI, OpenGL es
de aceleradores 3D por vía hardware, tales como el z-buffering por diseño independiente de plataformas y sistemas operativos
(que se usa para registrar la proximidad de un objeto al como ya lo mencionamos, y esto es un punto que podemos
observador, y es también crucial para el eliminado de superficies tomar en cuenta los programadores de Delphi, pensando en que
ocultas), antializado de líneas (reduce los bordes escalonados en próximamente saldrá una versión de Delphi para Linux, en la
las líneas dibujadas sobre una pantalla), transparencias, efectos que obviamente no podremos contar con DirectX.
atmosféricos, y correcta perspectiva del mapeado de texturas. Además es perceptiva a la red, de manera que es posible separar
Entre las desventajas que podríamos enumerar de Direct3D es nuestra aplicación OpenGL en un servidor y un cliente que
que es una interfaz complicada y poco portable. Sin embargo, si verdaderamente produzca los gráficos. Existe un protocolo para
queremos que nuestras aplicaciones gráficas corran bajo mover por la red los comandos OpenGL entre el servidor y el
Windows 9x soportando aceleración de hardware en casi todas cliente. Gracias a su independencia del sistema operativo, el
las tarjetas del mercado y al mismo tiempo funcionen sin tarjetas servidor y el cliente no tiene porque ejecutarse en el mismo tipo
aceleradoras o saquen provecho del MMX y los de plataforma, muy a menudo el servidor ser á una
microprocesadores que vendrán sin tener que programar las supercomputadora ejecutando una compleja simulación y el
rutinas de rasterización por software aparte, la única opción es cliente una simple estación de trabajo mayormente dedicada a la
Direct3D. visualización gráfica. OpenGL permite al desarrollador escribir
Aquí cabe hacer mención que Direct3D soporta un juego de aplicaciones que se puedan desplegar en varias plataformas
chips gráficos mucho más amplio que el soportado por OpenGL, fácilmente.
y esta es una de las razones por las que este sea tan popular en el Por encima de todo, OpenGL es una biblioteca estilizada de
área del desarrollo de juegos para PC’s, y que muchas empresas trazado de gráficos de alto rendimiento, y hay varias tarjetas
de desarrollo de este tipo de aplicaciones basen sus programas gráficas aceleradoras y especializadas en 3D que implementan
en esta API. Direct3D es el API elegida para portar juegos de primitivas OpenGL a nivel del hardware. Hasta hace poco, estas
consolas como Playstation a PC. avanzadas bibliotecas gráficas solían ser muy caras y sólo
Los programadores de Delphi tenemos mucho de donde escoger estaban disponibles para estaciones SGI u otras estaciones de
en cuanto a Direct3D, ya que existen en Internet una gran trabajo UNIX. Las cosas están cambiando muy deprisa y gracias
cantidad de componentes y librerías freeware que funcionan a las generosas licencias y el kit de desarrollo de controladores
como interfaz entre Delphi y DirectX. de SGI, vamos a ver más y más hardware OpenGL para usuarios
Entre los componentes más relevantes que podemos mencionar de PC’s.
respecto a DirectX con Delphi están los famosos DelphiX de Al contrario de Direct3D, OpenGL es un API altamente portable
Hiroyuki Hori, y los agregados que se han escrito para esta y sencilla. Lo de sencilla es más bien en comparación con
librería, como el TCollisionTester3DX de Henrik Fabricius, Direct3D, ya que para poder comprender la mayor parte de las
entre otros. Y qué decir además de la suite de componentes funciones de OpenGL es necesario tener un poco de
Delphi Games Creator, la cual también se distribuye de manera 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 permitiéndonos olvidarnos de las rutinas de rasterización por
parecer. software. Pero también es claro que aparecerán más versiones de
OpenGL provee prácticamente las mismas funcionalidades en Direct3D, incorporando mejoras y que no saldr án nuevas
cuanto a capacidades gráficas que Direct3D; incluso en algunos versiones de OpenGL para Windows (al menos esa es la
aspectos se comporta de una manera más eficiente, y posee una intención de Microsoft). Por otra parte tenemos los obscuros
serie de características que facilitan al programador la tarea de planes de Microsoft de lanzar una consola basada en Direct3D y
construir la escena, como tal es el caso de las Listas de Windows CE en conjunción con SEGA y de crear una nueva
Despliegue, que son una manera de almacenar comandos de API trabajado conjuntamente con Silicon Graphics, que promete
dibujo en una lista para un trazado posterior. ser una fusión de ambas APIs.
Los Programadores de Delphi tenemos un amplio panorama en Por esto no debemos preocuparnos por un futuro lleno de
frente nuestro respecto a OpenGL, ya que de por sí las versiones conjeturas y ocuparnos por el presente. Nuestra recomendación
de Delphi de 32 bits incluyen la DLL OpenGL32, que es la (si de algo sirve para aquellos que después de leer esto quedaron
versión de OpenGL para Win32, además incluye una unidad aun más confundidos) es determinar el mercado de la aplicación
llamada OpenGL.Pas la cual es la interfaz entre la DLL y que pretendemos realizar y el tiempo que se tiene para terminar
nuestras propias aplicaciones. Incluso algunas versiones de el proyecto, de esta forma podremos decidir por una complicada
Delphi traen archivos de ayuda para esta API y sus múltiples API con mercado amplio en las PC’s como Direct3D; o bien una
funciones. API sencilla con un mercado más restringido en las PC’s que
Debemos mencionar que existen muchos programadores de nos permite la posibilidad de portar código a casi cualquier
Delphi que han hecho valiosas aportaciones al mundo de los plataforma de una forma mas sencilla como tal es el caso de
gráficos, tales como: Mitchell E. James con su componente OpenGL.
GLPanel, ó Mike Lischke, el cual es un prominente miembro del En posteriores números estudiaremos más a fondo cada una de
grupo JEDI (Joint Endeavor of Delphi Innovators, Grupo de estas API’s (OpenGL y Direct3D), y presentaremos algunos
Esfuerzos de Innovadores de Delphi), y que también ha escrito programas interesantes que podemos realizar con Delphi
varias librerías para OpenGL en Delphi. Esto, por mencionar basándonos en estas tecnologías (como el mostrado en la figura
solo a algunos, a fin de cuentas existen muchos programadores 1), así como algunas cuestiones relacionadas con la
reconocidos en este ámbito. 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
que esto ofrece independencia del sistema operativo, pues no es
Introducción a la Programación lo mismo crear una ventana basándose en las API’s de
de Gráficos con OpenGL 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
Construcción de Aplicaciones Gráficas. aplicación de plataforma, ya que solo habría que conseguir la
Matrices y Vectores. Inicialización de versión de GLUT para el ambiente gráfico deseado y volver a
Contextos y consideraciones adicionales. 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
Bien, ahora nos dedicaremos a estudiar específicamente el API Linux. Las funciones contenidas en esta librería empiezan con
de gráficos OpenGL. En el articulo del n úmero pasado las letras glut.
hablábamos de las comparaciones entre OpenGL y Direct3D, y Estas librerías se encuentran disponibles en Internet, y se
de lo que es un API de gráficos solo en teoría, pero esta vez distribuyen de manera gratuita en diferentes sitios,
estudiaremos más a detalle y con ejemplos de código como se principalmente se pueden encontrar en el sitio oficial de
construye una aplicación gráfica basada en OpenGL. OpenGL en http://www.opengl.org , donde seguramente
Siendo honestos, la programación de gráficos 3D no es un tema encontrarán las de su sistema operativo en específico.
sencillo, ya que requiere de conocimientos previos de análisis Otro componente es el llamado Frame Buffer, que no es otra
numérico, álgebra lineal, física y otros muchos temas extras, cosa que el área de memoria donde se construyen los gráficos
sin embargo aquí trataremos de hacer esto lo más digerible antes de mostrarlos al usuario, es decir, que nuestro programa
posible para que todo el mundo podamos entenderlo sin tanto de OpenGL escribe en esta área de memoria, y
embrollo; aunque desafortunadamente, no hay forma de evitar automáticamente envía su contenido a la pantalla una vez que
la necesidad de tener conocimientos sobre matrices, vectores, y la escena está completamente construida. La figura 1 muestra
un poco de geometría, así que más conviene dedicar algo de gráficamente como esta constituida la estructura de abstracción
tiempo a estudiar estos temas si se quieren hacer este tipo de de OpenGL.
aplicaciones.

¿Como Funciona OpenGL?


OpenGL funciona a través de una serie de librerías DLL que
suelen tener variados nombres, pero que a fin de cuentas cubren
funciones muy específicas y son muy sencillas de identificar,
ahora veremos cuales son estas:
OpenGL.DLL (Open Graphics Library, Librería de Gráficos
Abierta).- Esta podemos encontrarla también con el nombre de
OpenGL32.DLL (para el caso de los sistemas operativos de 32
bits) o bien simplemente como GL.DLL . Esta es la librería
principal, que contiene la mayoría de las funciones que aquí
utilizaremos. Las funciones contenidas en esta librería inician
con las letras gl.
Figura 1 Niveles de Abstracción en OpenGL
GLU.DLL (Graphics Utility Library, Librería de Utilerías de
Gráficos).- También la podemos encontrar como GLU32.DLL.
Contiene funciones para objetos comunes a dibujar como Las Piedras angulares: las matrices y los
esferas, donas, cilindros, cubos, en fin, varias figuras ya vectores
predefinidas que pueden llegar a ser útiles en ciertos casos, así
Aquí es donde empieza el trabajo sucio de todo este asunto.
como funciones para el manejo de la cámara entre muchas
Toda la geometría que se despliega en las aplicaciones OpenGL
otras. Podremos identificar las funciones que pertenecen a esta
está basada en los conceptos de Matrices y Vectores y las
librería porque llevan antepuestas en su nombre las siglas glu.
operaciones aritméticas aplicables a estas estructuras.
GLUT.DLL (GL Utility Toolkit, Equipo de Herramientas de
En OpenGL existen básicamente tres matrices principales:
Utilería para el desarrollo de Gráficos).- Esta también permite
Una matriz de proyección llamada GL_PROJECTION, la cual nos
crear objetos complejos como GLU, aunque la principal
permite determinar la perspectiva que usaremos para observar
función de esta librería es permitir que los programas se
la escena generada, así como el tipo de proyección a usar (esto
vuelvan interactivos, o sea que posibilita la libre creación de
tiene que ver mucho con cuestiones de óptica y de geometría, y
ventanas, así como el acceso al ratón y al teclado. Nosotros
abordaremos este tema en otra ocasión más específicamente, ya
podríamos llegar a prescindir en ese aspecto de GLUT, ya que
que es bastante extenso y complejo). Esta matriz tiene una gran
Delphi nos ofrece de manera natural poder crear ventanas y
relación con otro concepto también muy interesante llamado
responder a los diferentes eventos del manejo del ratón y el
clipping, que consiste en recortar u ocultar todo aquello que
teclado; sin embargo, muchos programadores (principalmente
“está pero no se ve”, es decir lo que queda fuera del foco de
programadores de C y C++) piensan que programar
nuestra cámara o nuestro campo visual activo, este es un
aplicaciones basadas en GLUT tiene muchísimas ventajas, ya
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. de los vectores, y como se transforman las coordenadas hasta
Una matriz de Modelado llamada GL_MODELVIEW, donde ésta es llegar a las coordenadas reales de la ventana.
la matriz que usaremos para aplicar a nuestra escena
operaciones de rotación, traslación o escalamiento, o bien para Vectores de diversos tipos
manipular la posición y orientación de la cámara, para obtener Los vectores son un conjunto de valores que nos permiten
así las animaciones. La figura 2 muestra algunos ejemplos de definir dentro de nuestra escena, desde los vértices de las
matrices de transformación lineal para el caso de sistemas de 3 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
Debemos recordar que las librerías de OpenGL fueron escritas originalmente en C, así que
se preguntarán “¿y de cual conservan muchas características de este lenguaje, entre ellas, la estructura de sus
hierba hay que fumar para procedimientos, y los tipos que se utilizan.
saber como multiplicar Aunque en los tipos de OpenGL se anteponen las siglas GL, estos guardan una marcada
vectores por matrices?”, pues equivalencia con los tipos genéricos de C, así GLFloat sería el equivalente al tipo float de C, y
basta con echar una mirada a GLint, al tipo int. Y los rangos del dominio alcanzado por cada uno de estos tipos depende de
cualquier libro de Álgebra su correspondiente equivalencia en C.
Lineal y ahí se explica el Aunque OpenGL también provee de ciertos tipos de datos estructurados para el manejo
oscuro procedimiento. tanto de matrices como de vectores, y generalmente estos tipos ya vienen en las librerías de
En la figura 4 podemos OpenGL para Delphi con su respectiva equivalencia en tipos de Object Pascal.
observar el orden en como se
aplican las matrices a cada uno

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

el objeto, cara ó vértice al cual corresponda dicho color, a esta private


{ Private declarations }
característica de transparencia se le conoce comúnmente como
procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message
Alpha Blending. Todos estos valores (en el caso específico de WM_ERASEBKGND;
los colores) tienen un dominio que fluctúa en el rango de 0 a 1. public
Los vectores están compuestos por números que pueden ser de { Public declarations }
end;
tipo real, enteros, bytes, doubles, short, etc... Aquí cabe señalar
que OpenGL provee de sus propios tipos, los cuales por var
convencionalismos similares al que usamos en Delphi, Form1: TForm1;
anteponiendo una T a las clases que usamos, en OpenGL se RC : HGLRC;
Angulo : GLInt;
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í implementation
existen muchas equivalencias entre ambas tipologias (Ver
recuadro: Tipos en OpenGL). {$R *.DFM}
Para declarar un vector como un vértice descomponiendo sus procedure TForm1.FormCreate(Sender: TObject);
elementos usaríamos la instrucción GLVertex3f(X,Y,Z), donde X, Y y begin
Z son variables ó constantes de tipo GLFloat, Real, ó Single. La parte RC:=CreateRenderingContext(canvas.Handle,[opDoubleBuffered],32,0); //Primero
final de la instrucción que usamos: 3f , nos indica que el Creamos un contexto...
end;
procedimiento recibe como parámetro tres valores de punto
flotante. Así la instrucción GLVertex3i() funcionará exactamente procedure TForm1.FormDestroy(Sender: TObject);
igual solo que con valores enteros. begin
DestroyRenderingContext(RC); //Se libera el Contexto...
Cuando trabajamos los vectores como una estructura de datos lo
end;
que hacemos es pasar la dirección de memoria de esta estructura
en la llamada del procedimiento, usando el operador @. Por procedure TForm1.FormPaint(Sender: TObject);
ejemplo: Si suponemos una variable V de tipo Tvector, donde Var X,Y,Z:GLInt;
begin
Tvector está definido como: Tvector = Array [0..2] of GLFloat; podríamos ActivateRenderingContext(canvas.Handle,RC); // Se asocia el contexto con el
usar la instrucción: GLVertex3fv(@V); para declarar el vértice a partir manejador del Canvas...
de este dato estructurado. Observen como en estos casos las glClearColor(0,0.2,0,0); // Ponemos un color Verde Oscuro de Fondo...
instrucciones para manejar estructuras terminan en: v. glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); // Algo así como
borrar la Pizarra...
glMatrixMode(GL_MODELVIEW);
¡En Delphi!, ¡En Delphi por favor! glLoadIdentity;
gluLookAt(0,0,3,0,0,0,0,1,0); // Posición y punto de vista de la cámara...
Bueno, ahora realizaremos nuestro primer programa OpenGL
en Delphi, pero para esto, a fin de hacer las cosas mucho más glRotatef(30,1,0,0); //Primero hacemos una rotación de 30 grados respecto a X para
sencillas y que no nos cueste tanto trabajo empezar, utilizaremos obtener perspectiva...
una adecuación a la librería OpenGL.Pas (versión 1.2) de glRotatef(Angulo,0,1,0); //Se Rota la figura en el eje Y a partir de la variable Angulo...
Delphi hecha por Mike Lischke, un alem án con muchas GLPointSize(2.0); //Asignamos un tamaño de 2 pixeles para cada punto...
aportaciones al mundo de los gráficos en Delphi, y que provee
en esta librería muchas funciones que facilitan el proceso de //Ahora con tres ciclos anidados dibujamos un cubo con puntos de colores variados...
GLBegin(GL_POINTS);
inicialización de contextos, que nos permitirán entender este
For x := -5 to 5 do
caso de una manera muy sencilla. Tanto el código fuente que for y := -5 to 5 do
aquí veremos, como las librerías .pas que utilicemos podrán for z := -5 to 5 do
descargarlas desde el sitio web de esta revista sin ning ún begin
GLColor3f(X,Y,Z);
problema. GLVertex3f(X/10,Y/10,Z/10);
Primero que nada echemos un vistazo al Listado 1, y ahora end;
analizaremos bit por bit este código fuente, ó al menos eso GLEnd;
intentaremos.
SwapBuffers(canvas.Handle); //Copiar el Back Buffer en el canvas del formulario...
DeactivateRenderingContext; //Libera el contexto...
unit Unit1;
end;
interface
procedure TForm1.FormResize(Sender: TObject);
begin // Cuando se cambia de tamaño hay que actualizar el puerto de visión...
uses
wglMakeCurrent(canvas.handle,RC); // Otra manera de hacer el contexto dibujable
Windows, Forms, OpenGL, Classes, ExtCtrls, Messages, Controls, StdCtrls,Graphics,
cuando este ya está creado...
sysUtils, Dialogs;
glViewport(0,0,Width,Height); // Especificar un puerto de visión....
glMatrixMode(GL_PROJECTION); // Activar matriz de proyección...
type
glLoadIdentity; // Poner estado inicial en esta matriz...
TForm1 = class(TForm)
gluPerspective(35,Width/Height,1,100); // Especificar Perspectiva ...
Timer1: TTimer;
wglMakeCurrent(0,0); // Otra manera de liberar el contexto...
procedure FormCreate(Sender: TObject);
Refresh; // Redibujar la escena ...
procedure FormDestroy(Sender: TObject);
end;
procedure FormPaint(Sender: TObject);
procedure FormResize(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
6
Introducción a la Programación de Gráficos con OpenGL

inicialización con GLLoadIdentity(), para poder aplicar las


procedure TForm1.Timer1Timer(Sender: TObject);
operaciones de rotación con las operaciones GLRotate() a los
begin
Inc(Angulo,4); //Rotamos el angulo de observación de la escena... vértices que luego se definen.
Angulo := Angulo mod 360; La operación gluLookAt() sirve para determinar un punto de
Refresh; //y la volvemos a dibujar ... observación (ó cámara) para la escena, podríamos obtener el
end;
mismo efecto de rotación si en vez de transformar los vértices
procedure TForm1.WMEraseBkgnd(var Message: TWMEraseBkgnd); de la escena solo movemos este punto de observación, es algo
begin //Para borrar el fondo, y evitar el parpadeo ... similar a lo que se hace en los clásicos juegos 3D de estilo
Message.Result:=1; Doom. En posteriores artículos hablaremos concretamente de la
end;
cámara y su utilización en concreto.
end. Esta animación la construiremos utilizando solo puntos aislados,
por lo que usamos la operación GLPointSize() para determinar el
Como podemos observar en el listado, tenemos un formulario y número de píxeles reales que ocupará cada uno de nuestros
un componente Timer, el formulario nos servirá como la puntos, en nuestro caso 2. Y luego entre las declaraciones
ventana en donde se mostrarán los gráficos; y el Timer será GLBegin() y GLEnd() que sirven para determinar los vértices de una
quien coordine nuestra animación, en esta ocasión a fin de hacer figura, llamamos a GLColor3f() y GLVertex3f() dentro de tres ciclos
las cosas más sencillas usaremos el Timer para esta finalidad, anidados para dibujar todos los vértices con su correspondiente
pero ya veremos más adelante en artículos posteriores que es color. Nótese que se deben de declarar primero los colores que
mucho mas eficiente utilizar hilos de ejecución de alta prioridad los vértices, porque como veremos más adelante, OpenGL
para hacer esto, por ahora dejémoslo de ese tamaño. funciona como una máquina de estados finitos, donde se deben
Primeramente y como se deben de imaginar, necesitamos un declarar los atributos antes de las figuras.
lienzo en el cual plasmar nuestras “obras de arte”, por lo cual
necesitamos de procesos que nos permitan acceder directamente ¿Y el resto?
a algún objeto gráfico que nos sirva como lienzo. En este primer
Hasta ahora ya utilizamos la matriz de modelado para hacer las
ejemplo usamos el Objeto Canvas de nuestro formulario, pero en
rotaciones, pero como vemos en el evento OnResize hacemos uso
teoría podemos utilizar el Canvas de cualquier otra clase que
de la matriz de proyección para definir nuestro puerto de visión
contenga a éste objeto (un PaintBox por ejemplo). A este lienzo
y perspectiva. Entender como funciona realmente la matriz de
le llamaremos el contexto.
proyección es un poco complicado y ya la estudiaremos a su
Como pueden darse cuenta tenemos una variable global llamada
tiempo, por ahora solo debemos entender que el evento OnResize
RC de tipo HGLRC, esta variable será la que represente nuestro
se dispara cada vez que modificamos el tamaño de nuestra
contexto dibujable. En el evento OnCreate del formulario hemos
ventana, por lo cual debemos ajustar la escala de lo que estamos
escrito una línea de código que sirve para asociar nuestro
observando para no perder las dimensiones, es por eso que
contexto con el manejador (handle) del objeto Canvas del
debemos tenerlo presente. Observen como al final del código
formulario, lo cual hace que nuestras animaciones se
para este evento usamos el método Refresh del TForm, lo cual
desplieguen sobre este Canvas. Los parámetros que se pasan a
obliga al formulario a volver a dibujar la escena con la matriz de
este procedimiento los estudiaremos a detalle en otra ocasión.
proyección ya ajustada. También cuando Windows trata de
Del mismo modo en el Evento OnDestroy del formulario debemos
dibujar el formulario después de haberlo creado se dispara
escribir una línea de código que libere nuestro contexto del
OnResize, lo que nos garantiza que, desde el principio, tendremos
Canvas, para evitar así apuntadores perdidos por ahí.
ajustada nuestra perspectiva sin la necesidad de modificar
Ahora, las animaciones como han de suponer deben construirse
nosotros mismos el tamaño de nuestra ventana. Una vez
escena por escena, como en las películas de dibujos animados,
ejecutando este programa traten de modificar el tamaño ustedes
con ligeras diferencias entre ellas para obtener calidad en
mismos para que vean como es que funciona esto.
nuestra animación, y a la vez debemos hacer uso de algún
El procedimiento del Timer lo único que hace es incrementar
evento para mostrar estas escenas; así que podemos utilizar el
una variable global llamada Angulo y volver a dibujar la escena;
evento OnPaint del formulario para irlas mostrando una por una.
la operación mod que efectuamos a esta variable es para
Como sabemos este evento se dispara cada vez que Windows
asegurarnos que su dominio solo fluctuará entre los valores 0 y
tiene que dibujar el formulario así que podemos ocuparlo para
359, que son los 360 grados de una circunferencia. Nosotros en
que haga en él lo que nosotros deseamos que se vea.
el evento OnPaint rotamos la escena conforme a esta variable, lo
En este evento es donde escribimos el código para generar las
que ocasiona que escena con escena obtengamos nuestra figura
escenas que nos interesa mostrar, vemos como con cada frame o
en diferentes posiciones relativas, y con esto obtener la
escena, hacemos el contexto dibujable y al finalizar lo liberamos
animación. El numero de unidades que incrementemos a esta
de nuevo. La instrucción glclearcolor() recibe como parámetro un
variable con cada escena, será lo que determine la calidad de
vector de 4 valores que determinarán el color con el que se
nuestra animación a final de cuentas.
inicializará nuestro frame buffer antes de escribir en él, dando
Ahora como estamos utilizando el Canvas del formulario como
como resultado un color de fondo. La instrucción GLClear() sirve
lienzo y continuamente estamos mandando a redibujar la
para borrar de este frame buffer valores de escenas que hayan
escena, forzamos a que Windows tenga que borrar el contenido
sido construidas previamente a la actual. También podemos
del formulario anterior antes de mostrar el nuevo contenido, lo
observar como hacemos uso de la matriz ModelView, y su
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 hace por Software, así que no se quejen si en su equipo las
evitar que Windows se tome esta molestia, de cualquier modo animaciones se ven demasiado lentas, lo más seguro es que su
nosotros mismos estamos dibujando sobre esta ventana lo que Hardware sea el culpable.
se nos va antojando, así que para que perder ese valioso Bueno, hasta aquí llegamos en esta ocasión, porque parece que
tiempo. tenemos suficiente con que entretenernos por ahora, y todavía
Para evitar que Windows borre el contenido del formulario mucho por estudiar en el futuro... Hasta Pronto.
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),

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
En este caso cualquier coordenada que definamos como
Líneas con OpenGL en dos (x,y,z), será proyectada sobre el plano de visualización z=0
dimensiones 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
Proyección Ortogonal 2D. Construcción de rectángulo de visualización debemos definirlas mediante la
gráficos con Líneas. Antializado. instrucción glOrtho2D(), con la cual declaramos los valores de 2
Algoritmos recursivos de Fractales. coordenadas que determinarán el área del rectángulo, es decir,
la esquina superior izquierda y la inferior derecha.
OpenGL no se utiliza solamente para generar impresionantes
gráficos 3D, sino como veremos a continuación también
¿Qué son los Fractales?
puede utilizarse para hacer gráficas en 2 dimensiones. En esta Para comprender lo que es un fractal, hagamos un pequeño
ocasión trabajaremos con los diferentes tipos de líneas que ejercicio: tomemos una hoja de papel y dibujemos sobre ella
nos ofrece OpenGL; y a fin de hacer este tema todavía mas un segmento rectilíneo. La geometría Euclidiana nos dice que
interesante veremos un extra bastante útil e impresionante: se trata de una recta y por tanto tiene una sola dimensión (su
Los Fractales, un concepto matemático muy utilizado en la longitud). Ahora extendamos el segmento rectilíneo haciendo
graficación por computadora. 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
Visualización 2D
intuición nos dice que si la línea llena completamente el plano
Aunque aquí tratemos el tema de la visualización 2D en debe tener más de una dimensión, si bien no llegará a tener
OpenGL, es importante recordar que este es un API gráfico dos. Esto implica que nuestro dibujo debe tener una
de 3 dimensiones; así que si queremos utilizarlo para hacer dimensión fraccionaria.
representaciones 2D tenemos que hacer un pequeño truco. Este concepto tan simple comenzó toda una revolución en las
Como ustedes han de suponer lo que hemos de hacer es matemáticas. Varios matemáticos de fines del siglo XIX y
graficar todo lo que deseemos en un plano, para obtener así la principios del XX propusieron la existencia de una dimensión
apariencia de que estamos trabajando con 2 dimensiones fraccionaria y no debiera sorprendernos que sus colegas los
aunque realmente sigamos trabajando en 3. tildaran de locos, pues esa idea aparentemente tan aberrante
Entonces la visualización 2D se basa en tomar un área atentaba contra la noción de dimensiones enteras que todos
rectangular de nuestro mundo 3D y transferir su contenido a conocemos. Hubieron de pasar cerca de 50 años para que un
nuestro despliegue. El rectángulo de visualización está en el matemático se tomara en serio tan aventuradas teorías. En
plano z=0. Por default si no se específica el volumen de 1975 el Dr. Benoît Mandelbrot, acuñó un nombre para estas
visualización se construye un rectángulo de 2x2, donde las figuras que tienen una dimensión fraccionaria. Las llamó
coordenadas de este rectángulo quedan como: ((-1.0,-1.0),(- fractales, término que derivó del adjetivo latino fractus, que
1.0,1.0),(1.0,-1.0),(1.0,1.0)), estas coordenadas, claro, en el significa "irregular" o "interrumpido". Mandelbrot afirma
plano z=0. 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
Figura 1 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 Digital) entre otros, se limitan solo a encontrar aquellos pixeles
pantalla, trasladémoslo, rotémoslo y ajustemos su escala que puedan representar a la línea en cuestión, pero no se
aleatoriamente. Esto nos permitirá obtener imágenes ocupan de la presentación que esta tendrá para el usuario. En
relativamente complejas a partir de patrones sumamente OpenGL existe un concepto muy interesante al respecto
simples, si iteramos un número adecuado de veces. llamado antialiazing o antializado. El antializado consiste
Todos estos conceptos aunque de entrada parezcan complejos, precisamente en eliminar esos escalonamientos que se
veremos que su aplicación no lo es tanto y cómo podemos presentan tanto en las líneas como en los contornos de las
nosotros usar fractales para representar todo un paisaje, con figuras a representar para obtener así figuras más realistas, que
objetos de la naturaleza de una manera muy sencilla y simple. 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 ¡Vamos al Código!
sencillas, Líneas en ciclo y Líneas con patrones. Como Bueno, vamos directamente al ejemplo que aquí presentamos.
habíamos visto en el número anterior la definición de figuras Echemos una mirada al Listado 1, y a ver que podemos
en OpenGL se hace con las instrucciones GLBegin() y GLEnd(), aprender de esto. Por ahora pasemos por alto los
donde a GLBegin() se le pasa como parámetro el tipo de figura procedimientos que hemos definido para dibujar los fractales y
que nos interesa dibujar con los vértices definidos entre estas centrémonos en los eventos de los objetos.
instrucciones (el ejemplo pasado lo hicimos solo con puntos Como pueden darse cuenta la estructura de este programa es
usando GL_POINTS), así que ahora estudiaremos como hacer muy similar a la del programa que estudiamos en el número
líneas con ellas. anterior, ya que seguimos usando la misma versi ón de
Para hacer líneas sencillas usamos GL_LINES, con esta OpenGL.Pas, y el método de inicialización de contextos no
instrucción como parámetro para GLBegin() indicamos a OpenGL cambia para nada.
que dibuje una línea que una cada dos vértices que nosotros Las diferencias las empezamos a encontrar en el evento
definamos. Algo muy similar a cuando trabajábamos con el OnPaint(). Como vemos en la instrucción GLClear(), solo pasamos
Objeto Canvas y usábamos las instrucciones MoveTo(x,y) y como parámetro el Buffer de Color (GL_COLOR_BUFFER_BIT) y ya
LineTo(x,y), ¿lo recuerdan?... ¡ah... que tiempos aquellos! ... no el buffer de profundidad (GL_DEPTH_BUFFER_BIT) como en el
Las líneas en ciclo nos sirven para definir contornos. Para ejemplo pasado; la razón es que ahora sólo trabajaremos en 2
hacer este tipo de líneas usamos GL_LINE_LOOP, cuando dimensiones, así que no hay relación de profundidad entre las
utilizamos esta instrucción unimos todos los vértices que figuras que dibujaremos, por lo que no necesitamos limpiar
definimos con una misma línea en el orden en que hayan sido este buffer.
estos definidos, y al mismo tiempo se unen el último vértice Luego encontramos la instrucción gluOrtho2D (-1.0, 1.0, 0.0, 1.5); con
definido con el primero, cerrando así la figura. Esto es muy útil la que definimos un rectángulo de visualización cuya esquina
para dibujar contornos de polígonos (ya sean regulares o superior izquierda es (-1.0,1.5) y la esquina inferior derecha es
irregulares), por ejemplo. (1.0,0.0).
¿Recuerdan cuando trabajábamos con el Canvas que podíamos Ahora aquí vienen unas instrucciones que tal vez les resulten
cambiar el estilo de la pluma para dibujar líneas punteadas o nuevas en este momento glPushMatrix() y glPopMatrix(). Suponemos
con diversos patrones?. Pues en OpenGL también se pueden que todos aquí hemos trabajado alguna vez con estructuras de
declarar varios patrones para dibujar líneas. Para ello usamos datos de tipo Pila, y que entendemos los procedimientos de
en nuestro ejemplo la instrucci ón GL_LINE_STRIP como inserción y eliminación de elementos de esta estructura.
parámetro, éste permite escribir una secuencia de líneas unidas Bueno, pues estas instrucciones trabajan con una pila de
vértice a vértice, pero a diferencia de GL_LINE_LOOP el último matrices y sirven para hacer respaldos de la matriz de
vértice no se une con el primero. Ya veremos sobre la marcha transformaciones que luego podemos recuperar. La finalidad
como declaramos los patrones a utilizar. de hacer estos respaldos es para poder hacer ciertas
transformaciones sobre figuras específicas sin que éstas
Calidad en el trazado de líneas afecten al resto de las figuras en nuestra escena. Esto es
verdaderamente útil como veremos más adelante.
También, como en el número anterior vimos la instrucción
Delimitemos ahora lo que pretendemos realizar con este
GLPointSize()
para hacer más grande el tamaño de los puntos que
código. Bien, pues lo que dibujaremos es: “un paisaje en el
dibujamos, en el caso de las l íneas podemos hacer que
campo durante una noche de Eclipse de Luna ”, ¿qué les
OpenGL las dibuje más gruesas de cómo las presenta
parece?, no me negarán que es un título bastante artístico para
originalmente con la instrucción glLineWidth() , pasándole como
nuestra “obra de arte”; y todo usando solo líneas.
parámetro un número entero que indique el número de pixeles
Nuestro paisaje estará compuesto por árboles, estrellas, y la
que ocupará el ancho de las líneas a dibujar.
luna eclipsada, por supuesto. As í que necesitamos
Volviendo a los añejos recuerdos del Canvas... ¿Recuerdan
procedimientos que dibujen cada una de estas figuras. Tanto
como cuando dibujábamos líneas con cierto ángulo diagonal,
los árboles como las estrellas las construiremos usando
se dibujaban en forma de escalerita?; esto se debe en gran
fractales con procedimientos recursivos.
medida a que los algoritmos clásicos de trazado de líneas como
Los fractales que usaremos serán del tipo de los sistemas S-
el método de Bresenham ó el del DDA (Análisis Diferencial
>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. Un detalle más: hemos puesto el trazado de la línea punteada
En nuestro caso, los elementos serán Líneas y los subsistemas entre dos sentencias de push y pop de atributos. ¿Recuerdan
serán llamadas recursivas al mismo procedimiento. cuando en el artículo del número anterior dijimos que OpenGL
Basándonos en este tipo de sistemas podemos construir es una máquina de estados?, en futuros artículos veremos con
diversos tipos de figuras de la naturaleza, como árboles, más detalle estas operaciones de push y pop para el caso de los
estrellas, piedras, nubes, e incluso podr íamos llegar a atributos, pero brevemente lo que estamos haciendo con la
representar el sistema solar. primera sentencia glPushAttrib(GL_LINE_BIT) es guardar en una pila
Analicemos primeramente el procedimiento para construir el valor actual de la variable de estado GL_LINE_BIT (esta variable
árboles. La llamada a este procedimiento tiene los siguientes decide el patrón de punteado), entonces podemos modificar
parámetros: Arbol(x,y,t,teta,w:real); la idea con este procedimiento es GL_LINE_BIT con nuestra sentencia glLineStipple() y cuando hemos
dibujar a partir del punto (x,y), un segmento de línea de acabado llamamos a glPopAttrib() que devuelve el valor antiguo de
longitud t e inclinación teta. Y después volver a llamar la variable GL_LINE_BIT. Este mecanismo es una manera efectiva
recursivamente a este procedimiento, pasando como de modificar las variables de estado de OpenGL localmente. Si
parámetros el extremo final del segmento de recta generado y no lo hacemos así entonces todas las líneas dibujadas después
los parámetros t y teta con valores modificados para hacer de glLineStipple() tendrían el mismo patrón de punteado y
simétrica la figura. A fin de cuentas lo que hacemos es dibujar estaríamos forzados a declarar un patrón con glLineStipple() para
un arbolito más pequeño al final de cada segmento de recta. cada línea que trazásemos en nuestra aplicación. Push y pop
Observen como ahora utilizamos glVertex2f() para declarar nos evitan este molesto trabajo.
nuestros vértices ya que trabajamos en dos dimensiones. El
parámetro w nos servirá para determinar el tipo de árbol que ¿Y la luna y el antializado?
deseamos dibujar, pues como veremos, este simple Bien, pues la luna la construiremos como si fuera un polígono
procedimiento nos permite dibujar Cipreses, Jacarandas y con GL_LINE_LOOP, recuerden que a final de cuentas un circulo
Pinos. ¿A poco no es sorprendente?. podemos definirlo como un polígono que tiene muchos lados,
Algo similar pasa con el procedimiento para dibujar las tantos que no se llegan a distinguir. En nuestro caso hacemos
estrellas, solo que aquí todos los segmentos de recta tienen un uno de 100 lados. Como lo que pretendemos es dibujar una
mismo origen, el centro de la estrella. Así
que lo que variamos es el ángulo de
inclinación de cada segmento para obtener
así la figura final.

Las líneas punteadas


Siguiendo con el análisis sobre el
procedimiento Star(), que dibuja las estrellas,
vemos que aquí es donde usamos los
patrones con las líneas.
La función glLineStipple() especifica el patrón
usado para el punteado, por ejemplo, si
usáramos el patrón $AAAA (este es un
número en hexadecimal), en binario este
número es 1000100010001000, OpenGL
interpreta esto dibujando 3 bits apagados, 1
bit encendido, 3 bits apagados, 1 bit
encendido, 3 bits apagados, 1 bit encendido
y por último 3 bits apagados y 1 encendido.
El patrón se lee hacia atrás porque los bits
de menor orden se usan primero.
GlLineStipple() tiene dos parámetros, el patrón Figura 2
de punteado que debe ser un n úmero
hexadecimal y un factor entero que sirve para escalar este luna eclipsada, entonces solo debemos preocuparnos de
patrón; con un factor de 3 nuestra línea punteada mostraría 9 dibujar el contorno de esta luna, y aprovechamos que el color
bits apagados, 3 bits encendidos, 9 bits apagados, 3 bits con el que borramos la pizarra es el negro para evitar dibujar
encendidos, 9 bits apagados, 3 bits encendidos y por último 9 el resto.
bits apagados y 3 bits encendidos. Jugando con factores y Ahora, como decíamos, dibujar tantas líneas en tan diferentes
patrones binarios, uno puede dibujar todo tipo de l íneas ángulos ocasiona que nuestro circulo no luzca bien definido,
punteadas complicadas. En nuestro ejemplo usamos el patrón por lo que aquí si es conveniente utilizar el antializado.
$5555, con un factor de 1, lo que nos da un patrón uniforme de El antializado para el caso de las líneas se habilita con la
1 pixel apagado por 1 encendido. ¡Hagan cuentas!. 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 árboles pequeños, como es que se transforman las figuras
interesa dibujar toda nuestra escena con antializado, si no solo fractales para generar los diferentes tipos de árboles que
la luna. Pueden probar que sucede cuando ignoran estas líneas tenemos en nuestra escena, modificando el parámetro w que
de código, ya verán que así no parece luna. habíamos mencionado. Estas animaciones están controladas
nuevamente por un componente Timer.
Para terminar un poco de animación Por ahora eso es todo, ya tenemos nuestra escena construida
(Ver Figura 2); que se diviertan experimentando con las líneas
Por último, puesto que ya hemos utilizado los diferentes tipos
y los fractales. Recuerden que pueden descargar el código
de líneas disponibles en OpenGL, vamos a agregar un poco de
fuente de este programa y las unidades extras que utilizamos
animación a nuestra escena. Si mal no recordamos las estrellas
directamente desde el sitio web de la revista. Hasta Pronto...
“titilan”, por lo que usamos una variable para simular este
efecto, así como una variable adicional para mostrar con 2

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
escalar a partir de la multiplicación de 2 vectores.
Transformaciones Lineales en Las matrices por su parte pueden ser multiplicadas por otras
OpenGL 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
Las transformaciones lineales, aunque los respectivos renglones y columnas de los argumentos como
pudieran parecer un concepto un poco se muestra en la Figura 2, obteniendo los valores escalares de
oscuro, no lo son tanto, y son la base para estas multiplicaciones de vectores, es decir que cuando
multiplicamos una matriz por otra matriz, lo que obtenemos es
poder generar animaciones con API’s
una nueva matriz formada por estos valores escalares. Ahora
gráficas. solo hay que tener en cuenta que las dimensiones de ambos
argumentos deben de corresponder para poder hacer esta
Bien, esta vez abordaremos el tema de las transformaciones operación, es decir que el número de columnas de la primera
lineales; ciertamente es un tanto difícil hablar de términos matriz corresponda con el número de renglones de la segunda,
matemáticos y de geometría de manera coloquial, sin embargo ¿Lo ven?, de otro modo no se podrían hacer las operaciones
aquí trataremos de hacerlo lo más ligero posible para que todos sobre los vectores.
nos enteremos, aunque de cualquier modo es recomendable Y para multiplicar matrices por vectores lo que hacemos es
que cualquiera que se vea interesado en estos temas profundice tratar el vector en forma de renglón y multiplicarlo por cada
en los libros de Álgebra Lineal, donde muchas dudas una de las columnas de la matriz, como se muestra en la figura
seguramente serán resueltas (¿o tal vez ampliadas?). 3. Así que aquí podemos apreciar que al multiplicar un vector

Empecemos con la
teoría...
Bueno, creo que deberemos
empezar explicando el escabroso
procedimiento mediante el cual se Figura 3. Multiplicación de un Vector por una matriz
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
Vectores y las operaciones aritméticas aplicables a estas
Bueno, ¿y eso qué?
estructuras?, bien, pues ahora vamos a sumergirnos un poco en Ahora, a nosotros lo que nos interesa es multiplicar vectores
lo que respecta a estas operaciones. de 3 valores por matrices de 3*3 para obtener las
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 Figura 6. Rotación de un cuerpo

| X'| = | X |* ( | A |*| B | ) transformación


tanto de
Donde X’ representa al vector transformado, X al vector Traslación,
original, y A y B son R o t a c i ó n ,
2 matrices de Escalamiento y
transformación Reflexión.
distintas. Ahora, el orden
Y si en el caso en como se
anterior tomamos: apliquen estas
operaciones
| K | = | A |*| B | importa y
mucho...Como
entonces podemos bien sabemos en
Figura 7. Escalamiento de un cuerpo
sustituir K en la la aritm ética
segunda expresión común la multiplicación tiene la propiedad conmutativa, es
como: decir, que el orden de los factores no altera el producto; sin
embargo, en cuanto a la multiplicación de matrices de
| X'| = | X |*| K | transformación lineal, no se aplica esta propiedad.
Así que obtendremos diferentes resultados dependiendo de
¿Lo ven?... Podemos cual transformación apliquemos primero. La figura 8 muestra
aplicar primero todas claramente los efectos obtenidos de aplicar las
Figura 5. Traslación de un cuerpo
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
estas primitivas y cual es su comportamiento.
el número de grados que nos interesa rotar el cuerpo o la Hay algunos apuntes que hacer también al respecto pero eso lo
escena, y los otros 3 determinan el nivel en que se verá afectada reservaremos para otra ocasión, por ahora basta con saber que
la operación respecto a cada uno de los 3 ejes X, Y y Z. Por existen y que podemos hacer uso de ellas.
ejemplo, si nos interesara hacer una rotación de 30 grados solo Ahora veamos un ejemplo para estudiar de manera práctica las
sobre el eje X, pasaríamos un 30 en el primer parámetro, un 1 transformaciones lineales junto con algunas cosas nuevas.
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 Vamos al ejemplo!...
trasladar la figura en cada uno de los 3 ejes. También podemos Demos un vistazo al Listado 1, y veamos que podemos
pasar como parámetros valores negativos, y así obtener aprender de éste.
traslaciones en sentidos contrarios. Como podemos ver, hemos definido al principio un
Y el escalamiento se define con glScale...(); este también recibe 3 procedimiento que se encargará de dibujar el cubo que
parámetros que definen el grado de escalamiento que deseamos mostraremos en nuestra escena. Para esto lo que hicimos fue
aplicar a cada uno de los 3 ejes. Si en este caso usamos glScalef(); utilizar la primitiva GL_QUADS, la cual lo que hace es dibujar un
podemos pasar valores fraccionarios como parámetros y poder cuadro con cada 4 vértices que nosotros definimos. Y como ven
así hacer escalamientos más exactos. también, hemos definido un color diferente para cada uno de los
Recordemos que las operaciones deben aplicarse antes de la 8 vértices que definen las aristas de nuestro cubo. Debido a que
definición de los vértices de las figuras que se verán afectadas no hemos definido ningún modelado de despliegue para nuestra
por estas transformaciones, para que así estos vectores sean escena (todavía no llegamos a tanto), lo que se hace es tomar el
multiplicados por esta matriz ya definida. default, y hacer un degradado entre los vértices de la figura, por
Si deseamos aplicar una transformación solo a un determinado eso es que vemos que las caras de esta se encuentran iluminadas
cuerpo de nuestra escena, debemos definir primero las por un degradado de colores bastante llamativo. Por ahora
trasformaciones que afectarán a toda la escena y los vértices hemos definido la construcción de este cubo en un
que no se verán afectados por la transformación nueva, y luego procedimiento separado, pero más adelante veremos como usar
salvar la matriz en una pila con la instrucci ón glPushMatrix(); una herramienta bastante útil llamada las “listas de despliegue”,
Aplicar la transformación nueva, definir los vértices de la las cuales tienen una función semejante a lo que hacemos con
figura(s) afectada(s), y posteriormente, si nos interesa seguir este procedimiento.
definiendo vértices que no deberían ser afectados por esta En los ejemplos anteriores habíamos usado a los formularios
transformación, antes recuperar el estado anterior de la matriz como lienzos, ¿recuerdan?...pero pudiera suceder que no nos
desde la pila con glPopMatrix(); . Estas instrucciones que manipulan interesara utilizar toda la superficie del formulario para
la pila de matrices son muy útiles, y aunque cuesta un poco desplegar nuestras escenas, sino solo una porción de éste, y el
entender como funcionan en un principio, son muy prácticas resto del espacio utilizarlo para colocar controles que
una vez que uno ha captado la idea. manipulen la escena, ¿no?...Bueno, pues esta vez veremos
como hacerlo. Para este ejemplo tomaremos como lienzo a un
objeto de tipo Tpanel.
Las primitivas geométricas
Como ya no usaremos la versión de OpenGL.pas de Lischke,
Hasta ahora en los ejemplos que habíamos visto solo habíamos ahora nos sumergiremos un poco más en las profundidades de
usado líneas y puntos para crear nuestras figuras, pero en cómo inicializamos un contexto “a pie”, y entre las cosas que
OpenGL existen muchísimas primitivas de figuras que ya debemos hacer de este modo es el formato de pixeles que
iremos viendo a su tiempo. La figura 9 nos muestra algunas de

17
Transformaciones Lineales en OpenGL

tusaremos en el control (en este caso el Panel). Para esto hemos el Panel, y dentro de este procedimiento, hemos definido las
utilizado el procedimiento: procedure setupPixelFormat(DC:HDC) el cual transformaciones de rotación, traslación y escalamiento en
nos servirá para este formato. Como ven hemos definido una función de propiedades de 3 componentes de tipo TtrackBar, las
constante pfd de tipo TPIXELFORMATDESCRIPTOR la cual contendrá cuales se encargan de volver a dibujar la escena cada vez que
toda la información acerca del formato que elijamos para sus posiciones cambian, ya que tenemos asociado este
nuestro control OpenGL, y esa es la que pasamos como procedimiento al evento OnChange de estas componentes.
parámetro a la función ChoosePixelFormat(); la cual recibe además el En este ejemplo, hacemos rotaciones sobre el eje Y,
manejador del control donde desplegaremos la escena. traslaciones sobre el eje Z, y escalamientos sobre el eje X. Esto
Sobre los valores que componen el tipo de datos con el fin de poder apreciar como es que funcionan estas
TPIXELFORMATDESCRIPTOR no hablaremos mucho por ahora, ya transformaciones. Y el orden en como aplicamos las
que a estas alturas hay cosas que todavía ni siquiera hemos transformaciones es: primero la traslación, luego la rotación y
mencionado, más adelante hablaremos de temas como los por último el escalamiento.
diferentes buffer’s, así como de las paletas de colores a utilizar. El resto del programa no difiere demasiado de los que ya
De igual manera que en los anteriores ejemplos, el contexto lo hemos visto anteriormente, así que no creo que requiera de
inicializamos cuando creamos la forma y lo liberamos al mayores explicaciones. Además, los comentarios en el código
destruirla. explican cada línea a detalle. Como quiera aún tenemos mucho
Como pueden observar tenemos un procedimiento llamado que aprender en el futuro.
dibuja(), el cual es el que se encarga de vaciar el frame-buffer en 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
tridimensionales sin demasiado esfuerzo, por ahora nos
Interacción de gráficos con limitaremos a usar los predefinidos de OpenGL.
dispositivos
Veamos el ejemplo, ¿cómo respondemos a
Cómo podemos obtener programas gráficos los eventos?
interactivos de manera simple usando Bien, en el ejemplo del número anterior veíamos como
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 é
derivado de la clase TCustomControl.
Para esta ocasión hemos preparado un pequeño articulo sobre De todos es sabido que Delphi es un poderoso lenguaje
cómo podemos crear programas que resulten interactivos para orientado a objetos, y pues estaría mal que nosotros no
el usuario, es decir, hacer a este participar en la ejecución, y no utilizáramos esta gran ventaja a la hora de programar gráficos,
limitarlo solo a observar los resultados de todos los cálculos ¿no?. Supongamos que lo que ahora nos interesa es poder
matemáticos internos que deben realizarse para presentar desplegar nuestra escena 3D sobre una porción del formulario,
gráficos en tres dimensiones. y responder a los eventos del ratón solo en esa determinada
¿Familiar, no?... Este es el concepto que se utiliza región. Una solución sería usar un componente Tpanel como en
frecuentemente en los juegos de video para computadora... Son el ejemplo anterior, pero bueno, vamos a hacer un componente
muy comunes hoy en d ía y algunos de ellos son especial para lo que ahora nos interesa específicamente.
verdaderamente sorprendentes por la calidad de imágenes que Como pueden ver en el “Listado 1”, hemos definido una clase
muestran y lo interesantes que suelen ser. Pues bien, ahora llamada GLControl derivada de TCustomControl, la cual nos
empezaremos con un paso elemental, que es el como servirá precisamente como nuestro lienzo y controlador de
interactuar con los eventos causados por el usuario de nuestra eventos.
aplicación, esta vez veremos como responder a los eventos del Como pueden observar, en el evento OnCreate del formulario
ratón y el teclado. 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.
¿Cómo hacemos cuerpos complejos en
Como pueden ver volvemos a hacer uso de la funci ón
OpenGL? SetupPixelFormat(), la cual usaremos siempre que creemos un
Bien, OpenGL provee además de las primitivas básicas que ya contexto “a medida”. También usamos dos procedimientos
hemos visto hasta ahora, algunos cuerpos complejos que suelen extras: Draw(), el cual se encarga de dibujar y mostrar la escena,
ser de utilidad en algunas ocasiones. La ventaja de estos y GLInit() que se encarga de configurar la perspectiva y el
cuerpos es que la manera en como son construidos y mostrados ambiente de la escena. Cabe señalar aquí, que en este ejemplo
al usuario está muy optimizada, para hacer las aplicaciones tuvimos que definir algunas luces a fin de que se pudieran
bastante rápidas. Además de que nos evitan estar ideando la distinguir con claridad los cuerpos que habíamos creado, pero
manera de cómo crear procedimientos para construir estos ya tocaremos el tema de la iluminación más a fondo en otra
objetos que también suelen ser de uso bastante común. ocasión.
Los que en esta ocasión estudiaremos se encuentran en la
librería GLU32.DLL. ¿Recuerdan cuando en algún articulo Bueno, pero ¿cómo hago que un cuerpo se
pasado mencionábamos que en esta librería se encontraban
algunas figuras ya predefinidas?. Pues bien, as í es, aquí
mueva tridimensionalmente de acuerdo al
podemos encontrar procedimientos para construir: cilindros, movimiento del ratón?
conos (que en realidad vienen a ser una especialización de un Bien, para eso debemos utilizar algunas variables auxiliares
cilindro), discos y esferas. para lograr el efecto.
Los nombres de los procedimientos que crean estos cuerpos Primeramente aclaremos que lo que haremos aquí será solo
comienzan con las siglas glu, y reciben diferentes parámetros simular la rotación de un cuerpo respecto a dos ejes
cada uno, dependiendo del tipo de cuerpo que deseamos dimensionales, es decir solamente rotaciones en los ejes X e Y;
construir. Por ejemplo, para construir un cilindro lo que sin embargo puede quedárseles de tarea como podríamos
indicamos como parámetros, son: la altura que deberá tener además trasladar el cuerpo respecto al eje Z (para obtener
este cilindro, así como la longitud de los 2 radios que lo efectos de zoom) arrastrando por ejemplo el ratón con el botón
componen. Y para el caso de las esferas, debemos indicar el derecho presionado. De cualquier modo aquí simulamos ese
radio, y el número de paralelos y meridianos que la efecto con un TTrackBar como en el ejemplo del n úmero
compondrán (dependiendo de el número de paralelos y pasado.
meridianos que definamos será la calidad en la definición de Usaremos dos variables para controlar los ángulos que se verán
nuestra esfera). afectados para cada uno de los ejes, otras dos para controlar el
Para cada uno de los objetos que creemos con las funciones de desplazamiento del movimiento con respecto a estos ángulos, y
glu, debemos asignar un identificador de tipo gluQuadricObj, el cual por último otras dos para determinar el punto de inicio del
es en realidad un apuntador a una zona de memoria donde se movimiento. ¿Sencillo, no?. Pensemos en que estaremos
construirá el objeto en cuestión. No es tan complicado, ya lo desplazando el ratón sobre una superficie bidimensional, al cual
veremos mas claro en el ejemplo. le daremos un comportamiento tridimensional, algo que en
En otra ocasión veremos como crear nuestros propios cuerpos 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: Figura 1.- Nuestra aplicación en ejecución
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 Consideraciones adicionales con otros
otro lado, la instrucción glRotatef(betha+dy,1.0,0.0,0.0); hace lo propio dispositivos
con el otro eje. Y esto produce a final de cuentas el efecto de Hasta aquí ya hemos visto como podemos interactuar de una
que los cuerpos definidos parecen “seguir” el movimiento del manera muy simple con el ratón y el teclado, sin embargo
ratón sobre nuestro control. existen en la red diversos componentes que permiten también
Debemos tener en cuenta que todo esto lo hemos hecho para recibir eventos desde un Joystick por ejemplo, en cuyo caso, no
poder tener una referencia en dos ejes para un solo cuerpo, pero existe mucha diferencia con lo visto hasta ahora.
con un poco más de ciencia podríamos generalizar el método Y en caso de que nos interesara guardar una escena como un
para aplicarlo a todos los objetos de una escena, o solamente a archivo de imagen, ó bien imprimirla tampoco hay mayor
unos cuantos de ellos. problema, ya que basta con usar la famosa función CopyRect() del
Del mismo modo podríamos programar procedimientos que objeto Canvas asociado con el control sobre el cual
respondieran a eventos generados por el teclado asignándolos a desplegamos nuestra escena y con ese rectángulo podemos
los eventos OnKeyDown(), OnKeyUp() y OnKeyPress() de nuestro control. hacer lo que se nos pegue en gana.
Cabe señalar que en nuestro ejemplo también podemos obtener Bueno, hasta aquí llegamos por esta vez, que tengan suerte, y
respuesta a las teclas de cursor de nuestro teclado, siempre y que aprendan mucho de esto
cuando el TrackBar tenga el foco en nuestra aplicación. Hasta Pronto.
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.

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
que finalmente aparecerá en nuestra pantalla.
Tipos de proyecciones y
manejo de la cámara en La proyección Ortográfica
OpenGL 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
Como pasar de coordenadas 3D a ser paralelas entre sí. Este es una tipo de proyección
coordenadas de dos dimensiones, y como relativamente sencilla, que suele ser bastante útil en ciertos
interactuar con la cámara. casos. La figura 1 muestra gráficamente como se construyen
las líneas paralelas en esta proyección, podemos ver como se

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.
Figura 1. Proyección Ortográfica
¿Cómo pasar de coordenadas 3D a obtiene un dibujo de una de las caras de nuestro cubo en
coordenadas 2D en nuestra ventana? nuestro plano de proyección y así obtenemos lo que veremos
en pantalla.
Esta es una muy buena pregunta, ¿Cómo hacer que
Este tipo de proyección no preserva las dimensiones reales
aparezcan en nuestra ventana, que es 2D, puntos que en
de los objetos según la distancia hasta ellos. Con eso
realidad deberán tener una relación de profundidad entre
queremos decir que aún acercándose ó alejándose de ellos
sí?... Bien, pues la respuesta no es tan compleja si la
no se producen cambios de tamaño con lo cual el realismo
pensamos un poquito, lo que hay que hacer es proyectar toda
no es total. Se utiliza tradicionalmente en proyectos de
nuestra geometría en un plano, al que llamaremos Plano de
ingeniería del tipo de programas CAD/CAM. Quién no ha
Proyección.
visto una herramienta de modelación 3D (AutoCAD,
Como todos los planos, este representa una región 2D, y por
3DSMax, etc...) sin las 3 correspondientes vistas
convención suele situarse en Z = 0, es decir en el plano XY.
ortográficas desde ambos laterales y desde arriba. La cuarta
Y primero debemos hacer la proyección para después hacer
vista suele ser una perspectiva que comentaremos a
las conversiones de tipos de coordenadas. Entendamos esto
continuación.
de las conversiones de tipos:
Los parámetros a especificar en este caso son las
Cuando trabajamos con coordenadas 3D, es común que las
dimensiones de una caja (Xmin, Xmax, Ymin, Ymax, Zmin,
definamos como números de punto flotante, y es
Zmax). A los valores MAX y MIN también se les denomina
perfectamente válido hacer esto, si no ¡imagínense como
FAR o BACK y NEAR o FRONT. ¿Qué por qué una caja?...
funcionarían programas de diseño como por ejemplo
pues porque también hay que definir nuestro campo visual,
Autocad usando solo aritmética entera!...Sin embargo,
donde campo visual es la "cantidad" de mundo que la
nuestro monitor está mapeado en pixeles que tienen cada
cámara alcanza a ver. Porque imagínense que ustedes tienen
uno dos coordenadas enteras, por esta razón es que debemos
“Ojo de águila” y alcanzan a ver mucho más de lo que un
hacer una conversión que elimine los decimales sin que
topo medio ciego que apenas distingue su propia nariz, ¡Ah
estos dejen de ser significativos en la proyección. Por eso es
verdad!... pues esta caja determina qué tan buena vista
que hay que convertir forzosamente al final a enteros.
tendremos en nuestra aplicación.
Existen muchos tipos de proyecciones y podríamos escribir
En OpenGL la podemos definir de la siguiente forma:
libros completos al respecto, pero aquí nos limitaremos a ver
Cargamos la matriz de proyección, la inicializamos y
solo dos que resultan algo significativas para nuestros
usamos glOrtho(x_min, x_max, y_min, y_max, z_min, z_max); ó bien:
propósitos, la proyección Ortográfica, y la proyección en
gluOrtho2D(x_min, x_max, y_min, y_max); si se aceptan los valores por
Perspectiva. Ambas están clasificadas dentro de las
defecto de Zmin = -1.0 y Zmax = 1.0.
proyecciones planares.
Ya habíamos hecho alguna vez en artículos anteriores una
Y básicamente ambas consisten en definir una línea de
proyección Ortogonal, cuando hablábamos de cómo hacer
visión que va desde el observador hasta el objeto observado,
gráficas en 2D y en esa ocasi ón usamos gluOrtho2D()...
y trazar una serie de líneas que cortarán el plano de
¡¡revisen sus apuntes!!
proyección que hayamos definido generando así la figura 2D

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

La proyección en Perspectiva 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
Esta es la que definitivamente utilizaremos para dotar del
de visualización finito con unas fronteras bien marcadas.
mayor realismo a nuestras aplicaciones. Las proyecciones
Todo aquello que no se encuentre dentro del volumen será
perspectiva preservan las dimensiones reales de los objetos si
rechazado y no proyectado dado que no debe verse. Para
nos acercamos ó alejamos de ellos. Por tanto el efecto visual
tenerlo más claro pensemos en nuestros propios ojos. ¿Acaso
es justo el que necesitamos en casos de apariencia real.
vemos todo a nuestro alrededor?...no...tenemos una cierta
Veamos la figura 2 para tener una mejor idea de cómo
apertura visual, de unos 60º a 80º y más allá de eso nada de
funciona.
nada, solo conjeturas.
No somos como las palomas con los ojos en los laterales de
la cabeza o como una gran lente angular de una cámara. De
hecho tan sólo variando la apertura visual (FOV)
observaremos como es más o menos la cantidad de escena
que entra en pantalla.

Ahora vamos con la camara...


La cámara son nuestros ojos virtuales. Todo lo que ella vea
será proyectado, discretizado y finalmente mostrado en la
ventana de nuestra aplicación. Podemos imaginar que de la
cámara emana el volumen de visualización de forma que se
traslada con ella. Los parámetros a definir en cuanto a la
cámara son:
Posición XYZ en el mundo 3D. Al igual que un objeto
cualquiera, la cámara debe posicionarse. En un juego arcade
Figura 2. Proyección en Perspectiva 3D típico la cámara nos da la visión frontal del mundo y se
En la figura tenemos una proyección perspectiva con un sólo va moviendo a nuestro antojo con las teclas o el ratón. Cada
COP o punto de fuga. Todas las líneas ó proyectores vez que modificamos la posición varían las coordenadas XYZ
emanan de él y se dirigen hasta el objeto intersectando el de cámara. Orientación. Una vez situada debe orientarse.
plano de proyección. Como podemos observar en este caso Osea, yo puedo estar quieto pero girar la cabeza y mirar
los proyectores no son paralelos entre ellos. hacía donde me venga en gana. Dirección de "AT". Define
En OpenGL definimos esta proyección cargando la matriz de hacia dónde estoy mirando, a qué punto concretamente.
proyección, inicializandola, y usando gluPerspective( FOV en grados, En OpenGL definimos la c ámara con:
glulookAt(eyeX,eyeY,eyeZ,atX,atY,atZ,upX,upY,upZ);
Relación de Aspecto, z_near, z_far); donde los parámetros que hay
que pasarle a este método son los siguientes: Esta es la función que determina dónde y cómo está
El FOV se refiere al ángulo de abertura inicial, que dispuesta la cámara. Cuidado que la posición de la cámara no
determinará nuestro campo de visión. La relación de aspecto tiene nada que ver con el tipo de proyección que hayamos
es el cociente entre la anchura y la altura del plano de definido. La proyección se define sólo una vez, típicamente
proyección deseado, es decir de nuestra ventana, ó el control al principio del programa en una función inicializadora,
que usaremos para desplegar nuestra escena. Y los valores mientras que la cámara se mueve continuamente según nos
Near y Far son los mismos que en la proyección ortográfica, interese y más en un juego 3D !!!. Añadir a esto que la matriz
es decir que determinan qué tan lejos y tan cerca seremos que se modifica al llamar a esta función no tiene que ser
GL_PROJECTION sino GL_MODELVIEW. OpenGL calculará todas las
capaces de ver.
Podemos definir también una proyección perspectiva usando transformaciones que aplicará al mundo 3D para que
la función: glFrustrum(x_min, x_max, y_min, y_max, z_min, z_max);. En este manteniendo la cámara en el origen de coordenadas y
caso OpenGL calculará el "frustrum piramidal", es decir, el enfocada en la dirección negativa de las Z's, nos dé la
volumen de visualización perspectiva más idóneo. Son sensación de que lo estamos viendo todo desde un cierto
simplemente dos maneras distintas de acabar creando lo lugar. Para ello debemos recordar siempre activar esta matriz
mismo. A cada uno con su elección...¿no creen?... antes de llamar a gluLookAt().
Las distancias NEAR y FAR son siempre positivas y En cuanto a los parámetros que demanda la función son los
medidas desde el COP hasta esos planos, que ser án siguientes:
obviamente paralelos al plano Z = 0. Dado que la cámara Coordenadas del "eye". Es literalmente la posición XYZ
apunta por defecto en la dirección negativa de Z, el plano de dónde colocar la cámara dentro del mundo. Coordenadas
front (near) estará realmente situado en z = -zmin mientras del "at". Es el valor XYZ del punto al que queremos que
que el de back (far) estará en z = -zmax. mire la cámara. Un punto del mundo obviamente.
Lo hemos dado más o menos por asumido pero vamos a Coordenadas del vector "up". Si, es un vector y no un
recordar un poco. Pensemos que no hay que proyectar toda la punto. Con él regularemos la orientación de la cámara. Este
geometría existente sino tan sólo la que ve la cámara desde 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 A fin de hacer esto, que resulta ya de por sí un tanto
3 podemos verlo gráficamente. 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.
Figura 3. Las variables de la cámara La base de nuestro árbol en realidad es un cilindro, y noten
como debemos transformar la geometría del árbol antes de
En Delphi!!, En Delphi!!... mostrarlo, esto para ajustarlo a nuestro ambiente virtual.
Bien, veremos a continuación un ejemplo hecho en Ahora aquí es donde empieza lo verdaderamente interesante,
Delphi en el que dibujaremos un pequeño escenario virtual, ahora sí podemos entender que eran los números que
en el cual podremos desplazarnos y observarlo poníamos en gluperspective(), aquí vemos como hemos definido
detalladamente con ayuda del ratón y del teclado. Veamos el el ángulo de visualización a 35, y como pasamos el parámetro
listado 1 y la figura 4 para ver que pretendemos hacer. 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.
Figura 4. Nuestro mundo virtual 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); //Le ponemos una base feliz.
gltranslatef(0, 0.2, 0);
glrotatef(90,1,0,0);
P := GLUNewQuadric;
gluQuadricTexture(p,1);
gluCylinder(p,0.13,0.3,0.2,10,10);
gluDeleteQuadric(p);
gldisable(GL_TEXTURE_2D);
glpopmatrix;

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

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


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

procedure TForm1.FormResize(Sender: TObject);


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

procedure TForm1.WMEraseBkgnd(var Message: TWMEraseBkgnd);


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

procedure TForm1.Timer1Timer(Sender: TObject);


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

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


Y: Integer);
begin
if (ssLeft in Shift) then begin
dx:=(x-mStartX)/10;
dy:=(y-mStartY)/10;

33
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
queremos encontrar un vector normal a este plano solo
Iluminación con OpenGL (I) tenemos que encontrar un vector que sea perpendicular a
cualquiera de estos vértices... Pues bien, para esto solo
Cálculo de Normales, Tipos de Iluminación. 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
¿Qué chiste tendría un mundo sin luz?... imaginemos por un vector perpendicular a ambos y por lo tanto una normal del
momento un lugar en donde los colores sean simples y plano.
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 Figura 1. Calculo de normales para una cara.
para representar alegría, gozo y regocijo. Como vemos las
luces tienen múltiples aplicaciones estéticamente hablando. 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
¿Qué es la luz? obtenemos el vector normal. Y este vector ya puede asociarse
Bueno, técnicamente la luz es una forma de energía que a la correspondiente cara.
actuando sobre nuestros ojos nos hace ver los objetos. Según Una vez calculada la normal debemos de normalizar, es
los físicos esta forma de energía se desplaza en forma de onda decir, dividir ese vector por su propio módulo para que sea
a una gran velocidad y tiene extraños comportamientos unitario. De esta forma tenemos un vector normal de módulo
cuando choca con una superficie reflejante o refractante. igual a la unidad que es lo que OpenGL necesita.
Su estudio le corresponde a la disciplina de la Óptica, la cual Ahora, solo debemos tener cuidado con el orden en el que
es una parte de la Física que trata del estudio de la luz y de efectuamos estos productos vectoriales, porque de este orden
los fenómenos luminosos. Además también estudia sobre el depende que el vector normal apunte hacia fuera o hacia
comportamiento de los espejos y las lentes. dentro de la cara. Pensemos que siempre queremos que
A lo largo de este curso veremos como las leyes de la física, nuestras normales apunten hacia fuera de la cara visible, es
en particular de la óptica son muy aplicables en las decir hacia el frente. Ya que este vector nos servirá para
aplicaciones gráficas. Ya que si tratamos de simular la calcular la incidencia de la luz en la cara, y ¿para qué
realidad, antes debemos entender como suceden los queremos calcularla en una superficie que no es visible?,
fenómenos en la realidad, ¿no creen?. ¿lógico, no?.
Aquí cabe hacer un pequeño apunte que resulta algo
relevante: OpenGL utilizará la normal asociada a cada vértice
La clave de todo: Las Normales
para evaluar la luz que incide sobre éste. Si un vértice
Para iluminar una superficie o plano, necesitamos pertenece a más de una cara (es un caso obvio ilustrado en la
información sobre su vector normal asociado. Primero figura 1 donde el vértice A por ejemplo, pertenece a tres caras
veamos que es una normal: la normal de un plano es en distintas), ¿qué debemos hacer?. En este caso hay que
realidad un vector perpendicular a este, as í de fácil. promediar para obtener cálculos correctos por parte de
¡Volvemos otra vez a la tediosa Álgebra Lineal!... Bueno, no OpenGL. Tendremos que calcular la normal de cada una de
todo es miel sobre hojuelas en este ambiente, ni modo... aquí las caras a las que pertenece el v értice, promediarlas y
necesitamos entonces saber como calcular este vector y como después normalizar el resultado, con lo cual ese v értice
especificárselo a OpenGL, así que debemos repasar un poco presentará un vector normal al cual han contribuido todas las
lo que ya habíamos estudiado sobre aritmética de vectores ¿lo caras a las que pertenece.
recuerdan?.
Tratemos de ver esto con un caso práctico: veamos la "figura
1"... Supongamos que tenemos el objeto que se presenta en la
Eso está bien, pero...¿Cómo se definen
figura y deseamos calcular el vector normal a la cara superior normales en OpenGL?
de este objeto, la formada por los vértices A, B, C y D. Bien, para definir normales en OpenGL usamos el
Todos estos vértices son coplanares (adoro estos términos _) procedimiento glNormal3f(); declarándolo ya sea una normal por
es decir, que pertenecen a un mismo plano, as í que si 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 Para definir cada uno de estos tipos de iluminaci ón en
vector en cuestión. Como siempre hay variaciones sobre este nuestras escenas debemos usar la función glShadeModel();
procedimiento dado que es del tipo glNormal* al igual que como pasándole como parámetro el tipo de iluminación que
comentamos en otra ocasión para glVertex*. deseamos. Usamos GL_FLAT como parámetro para definir una
Esto es calcular las normales “a pié” como diríamos en iluminación plana, y GL_SMOOTH para una iluminación suave.
México, ya que involucra que nosotros mismos hagamos los También existen otros modos de definir el tipo de
cálculos de las normales ya sea para cada vértice o cara de iluminación cuando trabajamos con objetos de la librería
nuestra figura. Y cuando no somos muy buenos que digamos GLU, pero eso lo veremos cuando analicemos nuestro
para el Álgebra de Vectores, pues esto pudiera resultar un programa de ejemplo.
poco tedioso, ¿no es así?... Pues OpenGL también cuenta con
un cálculo automático de normales, (¡Vaya, que alivio!). Por Y se hizo la luz!!!...
un lado esto es bueno para nosotros los programadores OpenGL soporta en principio hasta 8 luces simultaneas en un
porque pues nos ahorra tal vez algo de tiempo en pensar en escenario. De hecho también depende de la máquina que
métodos para calcular las normales, ó tal vez calcularlas “a poseamos y de la RAM que le dejemos usar.
pié” en alguna hoja de calculo; pero por otro lado puede Las luces cuentan con nombre propio del estilo GL_LIGHT0,
llegar a resultar contraproducente si nuestra aplicación corre GL_LIGHT1, GL_LIGHT2 y así sucesivamente. Para activar o
en una configuración que no presenta aceleración para desactivar cada una de ellas también usamos glEnable() y
OpenGL, pues se carga al sistema con cálculos innecesarios glDisable() pasando como parámetro el nombre de la luz que
que ralentizan aún más lo que ya de por s í es deseamos afectar. También podemos activar y desactivar todo
computacionalmente exigente, es decir, el cálculo automático el cálculo de iluminación pasando como parámetro
de la iluminación.
GL_LIGHTING a estos procedimientos.
Bueno, de cualquier modo debemos mencionar aquí todas las Ahora tenemos que ver las características específicas de cada
posibilidades... para habilitar el cálculo automático de las una de estas luces, tanto como su posición en la escena, como
normales solo tenemos que usar la instrucci ón su orientación y su color, por ejemplo. Hay algunas otras
glEnable(GL_NORMALIZE); y así OpenGL hará el trabajo por
características, pero esas las estudiaremos en el siguiente
nosotros. Y cuando deseemos nosotros tomarnos esa molestia número (no se trata de bombardear con información para
solo debemos inhibir el c álculo automático con luego no enterarse de nada, ¿no?).
glDisable(GL_NORMALIZE);. Estas funciones para habilitar e
Para todas estas características usaremos la función que en
inhabilitar cosas ya las habíamos mencionado anteriormente, lenguaje C está definida así:
cuando hablábamos de que OpenGL se comportaba como una
máquina de estados finitos (autómata), bien, pues aquí está un void glLightfv( Glenum light, Glenum pname, const Glfloat *params );
ejemplo más de eso.
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 Figura 2. La forma de despliegue de nuestra escena
interpolación de colores para el resto de puntos. De esta
forma ya empezamos a presenciar escenas granuladas, con
refiriendo (GL_LIGHT0, GL_LIGHT1, GL_LIGHT2, etc.) En cuanto a
degradados en la geometría y la calidad ya empieza a ser
*params, le pasamos un arreglo de valores RGBA reales que
notable.

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!!!
Bien, empezaremos a analizar el código que mostramos en
Figura 3. La forma de controles para la iluminación el "listado 1".
Como veremos, esta vez tenemos una aplicación con dos
definen la característica en concreto. Estos valores RGBA formularios, uno para mostrar la escena, y el otro para mostrar
definen el porcentaje de intensidad de cada color que tiene la una serie de controles que nos permitan modificar la luz que lo
luz. Si los tres valores RGB valen 1.0 la luz es sumamente ilumina todo.
brillante; si valen 0.5 la luz es aún brillante pero empieza a La primera parte ya nos debe de resultar familiar a estas
parecer oscura, de un tono de gris. alturas, ¿no es así?. Lo único nuevo que observamos aquí es
En cuanto al pname este nos servirá para determinar la que hemos declarado tres arreglos como variables globales que
característica que deseamos modificar de dicha luz. Ahora nos servirán para determinar la Posición, el valor difuso y
veremos que valores puede tomar este parámetro: especular de la luz que tendremos en nuestra escena.
Pasamos GL_AMBIENT para definir la característica ambiental, la Realmente la parte distinta que podemos apreciar en el código
cual define la contribución de esta fuente de luz a la luz es en la parte del evento OnPaint() del formulario principal.
ambiental de la escena. Por defecto esta contribución es nula. Como ven, no difiere mucho la primera parte de este evento de
Si usamos GL_DIFFUSE modificamos la característica difusa de la lo que ya hemos estudiado anteriormente hasta que llegamos a
luz, que es lo que entendemos como el color que tiene la luz. la parte de los glLightfv() donde definimos las características de la
Para GL_LIGHT0 los valores RGBA por defecto valen 1.0. Para el única luz que tendremos en escena: la posición, la difusa y la
resto de luces los valores por defecto son 0.0. especular.
Con GL_ESPECULAR modificamos la característica especular de la Tendremos una esfera, la cual será nuestro objeto iluminado, y
fuente de luz. Esta se trata de la luz que viene de una dirección esta esfera estará rotando sobre su propio eje Y para que así
particular y rebota sobre un objeto siguiendo una determinada tengamos una mejor apreciación del movimiento de nuestra
dirección. Es la componente responsable de las zonas más luz. En la segunda forma hemos puesto un CheckBox el cual
brillantes en la geometría, de los llamados “highlights” o nos permitirá habilitar e inhabilitar la luz en nuestra escena, y
“luces altas”. 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); // Se definen las


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

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

//Se habilitan las luces...


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

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

Listado 2

unit Unit2;

interface

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

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

40
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
nos interesa afectar, el segundo es el parámetro a modificar, y
Iluminación con OpenGL (2) el tercero es el valor ó valores que tomará dicho parámetro.
Más sobre Luces, Definición de Materiales, Así pues para indicar estos tres parámetros para la luz
Tipos de Reflexiones. número 3 de nuestra escena usaríamos:
glLightf(GL_LIGHT3,GL_CONSTANT_ATTENUATION,0.8);
Bien, continuaremos con la serie sobre iluminación que glLightf(GL_LIGHT3,GL_LINEAR_ATTENUATION,0.5);
iniciamos en el número anterior, pero esta vez hablaremos un glLightf( GL_LIGHT3, GL_QUADRATIC_ATTENUATION, 0.1 ) ;
poco más técnicamente acerca de las luces, y de los modos en
como ésta es recibida por diferentes cuerpos. 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
Más parámetros para las luces fuentes de iluminación.
Aparte de las características que ya habíamos mencionado Veamos la Figura 2... en ella encontramos el anglicismo
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).

Figura 2

“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
Figura 1 decirlo en Español!!), sin embargo el ambiente de los
gráficos está plagado de anglicismos que dicen mucho en
El primero de estos (a), se refiere a la atenuación constante pocas letras, y no nos queda más remedio que vivir con eso
que sufre sin importar la distancia. Es el desgaste natural de por ahora.
la energía luminosa. En OpenGL la llamamos En fin, para definir el cono de nuestra luz usamos los
GL_CONSTANT_ATTENUATION y por defecto tiene el valor de 1.0 si siguientes parámetros:
este no se especifica. GL_SPOT_CUTOFF para definir el cono. En este parámetro lo que
El segundo (b), es la atenuación lineal, y la llamamos así hacemos es definir un ángulo de abertura para el cono, con un
porque es el parámetro que se multiplica linealmente por la valor que puede ir de los 0.0 a los 90.0 grados. Y en caso de
distancia en nuestra formula. Lo cual nos da un factor que se que no nos interese definir un ángulo, usando un valor de
incrementa linealmente a medida que aumenta la distancia. 180° estaremos desactivando esta opción.
En OpenGL lo llamamos GL_LINEAR_ATTENUATION y por defecto GL_SPOT_DIRECTION, el cual nos sirve para restringir la dirección
es igual a 0. de la luz emitida. Es un vector de tres valores reales que
El tercero (c), es la atenuación cuadrática, y a ésta la corresponden al vector direccional que indica hacia adonde
llamamos así porque a este parámetro lo multiplicamos por el apunta nuestro foco. Por defecto esa dirección es la de las Z's
cuadrado de la distancia a aplicar. Con esto obtenemos un negativas.
valor aún mayor que con la atenuación lineal, y por lo mismo Y por último GL_SPOT_EXPONENT, que regula la pérdida de
una atenuación más severa. En OpenGL hacemos referencia a intensidad de la luz a medida que nos alejamos del centro del
este parámetro con GL_QUADRATIC_ATTENUATION que por defecto cono, algo similar a lo que habíamos mencionado sobre la
es igual a 0. atenuación con la distancia. Los valores que este parámetro
Y ahora, ¿de qué nos sirve saber esto?... Bien, pues como puede tomar fluctúan entre 0.0 y 128.0. ¿Intuimos como se le
pueden darse cuenta, la función en realidad representa una indican estos valores a cada luz en OpenGL?
fracción de 1 sobre algo... Pues OpenGL reduce la intensidad
de la luz que llega a un determinado lugar con esta fracción,
Los materiales...
evidentemente esto de manera inversamente proporcional a la
distancia a la que se encuentre dicho lugar. No es lo mismo iluminar un brillante que un pedazo de
Para indicar estos parámetros de las luces a OpenGL usamos madera, como tampoco es lo mismo iluminar un pedazo de
la ya conocida función glLightf(); a la que, como han de metal que un trozo de tela, o de plástico. Esto porque existen
recordar, le pasamos tres argumentos, el primero es la luz que 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
propiamente de principios físicos * params
todo lo que hemos estado GL_FRONT GL_DIFFUSE ( R, G, B, 1.0 )
mencionando. GL_BACK GL_AMBIENT ( R, G, B, 1.0 )
A cada pedazo de la geometría que GL_FRONT_AND_BACK GL_AMBIENT_AND_DIFFUSE ( R, G, B, 1.0 )
compone nuestra escena podemos GL_EMISSION ( R, G, B, 1.0 )
asignarle un material distinto, y así GL_SPECULAR ( R, G, B, 1.0 )
también poder representar objetos GL_SHININESS [ 0, 128 ]
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 habí amos mencionado en artí culos anteriores, así que
los valores que pueden adoptar los par á metros de la nos centraremos en la parte donde definimos las
funció n. En el caso de face tenemos tres posibilidades caracterí sticas de la luz, y el material a usar. Como ven, la
dependiendo de si la caracterí stica en cuestió n debe 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 Depth-


Buffer

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

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

glLightfv(GL_LIGHT0, GL_AMBIENT, @ambient); //Se definen


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

//Se habilitan las luces...


glEnable(GL_LIGHTING);
glenable(GL_LIGHT0);

//Se dibujan las esferas...


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

//* Esfera en primer renglón, segunda columna


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

//* Esfera en primer renglón, tercera columna


//* solo reflexión difusa y especular ; alto en brillo

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

//* Esfera en primer renglón, Cuarta columna


//* solo reflexión difusa y emisión sin brillo

45
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. Dibujaremos las esferas situándolas en 3 líneas y 4
Por ejemplo, para el caso de los arreglos que definen columnas, ordenadas de acuerdo a las características que
nuestra luz, vemos que usamos una luz que no tiene efecto presenta el material usado para construirla. Así tenemos que
sobre el ambiente, y que es completamente blanca (¡pura en la primera y la cuarta columna no usamos ning ún
como la nieve!), que está colocada en las coordenadas: coeficiente de brillo, en la segunda usamos un brillo alto, y
(0.0,3.0,2.0). Para el caso del material tenemos m ás en la tercera un brillo bajo.
arreglos, tenemos un arreglo con valores de 0, para Y así vamos haciendo combinaciones. Si se fijan un poco
determinar cuando no usaremos color para definir alguna verán por ustedes mismos cuales han sido los criterios que
característica del material (ambiente, difusa, especular, hemos aplicado a cada una de las filas y columnas en
brillo), y del mismo modo para el brillo tenemos tres nuestra escena de esferas. En algunas usamos valores de
posibles valores, que puede ser un alto coeficiente de brillo, emisión, en otras no; y en el caso de la tercera fila usamos
o uno bajo, o de plano ningún brillo. un color de material un poco diferente. Observen como
Bien, nuestro demo lo construiremos a partir de esferas cada vez que vamos a dibujar, debemos definir de nuevo
usando un procedimiento de la librería GLUT: glutSolidSphere() todas las características del material a utilizar, porque se
al cual se le pasan como parámetros el radio que tendrá esa conservan las anteriores, ¿Recuerdan lo de la máquina de
esfera, así como el número de meridianos y paralelos que la estados que mencionábamos?.
definirán. Por el mismo hecho de que estamos definiendo Y hasta aquí llegamos por ahora con la iluminación, en lo
esferas, solo usamos GL_FRONT para las características, ya que se refiere a sus conceptos básicos. Más adelante, en
que al ser éstas objetos cerrados, nunca veremos la cara posteriores artículos, y cuando ya estemos más avanzados
posterior de los polígonos que las forman. 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.

En esta ocasi ón tocaremos el tema de las texturas. Este es a las figuras que
un tema bastante interesante ya que aunque de primera defin íamos en la
vista pareciera sencilla la idea de poner una imagen sobre matriz de modelado
una cara de una figura, en realidad es algo que involucra les pod íamos aplicar
muchas cuestiones adicionales. escalamientos y
Las texturas vienen a darle un mayor realismo a las rotaciones, a las
escenas que elaboramos no solo con OpenGL, sino con texturas tambi é n
cualquier motor de gr á ficos. Estas nos pueden ser útiles podemos rotarlas y
para representar de manera sencilla la rugosidad ó la escalarlas respecto a
finura de una superficie. Bien dicen que Una “ imagen dice cualquier coordenada,
más que mil palabras”; pues esto tambi é n es muy siempre teniendo en
aplicable a ún propiamente a las cuestiones de los gr á ficos. cuenta, claro, que
Y en cuanto a la aplicabilidad suele ser tambi é n muy estamos trabajando
variada. Pueden servir por ejemplo para representar el con superficies, y que
Figura 1.- La textura original.
nivel de altitud de una coordenada sobre un terreno, o las im á genes que
simplemente para definir el estampado”
“ que presenta una manejaremos (por lo
pared en una habitaci ón. El caso es que las texturas son menos en este
algo indispensable a la hora de generar gr á ficos de alto número) son de 2
realismo. dimensiones. Veamos
La texturizaci ón es t ípicamente usada para proveer las Figuras 1, 2 y 3
detalles de color a superficies intrincadas. Por ejemplo, donde aparecen
supongamos que deseamos modelar un mueble de madera ejemplos de
barnizada, es necesario dibujar sobre las partes de ese transformaciones
mueble para que realmente parezca de madera. Aunque la lineales aplicadas a
texturizaci ón tambi é n puede ayudarnos a solucionar una textura.
algunos otros problemas que resultar ían menos obvios, y OpenGL soporta
que estudiaremos a su tiempo. Figura 2.- imá genes de texturas
de una y dos
La misma textura rotada 45 grados.
Una matriz especial para las texturas dimensiones (1D y
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
Figura 3.- soportan texturas de 3
caracter ísticas y para qu é nos sirven. Pues es el turno de
y
La misma textura despu é s de haber 4 dimensiones
hablar de la matriz de texturas.
sido escalada linealmente. tambi é n (3D y 4D),
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

50
Texturas con OpenGL(I)

sencillito. tanto la superficie como la textura tengan el mismo


tamaño, en cuyo caso la relación resulta ser lineal (1 Píxel
Un concepto nuevo: Los Texeles = 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 Texel, vendría siendo como el elemento relativo a un un tamaño mayor.
píxel en un objeto Canvas, solo que aplicado a la lógica de Para solucionar este tipo de problemas OpenGL
una textura. Es decir, se refiere a cada uno de los píxeles implementa 2 tipos de filtros. A estos se les llama: Filtros
de la imagen que forman nuestra textura, (o al menos así de Minificación y de Magnificación.
podemos nosotros entenderlo) El Filtro de Minificación se ocupa cuando muchos texeles
Algo que debemos hacer primeramente es mapear la se mapean a un mismo píxel (Texel < Píxel), y el filtro de
superficie que vamos a texturizar. Es decir, determinar las Magnificación se ocupa cuando muchos píxeles del
esquinas y las orillas de nuestra textura en la cara. Para despliegue están mapeados por un mismo texel (Texel >
que nos quede un poco más claro, supongamos que Píxel). No es tan complicado, ¿O sí?, Viendo la Figura 4
deseamos texturizar un cuadrado, bien, pues tenemos que nos enteraremos mejor.
decirle a nuestro motor de gráficos que esquina de nuestra El más simple de estos filtros es conocido como "Point
textura es la que debe aplicar a qué vértice de nuestro Sampling" (Muestreo de puntos). En OpenGL
cuadro, así podemos texturizar con la imagen invertida, ó denominamos a este filtro con la sentencia GL_NEAREST.
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 Figura 5.- Los criterios de los filtros.
una coordenada intermedia dentro de nuestro
mapa de textura. Este consiste en encontrar simplemente aquel Texel que
resulte ser el más cercano a la coordenada de la textura,
La Minificación y la Magnificación... este es muy rápido, pero obviamente no el de mejores
Al momento de aplicarle una textura a una superficie, resultados. Sin embargo si es utilizado, porque funciona
puede darse el caso de que esta superficie no sea del para aquellos casos en los que la velocidad es m ás
mismo tamaño en píxeles y texeles que nuestra imagen. importante, pero son más recomendables aquellos filtros
Aquí pueden sucederse tres casos (Ver Figura 4), Que 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
Figura 4.- Los 3 casos posibles de filtrado. 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 que hace es escalar el color de la textura a partir del color
interpolación distinta para después hacer una interpolación original de la superficie. La función Modulate se define en
entre cada uno de estos mismos ejemplos, obteniendo así OpenGL con la sentencia GL_MODULATE.
una mejor definición para el mapeado final. El más usado La función de ambiente Decal, simplemente superpone el
de estos filtros (y también el más caro computacionalmente color de la textura al color de la superficie, aplicando
hablando) es el de mipmapping trilineal, el cual consiste en también efectos de Alpha Blending cuando se trabaja con
tomar 4 ejemplos de cada uno de los 2 niveles de mipmaps sistemas RGBA. Existe también una generalización de este
más cercanos y entonces interpolar los dos conjuntos de filtro que se denomina Replace que trabaja de una manera
ejemplos. muy similar. Ya veremos más claramente su efecto en el
OpenGL no provee de comandos para la construcción de ejemplo. Estos filtros se definen en OpenGL con las
mipmaps, pero la librería GLU nos proporciona algunas sentencias GL_DECAL y GL_REPLACE respectivamente.
rutinas simples para generar mipmaps usando un filtro Una última función es la denominada Blend, la cual hace
simple. En el ejemplo mostraremos como se construyen una combinación entre el color de la superficie y el color
mipmaps con esta librería. Pero profundizaremos en su de la textura. En OpenGL esta se determina con la
utilización en el siguiente número. sentencia GL_BLEND.
En la Figura 5 se muestra gráficamente la aplicación de En la Figura 6 tenemos una muestra de los efectos que
estos filtros. 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 Figura 6.- Los efectos de las funciones de ambiente. llamaremos
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 definición baja usamos point sampling en ambos casos.
encontramos con un tipo enumerado TDefinición, el cual nos Para definir el tipo de filtro a aplicar usamos la función
servirá para determinar con qué calidad deseamos glTexParameteri() utilizando como primer parámetro la
presentar la texturización donde sus elementos son dAlta, sentencia GL_TEXTURE_2D para indicar que lo que haremos
dMedia y dBaja, los cuales corresponden a definición alta, de será modificar un parámetro de una textura bidimensional,
calidad media y calidad baja respectivamente. el segundo parámetro es el tipo de filtro a definir, aquí
Ya dentro de la definición de TTextura encontramos 2 usamos GL_TEXTURE_MAG_FILTER para magnificación, y
campos privados que son IH e IW, que nos servirán para GL_TEXTURE_MIN_FILTER para minificación, y el tercero es
almacenar las dimensiones (Ancho y Alto) de la imagen propiamente el filtro a aplicar.
original que utilizamos para la construcción de la textura. La función de ambiente se declara usando el
En la sección pública encontramos más campos; el de img procedimiento glTexEnvf() con los par ámetros:
es un arreglo unidimensional donde almacenaremos GL_TEXTURE_ENV , GL_TEXTURE_ENV_MODE y por último la
propiamente la imagen, en forma de un buffer. En este función de ambiente a aplicar, que en nuestro caso
arreglo de Bytes acomodaremos los valores RGB de cada estamos almacenando en el campo ModeEnv.
uno de los píxeles de nuestra imagen original en forma Después definimos el buffer donde se encontrará nuestra
lineal, para que pueda ser interpretada por los comandos textura y sus dimensiones. El caso de las definiciones de
de OpenGL. Las dimensiones del arreglo dependen del MipMaps las estudiaremos en el siguiente número, y en
tamaño máximo que soportaremos para las imágenes. este nos centraremos en la definición de texturas simples
Hagan cuentas y determinarán si una imagen es soportada usando la función glTexImage2D() a la cual le pasamos como
o no por este buffer. Nota: Recuerden que trabajamos con parámetros las dimensiones de la imagen original, el tipo
imágenes de dimensiones de potencias de dos. de datos que contiene el buffer (en este caso bytes sin
El campo ModeEnv nos servirá para definir el tipo de signo), y la dirección de memoria donde se encuentra el
función de ambiente que utilizaremos para nuestra textura, buffer que contiene los códigos RBG, entre otros. Por
ya sea GL_DECAL, GL_REPLACE, GL_MODULATE ó GL_BLEND. Y el último, solo debemos habilitar la texturización en la
campo Definición que usaremos para determinar el tipo de máquina de estados finitos de OpenGL, esto lo hacemos
definición a usar en cada textura, ya sea Alta, Media o con la llamada de procedimiento glEnable(GL_TEXTURE_2D).
Baja. Del mismo modo para inhibir la texturización desde
Dentro de los métodos, primero nos encontramos el cualquier instancia de esta clase llamaremos al método
constructor Create, en el cual lo que hacemos es invocar al InhibeTexturas() el cual lo único que hace es deshabilitar
constructor de la clase ancestra TObject con la instrucción esa característica dentro del autómata de OpenGL con
inherited, y después asignar valores por omisión a los glDisable(GL_TEXTURE_2D).
campos ModeEnv y Definición, en este caso usamos por defecto Con esta definición de clase nos simplificaremos en gran
la función de ambiente GL_DECAL y una Definición Alta. medida la utilización de las texturas en nuestras
El método CargarDe() lo utilizaremos para cargar nuestra aplicaciones, y la iremos enriqueciendo con el tiempo, ya
textura en el buffer img a partir de un mapa de bits (TBitmap) lo verán.
Como pueden ver lo único que hacemos en este
procedimiento es decomponer cada uno de los colores de Y después de la definición de la clase,
los píxeles de nuestro mapa de bits en 3 valores que serán las coordenadas del mapa de textura...
el código RBG de dicho color y acomodar estos valores en
forma lineal en el buffer. De una manera similar Lo que haremos en esta ocasión es una pequeña
podríamos implementar un método que cargara la textura animación con un cubo, una esfera y un cono, con texturas
desde un archivo en cualquier formato (BMP, JPG, WMF, diferentes, y con una barra de herramientas para
etc.); Eso pueden tomarlo como una tarea para ustedes. configurar el comportamiento de dichas texturas.
El siguiente método UsaTextura() es con el que invocaremos El Listado 2 corresponde al código de nuestro formulario
propiamente a cada una de nuestras texturas en la principal aquí es donde vemos la definición de 3 variables
aplicación. En este método es donde nos toca definir los globales que contendrán las texturas que ocuparemos para
filtros de Magnificación y Minificación que utilizaremos unit Unit1;
para cada una de las tres definiciones que contemplamos
en nuestra clase. Observen que en la definici ón alta interface
usamos mipmaping para la definición del filtro de
uses
minificación (ya que sólo en este caso funciona), y esto Windows, Forms, OpenGL, Classes,
nos da una mayor calidad en cuanto a la presentación de la ExtCtrls, Messages, Controls, StdCtrls,
textura. En el caso de la definición media lo que hacemos Graphics, sysUtils, Dialogs, Texturas;
es utilizar interpolación lineal para ambos filtros, y en la

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 parámetros los dos valores que le tocarán a este vértice
escena. La Figura 7 nos muestra las imágenes que dentro del mapa de textura. Como recordarán, estos son
usamos para texturizar nuestras figuras, como se dan valores que fluctúan de 0 a 1, en este caso usamos solo
cuenta todas son imágenes de diferentes dimensiones en los valores enteros porque declaramos los extremos de la
cuanto a altura y anchura. imagen en cada cara, teniendo la misma imagen en cada
Dentro de la clase del formulario hemos agregado un lado, pero bien podríamos usar valores decimales, para
método extra para dibujar el cubo texturizado indicar que una sola imagen envolverá a todo nuestro
(TForm1.Cubo), como ven dibujamos cada una de las caras cubo.
de nuestro cubo usando polígonos. Aquí lo interesante es En el evento OnCreate() del formulario instanciamos
que noten como antes de definir cada v értice le nuestras variables a partir de la clase TTextura que hemos
determinamos cual será la coordenada del mapa de definido y cargamos cada textura a partir de cada una de
textura que le corresponderá a dicho vértice; para esto las imágenes que mantenemos en componentes TImage,
utilizamos la función gltexcoord2i(), pasando como 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 cada vértice del cuadrado usando glTextCoord2f(); pero con
dedicada a las texturas en OpenGL, esta vez estudiaremos esto obtenemos una textura estática que siempre se aplica
como podemos texturizar cuerpos para los cuales no del mismo modo a esta figura. Esto puede resultarnos útil,
tenemos definido nuestro mapa de textura y algunas por ejemplo, para definir una pintura en una galería, ó los
ventajas que podemos tener de esto mismo. interiores de una nave espacial, por citar algunos casos.
También conoceremos como hacerlas en forma de Ahora veamos como podemos hacer que tengamos un
mosaico, y para terminar algo sobre como basar nuestras poco de animación variando las coordenadas de texturas a
aplicaciones gráficas en GLUT, del que tanto hemos aplicar, por ejemplo, como podríamos programar de una
hablado, y que hasta ahora solo conocíamos en teoría. manera muy simple una corriente ó caída de agua
variando muy poco este código con:
La generación automática de
glBegin(GL_QUADS) ;
coordenadas de texturas glTexCoord2f(0.0,down) ; glVertex2f(-1.0,-1.0)
En el mes pasado habíamos visto que para poder aplicar ;
glTexCoord2f(0.0,up) ; glVertex2f(-1.0, 1.0) ;
una textura a un objeto gráfico, era necesario que este glTexCoord2f(1.0,up) ; glVertex2f( 1.0, 1.0) ;
objeto estuviera mapeado, es decir que tuviera señaladas glTexCoord2f(1.0,down) ; glVertex2f( 1.0,-1.0)
las coordenadas de texturas que corresponderían a los ;
texeles a aplicar a cada uno de los vértices de nuestra glEnd() ;
figura. down := down + 0.1 ;
También vimos que este proceso de mapeado lo podía up := up + 0.1 ;
hacer nuestra propia aplicación generando nosotros
mismos las coordenadas a aplicar a nuestros modelos (esto Como ven lo único que hicimos fue introducir dos
cuando nosotros los dibujamos con primitivas básicas), ó variables que nos sirvieran como controladores del mapa
bien, invocando a algún método que lo hiciera para de textura (up y down) y que se modificarán con cada
objetos predefinidos, como es el caso de gluQuadricTexture(), la iteración de las escenas, y así se logra la magia. Esta es
cual genera el mapa de textura para objetos creados con una manera muy simple y hasta cierto punto lógica de
gluNewQuadric(), pero ¿Qué hacemos si tenemos un cuerpo en animar una superficie texturizada.
nuestra escena que nos interesa texturizar, pero no Pero no siempre tenemos la oportunidad de definir
tenemos modo de generarle su mapa de textura?...O bien, nosotros mismos de manera tan directa las coordenadas de
¿Qué hacemos si quisiéramos que este cuerpo representara texturas a utilizar, ya que algunas veces usaremos objetos
una reflexión de la textura del ambiente? predefinidos por alguna librería gráfica ó importados
Una manera muy simple de tener animación en cuanto a desde algún programa de diseño, en el cual no se haya
las texturas es variando las coordenadas asociadas a definido el mapa de textura para ese cuerpo; y en esos
nuestra figura, por ejemplo, con: casos lo que nos conviene es utilizar la generación
automática de coordenadas de texturas que proporciona
glBegin(GL_QUADS) ; OpenGL.
glTexCoord2f(0.0,0.0) ; glVertex2f(-1.0,-1.0) ;
glTexCoord2f(0.0,1.0) ; glVertex2f(-1.0, 1.0) ;
glTexCoord2f(1.0,1.0) ; glVertex2f( 1.0, 1.0) ; Generación Lineal y Esférica
glTexCoord2f(1.0,0.0) ; glVertex2f( 1.0,-1.0) ; Tenemos dos formas o criterios para generar coordenadas
glEnd() ;
de texturas de manera dinámica en OpenGL, de forma
Obtene mos un cuadrado texturizado, ya que como Lineal (Linear), y mapeando de manera esférica (Sphere
habíamos visto declaramos una coordenada de textura por 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 aunque el objeto cambie de posición, ó de tamaño. Por
normales y vectores de reflexión. La Figura 1 nos muestra este motivo, estas coordenadas son generadas después de
algunos ejemplos de aplicación de estos criterios. 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 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 Figura 3 .-
este caso lo que se pretende es hacer que las coordenadas Una aplicación de la generación de coordenadas
de texturas que se muestran al observador no var íen, lineales al observador la tenemos al mapear terrenos.

Y vamos con las texturas esféricas...


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

61
Texturas con OpenGL(II)

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
Figura 4 .- Objetos texturizados con Texturas Esféricas. la dirección de incidencia en el cálculo de la
textura.

Los criterios de repetición y dos


coordenadas para las texturas
Como ya vimos, cuando definimos el mapa de texturas,
definimos coordenadas que van de 0 a 1 en ambos ejes.
Como resultaría complicado hablar de los ejes X e Y para
las texturas (ya que se confundirían con los ejes del
propio objeto), para este caso se utiliza una nomenclatura
diferente para llamar a los ejes: llamamos S al que
correspondería al eje X de la textura, y T al eje Y.
Pero, ¿Qué pasa cuando definimos una coordenada de
textura que resulta superior a 1 ó inferior a 0?... ¡pues
resulta que se aplica un criterio de repetición basado en la
fracción decimal o entera de esa coordenada!
Estos criterios pueden ser de dos tipos: Trasformar la
coordenada al rango de [0..1] para así repetir la textura
ordenadamente(GL_REPEAT), o bien, simplemente repetir la
Figura 5 .- Como se ve una textura esférica. coordenada que resulte más cercana en el rango de [0..1]
(GL_CLAMP).
tmodelo poligonal, y ver la textura original como si fuera Estos dos criterios son los valores con los cuales se
parte de las caras de un cubo. Para enterarnos mejor de modifican los parámetros GL_WRAP_S y GL_WRAP_T de las
esto veamos la Figura 6, donde mostramos como se 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,
Figura 6 .- esta vez veremos que son esos parámetros tan
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)

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
Figura 4 .- Objetos texturizados con Texturas Esféricas. 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 Como resultaría complicado hablar de los ejes X e Y para
esto veamos la Figura 6, donde mostramos como se 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,
Figura 6 .- simplemente repetir la coordenada que resulte
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 R, G, B y Alpha , o valores de iluminación o intensidad
modifican los parámetros GL_WRAP_S y GL_WRAP_T de las son seleccionados para usarlos en la descripción de los
texturas 2D, esto es porque se pueden combinar para cada Texeles de la imagen. Este puede ser uno de treinta y ocho
uno de los ejes S y T que mencionábamos hace un símbolos de constantes ya predefinidos. El único que
momento. hemos usado aquí ( GL_RGB) especifica que los
La Figura 8 nos muestra las posibles combinaciones que componentes Rojo, Verde y Azul son usados para
podemos efectuar con estos dos criterios para ambos ejes, describir un texel.
y los efectos que se tienen en la presentación final. 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
Figura 8 .- Los casos en los criterios de repetición de coordenadas. alguna localidad de memoria previamente
inicializada con el procedimiento glGenTextures(),
Un vistazo al método constructor de las que genera el espacio donde se almacenará dicha textura.
texturas y al mipmapping En el ejemplo mostramos como se puede hacer para
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. Al Ejemplo...
El primer parámetro puede ser GL_TEXTURE_2D o Bien, revisemos los Listados, y veamos que cosas nuevas
GL_PROXY_TEXTURE_2D. En esta ocasión no discutiremos nos muestran esta vez...
GL_PROXY_TEXTURE_2D, pero en vez de eso hablaremos de El Listado 1 nos enseña como adecuar la anterior
GL_TEXTURE_2D; el segundo parámetro es usado cuando definición de la clase TTextura para trabajar con el método
utilizamos texturas de múltiples resoluciones. Estas son glBindTexture(), para lo cual definimos un arreglo llamado
utilizadas para el Mipmapping, algo de lo que hablaremos Texts, el cual nos servirá para almacenar hasta cien texturas.
un poco después, cuando ponemos este parámetro en 0 Como ven glBindTexture() nos sirve tanto para definir la
indicamos que trabajaremos con sólo una resolución. textura a utilizar al crearla como al utilizarla, ya que se
El siguiente parámetro indica que valores de componentes 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, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
end ;
dBaja:
begin
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
end;
end;

glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, ModeEnv);

glBindTexture(GL_TEXTURE_2D, texts[TexID]);
glenable(GL_TEXTURE_2D);
end;

Procedure TTextura.InhibeTexturas;
begin
gldisable(GL_TEXTURE_2D);
end;

En este ejemplo utilizamos la librería GLUT, por lo El Listado 2 nos muestra el código de nuestro programa
tanto, la ventana en la cual mostraremos la animación la principal, y en el vemos que tenemos declarados
crearemos en tiempo de ejecución, y ese código lo distintos procedimientos, el primero de ellos es Display(),
escribiremos directamente en el código de nuestro que corresponde al procedimiento que dibujará la
proyecto DPR. 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, llamadas a los procedimientos que crearán nuestra ventana
la ventana en la cual mostraremos la animaci ón la de despliegue, y a las funciones de dibujo y animación:
crearemos en tiempo de ejecución, y ese código lo glutInitDisplayMode() sirve para inicializar el contexto y los
escribiremos directamente en el código de nuestro modos de despliegue a utilizar, glutInitWindowSize() sirve para
proyecto DPR. determinar las dimensiones de alto y ancho de nuestra
El Listado 2 nos muestra el código de nuestro programa ventana; glutInitWindowPosition() para la posición de esta
principal, y en el vemos que tenemos declarados distintos (Obvio, ¿no?); glutCreateWindow() para crear propiamente la
procedimientos, el primero de ellos es Display(), que ventana pasando como parámetro el titulo de esta;
corresponde al procedimiento que dibujará la escena, en glutDisplayFunc() para definir que procedimiento se encargará
este podemos observar que ajustamos los criterios de de dibujar la escena; glutIdleFunc() para definir la función de
repetición para las coordenadas S y T de acuerdo a la animación, y por último glutMainLoop(); ¡para empezar con el
selección de un RadioGroup en la ventana de herramientas, show! La Figura 9 muestra nuestra aplicación en
esto usando el método glTexParameterf(), del mismo modo ejecución.
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
Figura 9 .- La aplicación corriendo con un modo de
volver a inhibir este cálculo con glDisable() con los mismos
ambiente de GL_BLEND
parámetros. Como estamos trabajando con GLUT
debemos llamar al método GlutSwapBuffers() para vaciar el Tengamos en cuenta que ahora estamos aprovechando las
contenido del frame buffer en nuestra ventana. características de cada uno de nuestros equipos, ya que no
El procedimiento Animación() será esta vez el que coordine usamos un TTimer para controlar la animación y así
las animaciones en nuestra escena; como vemos, este hacemos que esta vaya a la velocidad que el mismo
modifica algunos parámetros que usamos para definir las equipo soporte (que puede ser a más de un cuadro por
coordenadas de textura de la figura de fondo, lo que nos milisegundo como con el Timer). Saludos y hasta pronto.
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

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 cuestión, y después de haber sido influenciado por haber
combinación de dos conceptos muy interesantes en el visto repetidas veces los manuales de “Apague la tele, no
desarrollo de gráficos: las luces, y las texturas. sea holgazán, y hágalo usted mismo”; en este número
Ciertamente aún tenemos mucho que decir respecto a veremos como podemos agregar un método que haga esto
todas las posibilidades que nos ofrece el amplísimo tema a la clase que hemos venido manejando para el trabajo con
de la texturización, pero ya las iremos mencionando a su texturas. Tendremos oportunidad de ver que, en realidad,
tiempo, por ahora repasaremos algo de lo que ya hemos este trabajo no resulta tan complicado y que podemos
venido hablando, y evaluaremos de lo que somos capaces hacerlo sin mayores problemas, y con esto mismo
de hacer a estas alturas. elevaremos la capacidad en cuanto a tamaño de imágenes
Aprovecharemos igual, para seguir profundizando en el soportadas para la clase, usando un manejo dinámico de la
estudio de GLUT, que como ya hemos visto puede memoria, en vez de usar arreglos estáticos para hacer las
solucionarnos muchos problemas a la hora de programar veces de buffer de pixeles.
gráficos si sabemos usar la librería pertinentemente.
Las texturas iluminadas...
Antes de empezar: Carga dinámica de Aunque esto pudiera sonarnos a “Imágenes en el
bitmaps como texturas. Nirvana” la realidad es que es muy posible combinar
En los primeros artículos de esta serie hablábamos de las luces, brillos y texturas haciendo una remembranza a
librerías que componen a OpenGL, y hab íamos través de lo que ya hemos estudiado hasta ahora.
mencionado a GLU y a GLUT, ¿recuerdan?; pues bien, Como recordarán, para iluminar un objeto se necesita una
existe una más que no es tan conocida llamada GLAUX, fuente de luz, y definir las características de reflexión del
en esta librería al igual que las otras lo que encontramos material del cual estará compuesto el objeto, y después de
son algunos procedimientos para crear figuras eso, las normales hacen todo el trabajo. Pero ¿Qué
prediseñadas, como esferas, cubos, etc. Pero una hacemos si además queremos poner una imagen sobre el
peculiaridad con la que cuenta, es que tiene un método material, pero queremos conservar las propiedades del
que permite cargar bitmaps directamente desde archivos material original del objeto?; pues bien, el preservar las
BMP. características originales del material dependerá de la
OpenGL provee de un tipo para trabajar con bitmaps función de ambiente que utilicemos para la textura.
llamado GLBitmap, y GLAUX nos proporciona métodos para Si usásemos GL_DECAL o GL_REPLACE definitivamente se
crear objetos GLBitmap directamente desde archivos, para ser perderían los valores asignados al material, y
utilizados en texturas. Ahora como todo, el uso de esta obtendríamos de resultado en la escena un objeto que
librería tiene sus ventajas y también sus desventajas; una parece no ser afectado por las luces, ya que la textura,
de las ventajas sería que usando las funciones que nos simplemente, reemplaza los píxeles del material con los
provee podemos desentendernos de la adecuación en píxeles de la textura sin tomar ninguna otra clase de
memoria de la carga de imágenes para las texturas consideraciones, así que en nuestro caso eso no nos
llamando simplemente a un método que hace todo el serviría.
trabajo por nosotros; pero una desventaja (que sí resulta En cambio, si usamos GL_MODULATE o GL_BLEND ¡los valores
significativa) es que para usarla necesitamos incluir la se conservan y se respetan!, veamos por qué:
librería GLAUX.DLL en nuestro proyecto, con el En lo que es el esquema de secuencia de operaciones del
consabido incremento en tamaño y espacio del mismo, motor gráfico de OpenGL, las operaciones de
donde además irán incluidas otras funciones, aparte de las texturización son las últimas que se aplican a los cuerpos
utilizadas, que tal vez nunca lleguemos a requerir. antes de ser presentados, lo cual significa que antes de
Por lo mismo, después de haber analizado el problema en 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 funciones que esta nos proporciona, ahora mencionaremos
aplicamos las texturas usando alguna de estas funciones de algunas otras funciones y empezaremos a ver como
ambiente, lo que hacemos es interpolar los valores de los podemos responder a eventos del usuario, como el pulso
píxeles ya calculados, con los valores de los texeles de una tecla o de algún botón del ratón. En esta ocasión
correspondientes, obteniendo así la ilusión de que los abordaremos las funciones de interacción con el teclado.
efectos de iluminación también han sido aplicados a la En el Listado 1 tenemos el código fuente de nuestro
textura. Si combinamos la utilización de estas funciones programa principal y en el podemos apreciar que se
de ambiente, podemos obtener diversos efectos en cuanto utilizan algunas funciones de GLUT que pueden resultar
a la manera en como se ven afectados los colores un tanto nuevas para nosotros; como por ejemplo es el
originales de la textura, combinándose con los colores de caso de: glutFullscreen(); esta función convierte nuestro
los materiales. programa en una aplicación de pantalla completa. Lo que
hace es adecuar el área de despliegue a la resolución activa
Más sobre GLUT... al momento de ejecutar el programa, por lo que no nos
permite tener pleno control del modo de despliegue a usar,
El mes pasado empezamos a estudiar la librería GLUT y
y si la resolución es muy alta, veremos las animaciones de
cómo se utilizan y para qué sirven algunas de las
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 // Color en formato RGB
GLUT_DOUBLE or // con Doble Buffer
GLUT_DEPTH); // Y un buffer para la prueba de profundidad...
// Y creamos la ventana...
glutCreateWindow('VentanaGL');

// inicializamos el ambiente de OpenGL...


glInit();

// Preguntamos si queremos mostralo a pantalla completa


if (MessageBox(0, '¿A pantalla completa?', 'VentanaGL', MB_YESNO or MB_ICONQUESTION) = IDYES) then
begin
glutFullscreen();
end;

// Definimos las funciones de ajuste de tamaño,


glutReshapeFunc(glResizeWnd);

// de teclas presionadas,
glutKeyboardFunc(glKeyPress);

// para teclas especiales,


glutSpecialFunc(glSpecialKey);

// para la función de dibujo


glutDisplayFunc(glDraw);

// y una más para saber si nuestra ventana está visible o no...


glutVisibilityFunc(glVis);

//Y empezamos con el Show...


glutMainLoop;
end.

En futuros artículos estudiaremos más a fondo la manera propio método glSpecialKey(), sirve para aquellos casos en los
de hacer aplicaciones de pantalla completa con m ás que la tecla pulsada lo que devuelve es un c ódigo de
cuidado y procurando acelerar los gráficos, por lo pronto rastreo, pero no un valor específico, para estos casos GLUT
esta queda como una premisa y una manera cuenta con su propia definición de constantes, y así
exageradamente sencilla de hacerlo. podemos preguntar si la tecla presionada ha sido alguna
Siguiendo con las funciones nuevas nos encontramos con tecla de función, o de manejo de cursor, entre otras.
los procedimientos glutKeyboardFunc() y glutSpecialFunc() los cuales Observen como ambos métodos devuelven tres parámetros,
se utilizan para definir otros procedimientos que respondan ya que además, como información adicional, nos informa
a los eventos del teclado. El primero de estos de la coordenada del ratón al momento de presionar dicha
(glutKeyboardFunc) funciona y es aplicable para todos aquellas tecla, esto puede resultar de mucha utilidad en la práctica,
teclas presionadas que devuelvan algún byte como valor, ya lo veremos.
como por ejemplo todas aquellas que entran dentro de las Y por último, entre la nuevas funciones que mostramos
constantes VK_* por todos conocidas, incluso en el aquí está glutVisibilityFunc(), la cual nos devuelve un valor
ejemplo vemos como preguntando si la tecla ha sido la de booleano, que nos informa si la ventana creada con GLUT
Escape (VK_SCAPE) terminamos la aplicación. El segundo es visible o no lo es. Uno de los casos es los que no es
procedimiento (glutSpecialFunc), que asociamos a nuestro visible una ventana, es por ejemplo cuando esta est á

74
Luces y Texturas con OpenGL

minimizada; entonces no tiene sentido seguir molestando la imagen y solo transformarla y agregar la característica
al procesador con cálculos de los que el usuario ni siquiera de almacenamiento dinámico, pero por lo menos en esta
se va a enterar, por lo tanto cuando eso suceda lo que nos ocasión lo haremos de la manera “difícil”, a fin de
conviene es reasignar (o más bien inhibir) la función enterarnos también como está constituido un archivo de
encargada de las animaciones, ¿no les parece?. Esa es una Mapa de Bits físicamente.
de las características que hacen fuerte el sistema de Como ustedes saben “Hay mas de una manera de pelar un
asignación de procedimientos de GLUT, la facilidad con la gato”...¿Alguien sabe quién dijo eso?... seguramente
que se puede cambiar el comportamiento de la aplicación alguien que tenía preferencia gastronómica por los felinos,
en general. ¡que raras costumbres!, ¿no les parece?, ¡pero en fin!, creo
que estoy divagando... el caso es que siempre hay más de
¿Y qué cambiamos en la definición de una forma de hacer las cosas, y es por eso que aqu í
estaremos presentando diversas variaciones que pueden ser
nuestra clase? aplicables a la definición de nuestra clase, ya sea para
El Listado 2 nos muestra las adecuaciones que debemos adecuarla a nuevas necesidades, o bien para simplemente
hacer a nuestra clase TTextura para poder cargar de mostrar una manera diferente de hacer lo mismo, para que
manera dinámica las texturas directamente desde archivos cada quien decida como es que le conviene más hacer su
de Mapas de Bits. propio trabajo.
La manera más simple de hacerlo, hubiera sido utilizar un A través de los artículos de esta serie hemos hecho
objeto de la clase TBitmap y a partir de este objeto cargar 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, 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;

Destructor TTextura.Free;
begin
IF Assigned(PData) then
FreeMem(PData,BitmapLength);
end;

end.

mostraremos una combinación de algunas versiones a la aplicación el objeto responsable de la nueva instancia)
anteriores con nuevas características como la carga desde el propio constructor, para que sea la misma
dinámica, y el acceso a archivos de mapas de bits. aplicación quien lo libere cuando ésta termine, con eso
Observando el Listado 2 podemos notar que la primera “santo remedio”.
diferencia significativa resulta ser que hemos cambiado la En cuanto a las propiedades, hemos eliminado el arreglo
clase padre de TTextura, y ahora usamos TComponent en vez de estático que usábamos como buffer y en vez de eso
TObject , pero esto tiene su justificaci ón, y ahora la utilizamos un puntero (pdata) y un valor numérico, que
explicamos: indique el tamaño del buffer dinámico (bitmaplength). Con
Como recordarán, ahora estamos trabajando la aplicación esto hacemos un tanto más eficiente el uso del espacio en
a través de GLUT, y por lo tanto estamos un tanto memoria requerido para el objeto, ya que no
restringidos a los eventos que son soportados por esta desperdiciamos bytes con texturas pequeñas.
librería, y por lo mismo, ya que esta no maneja algún La estructura de un archivo BMP es en realidad bastante
evento para la finalización de la aplicación, puesto que simple, ya que basta con leer estructuras predefinidas para
esto es manejado directamente por la instrucci ón obtener la información de la imagen. En la clase TTextura
glutMainLoop(), no tenemos modo de enterarnos de cuando la definimos un método para acceder exclusivamente a un
aplicación termina. Como pueden observar en el Listado archivo BMP (LoadBitmap), en este podemos ver como se
2, para esta definición de la clase ya implementamos un abre el archivo solo para lectura, y se empiezan a leer
método Destructor (Free), en el cual liberamos cierto sector estructuras, como fileheader de tipo BITMAPFILEHEADER y
de la memoria reservada para el buffer de texeles, y que es después infoheader de tipo BITMAPINFOHEADER, donde de este
necesario llamar antes de que la aplicación termine, para último obtenemos el ancho y alto en píxeles de la imagen
que no quede esa región como “laguna perdida”. Para contenida en el archivo.
evitar esto lo que podemos hacer es derivar de la Clase Justo después de haber leído la paleta de colores usada en
TComponent y, al momento de crear nuestro objeto de el archivo, empezamos a leer propiamente los píxeles de
textura, “colgarlo” de la misma aplicación (es decir, hacer la imagen, y leemos tantos valores como hayamos

78
Luces y Texturas con OpenGL

calculado que existen en BitmapLength, y aprovechamos que hemos aprendido


el procedimiento ReadFile() carga directamente esos valores podrán hacer cosas aún
en el buffer que señala pdata. Por el mismo hecho de que más interesantes, todo
este procedimiento (LoadBitmap) es llamado por otro más es cuestión de tener
general (CargadeArchivo) que es el que convierte propiamente imaginación.
el buffer en una textura, este bien podría ser declarado Hasta el próximo mes.
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

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 Silicon, Alpha o Sun, sistemas operativos Irix, Motif,
interesante sobre los gráficos 3D con OpenGL: Las Listas Linux... y todo a la vez. Y es que la unión hace la fuerza
de Despliegue, algo que es muy propio de OpenGL, y que ¿¿no??
brinda muchas ventajas si se sabe utilizar
pertinentemente. Listas de Despliegue ó "Display lists" en
También hablaremos acerca de la librería GLAUX que OpenGL.
tanto mencionamos el mes pasado, y que en vista del
Las listas de despliegue de OpenGL ilustran muy
interés despertado entre la comunidad estudiaremos esta
claramente como podemos utilizar la filosofía cliente-
vez con mayor profundidad, haciendo una nueva
servidor para nuestros gráficos.
adecuación a nuestra clase de manejo de texturas: TTextura,
Supongamos que tenemos un computador dedicado única
para poder interactuar con dicha librería.
y exclusivamente a dibujar primitivas en el tubo de rayos
catódicos (CRT) de nuestro monitor. Supongamos que
¿Una Arquitectura Cliente-Servidor en esta máquina cuenta con un muy limitado juego de
OpenGL?... instrucciones de programación que sabe ejecutar. Tan
En una red contamos con computadores servidores sólo sabe dibujar lo que le manden. Otro computador
(servers) que llevan a cabo acciones demandadas por compila y ejecuta el programa, genera unos resultados y
computadores clientes (clients). Si yo quiero imprimir un le manda las correspondientes instrucciones para
documento y la impresora se encuentra f ísicamente dibujarlos al Display Processor (computador dedicado en
ubicada en otro edificio, tendré que dialogar usando mi exclusiva a renderizar por pantalla ). Estas instrucciones
computador (cliente), con otro computador (servidor) se almacenan en una memoria de render (display
para que me permita mandarle mi documento y lo memory) en forma de fichero/lista de primitivas (display
imprima por mí. file o display list).
El hecho de contar con diversos servidores y muchos Así cada vez que le mandemos la lista de despliegue al
clientes configura una red (network) donde todos los procesador de despliegue (display processor), éste ya se
esfuerzos se comparten, se distribuyen y así se consigue encargará de dibujar cuando lo crea conveniente y
un comportamiento óptimo, veloz y ¡¡barato !! nosotros quedamos libres para dedicarnos a otros
Una estación de trabajo que cuente con una pantalla, un quehaceres.
teclado, un ratón ... puede actuar perfectamente como un El Procesador de despliegue (display processor) mandará
servidor de gráficos. Esta máquina nos provee de dibujar la lista de despliegue a una frecuencia lo
servicios de salida gráfica en pantalla, y de entrada suficientemente alta como para evitar parpadeo en
gracias al teclado y al ratón. Estos servicios podrán ser pantalla, y lo hará solito. Así nosotros le decimos lo que
requeridos por cualquier cliente que pertenezca a la red. tiene que dibujar una sola vez y él ya lo hace
Nuestras aplicaciones OpenGL son clientes que usan al repetidamente. ¿No les parece que nos ahorramos así
servidor de gráficos para ejecutarse y visualizarse. mucho trabajo?
Pensemos que deberíamos ser siempre capaces de Veamos la Figura 1. Tal como decía, nosotros asumimos
ejecutar la misma aplicación en diferentes servidores de la el papel cliente ejecutando el programa, generamos una
red. Esta es la filosofía interna de trabajo de nuestra lista de despliegue con lo que hay que dibujar, se lo
querida librería. enviamos a la DPU (Display Processor Unit - Unidad de
Para la realización de grandes producciones Proceso de Renderizado) ¡¡y que dibuje!!
cinematográficas plagadas de gráficos (simulación física) Hoy por hoy lo que se llamaba DPU se ha sustituido por
como Titanic, Spawn o Perdidos en el Espacio, se un computador servidor de gráficos mientras que lo que
necesitaron decenas de computadores conectados en en la figura mencionamos como host es nuestro
paralelo, compartiendo memoria y procesadores, y computador cliente. Los problemas que nos encontramos
calculando como locos 24 horas al d ía. Estaciones 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);
práctica de importar texturas en OpenGL a partir de
glBegin(GL_POLYGON); archivos de mapas de Bits (BMP) era usar la librería
glColor3f(0.0, 0.0, 1.0); GLAUX. Como en esa ocasión comentábamos una de las
glVertex3f(-10.0, -10.0, 0.0); desventajas que la utilización de esta librería conlleva es
glVertex3f(10.0, -10.0, 0.0);
glVertex3f(10.0, 10.0, 0.0); la dependencia de una nueva DLL en nuestros proyectos
glVertex3f(-10.0, 10.0, 0.0); (GLAUX.DLL), con el consabido aumento en espacio que
glEnd( ); esto representa.
glEndList( ); 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
Es un polígono que consiste en un cuadrado de lado 20 y librería para cargar texturas en nuestras aplicaciones. Para
de color azul que se encuentra situado sobre el plano Z=0. eso, veamos el Listado 1.
Iniciamos la lista con glNewList() pasándole el nombre que
unit Texturas;
tendrá esta lista, en nuestro caso usamos CUADRADO, y
también el parámetro GL_COMPILE. Éste se encargará de que interface
se envíe la lista al servidor para que la guarde pero que no
uses OpenGL, windows, Graphics, SysUtils, Dialogs,Classes, GLAux;
la dibuje hasta que se lo ordenemos.

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 Ahora dentro de la definición del método CargadeArchivo(),
en esta ocasión a nuestra clase TTextura, es solo modificar hemos declarado una variable llamada TextureImage,
un poco el método CargadeArchivo() el cual hemos también del tipo PTAUX_RGBImageRec y como se
transformado en una función booleana, que nos indicará imaginarán lo que hacemos en el cuerpo de la función lo
si es que la carga de la imagen en la textura se ha que hacemos es cargar la estructura de la imagen con la
realizado de manera exitosa o no. El resto de la función que acabamos de estudiar: LoadBMP(). ¡Tan simple
definición de la clase es prácticamente la que todos ya como eso!
conocemos. Una vez hecho esto, ya podemos acceder a las
También, como una función adicional hemos declarado propiedades de la imagen como lo son su ancho, alto y
LoadBMP(), la cual es una función que nos devuelve un los píxeles de los que está compuesta. Como nuestra
apuntador a una estructura, de tipo PTAUX_RGBImageRec, variable en realidad es un puntero para acceder a los
que es la estructura que almacenará toda la información elementos de la estructura que contiene a la imagen
de la imagen que hallamos pasado como referencia en el tenemos que utilizar el símbolo ^. Así, para referenciar
único parámetro que definimos para esta función. al ancho de la imagen debemos usar: TextureImage^.sizeX; Y
Aunque esta función solo la utilizamos (entre otras si recordamos en el procedimiento glTexImage2D() debemos
cosas) para validar que el archivo exista, puesto que la pasar como parámetros tanto el ancho ( SizeX en la
función que realmente hace el trabajo pesado es estructura), como el alto (SizeY), y los píxeles en sí de la
auxDIBImageLoadA(), esta es la que se encarga de cargar la imagen (Data).
imagen en la estructura. 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 Ya hemos visto todas las virtudes de las que podemos
correspondiente a este artículo disponible en el mismo colmar a las listas de despliegue, pero ahora en el Listado
sitio de esta revista. 2 veremos algunos ejemplos donde estas son aplicables, y
otros donde no resultan serlo tanto.
Las “Display Lists” y sus excepcionales De entrada lo que vemos es que hemos definido en este
desventajas... no todo es miel sobre listado tres formas de hacer “lo mismo”, una de la manera
tradicional, otra usando procedimientos auxiliares, y una
hojuelas...

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 posición.


que hacemos es definir un cuadrado texturizado. Como ven esto de usar procedimientos algunas veces es
En el evento OnCreate del formulario hemos puesto una más conveniente que usar listas de despliegue, todo
definición de lo que sería una lista de despliegue, con un depende de las necesidades que tengamos al momento de
elemento llamado Roca, en el cual hemos empotrado las dibujar. Como en este caso que aunque necesitábamos
instrucciones para dibujar un cuadrado de lado 2 con su dibujar un cuerpo varias veces en la escena, no podíamos
respectivo mapa de textura. Esta lista de despliegue la usar listas de despliegue porque estas no soportaban el
utilizamos para dibujar las paredes que aparecerán en mapeo dinámico de texturas para simular el flujo de
nuestra escena. Como estas son figuras estáticas no hay agua.
problema y podemos dibujarlas usando las “display lists” Saludos y como dirían al norte de mi pueblo: Happy
Siguiendo en orden descendente encontramos el Programming
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 Figura 2.- Nuestra aplicación en ejecución
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

87

Вам также может понравиться