Capítulo 6.
OpenGL y Delphi 4.
Bienvenidos a una nueva entrega
del curso de Delphi.
Como los tiempos de actualización
en la informática siempre están presentes, a partir de ahora,
y tras las introducciones anteriores a Delphi 4, sobre esta nueva versión
es sobre la que vamos a trabajar.
Como ya habréis comprobado
si habéis echado un vistazo a los anteriores capítulos, este
curso es un curso atípico. En él estamos tratando muchos
y variados temas del mundo de la programación, desde la interpretación
de ficheros MP3 y la creación de componentes personalizados, hasta
lo que vamos a ver a continuación: OpenGL
OpenGL es, al igual que las
más conocidas DirectX, una poderosa librería de programación
para crear representaciones tridimensionales.
Pero aquí no vamos
a explicar lo que es OpenGL, ya que para eso está el curso de Oscar
sobre OpenGL, aquí en Macedonia, el cual trata el aspecto teórico
de lo que hay que conocer para entender lo que es una representación
tridimensional.
Y llegado aquí, he
de reconocer, que, gracias a su curso, fue como me interesé por
OpenGL... sobre todo porque sus ejemplos estaban en C y yo decidí
que: ¿por qué no tratar de hacer lo mismo pero en Delphi.
¡Pues dicho y hecho!
¡Ahora tenemos el
siguiente artículo, fruto de la curiosidad y del afán de
demostrar que este lenguaje es capaz de estar a la altura de otros en cualquiera
de los temas variados de que consta el mundo de la informática!
Pero antes que nada, os
recomiendo que os paséis por el curso de OpenGL de Oscar, ya que
su aprendizaje es requisito mínimo para comprender el código
que se incluye aquí.
¿Y DirectX?
Otro punto que me gustaría
que conociérais es una duda que tuve en mi largo año ya de
programación con OpenGL: ¿y por qué OpenGL en lugar
de DirectX?
Pues la respuesta es muy
sencilla. Primero empecé con OpenGL, y cuando comencé a hacer
pruebas con DirectX (Direct3D en mi caso particular)... bueno... pues que
de cada 3 ejecuciones de prueba me cascaba el ordenador 2 veces.
Direct3D es tremendamente
complejo, y no es que esto sea un fallo, el fallo es que es tremendamente
complejo cuando no es necesario.
Comparando la elegancia
de OpenGL(y eso que tambien puede parecer confuso) con las barrabasadas
de código de Direct3D, es algo así como ver un código
fuente en Delphi, y otro que haga lo mismo, pero en Visual? C++, y es que
Direct3D es como una extensión de C++ y toma de él su filosofía
de locura de código (y lo dice un ex–enamorado del C++).
Una cosa que hay que tener
en cuenta, es que todos los programas que utilizan OpenGL se basan en unas
DLLs que se encuentran en el directorio System si la versión de
Windows es la OSR2 o superior. Por esto, no es necesario preocuparse de
tener que instalar nada adicional.
GLUT, GL y GLU
No, no me estoy ahogando
;-)
GLUT es una librería
de utilidades freeware. Está disponible para la mayoría de
los sistemas operativos que soportan OpenGL, y proporciona una gran portabilidad
(a través de C/C++) debido a que es independiente del sistema operativo.
Aunque existe una unidad
GLUT para Delphi, no es necesario utilizarla (para algo es un lenguaje
superior), debido a que las capacidades estándar de Delphi nos proporcionan
mecanismos para utilizar directamente los mensajes de repintado (WM_PAINT)
y de otro tipo, que suplen esa necesidad.
En el directorio System,
se encuentra el fichero GLU32.DLL, que es la versión de Microsoft
que implementa la librería de OpenGL desarrollada por Silicon Graphics.
Tambien es posible instalar las librerías de Silicon, que por cierto
y cómo no, son superiores a las de Microsoft, pero vamos a utilizar
lo que disponemos todos en nuestro Windows.
Para poder utilizar esta
DLL, nos bastaría con saber cuáles son las rutinas que implementa,
y la sintáxis para llamarlas, con lo que un vistazo al curso de
OpenGL de Oscar sería necesario.
Pero debido a que Delphi
cada vez es más popular, existen cientos de sitios de internet en
los que encontrar unas unidades (.PAS) que se han difundido como la pólvora
entre todos los usuarios de Delphi.
Se trata de GL.PAS y GLU.PAS.
Han sido desarrolladas por:
Artemis Alliance, Inc.
289 E. 5th St, #211
St. Paul, Mn 55101
(612) 227-7172
71043.2142@compuserve.com
Estas unidades de las cuales
no tenemos que preocuparnos ya están incluidas en este artículo,
nos facilitarán la tarea (aún más) de importar y llamar
las cabeceras de los procedimientos, funciones y tipos de datos de la librería
DLL.
Para su uso, tan solo hay
que copiarlas en el directorio "Lib" de Delphi, siendo compatibles con
las versiones 2, 3 y 4.
Yo he programado en OpenlGL
desde C++
Pues si este es tu caso,
encontrarás que todo lo que hacías antes podrás hacerlo
ahora sin ninguna limitación. Tan solo habrá que acostumbrarse
a la sintaxis de Delphi a la hora de llamar a las funciones.
Lo suyo sería echar
un vistazo a los dos ficheros anteriores, para comprobar las diferencias.
Pero seguro que al poco tiempo los programadores de C++ se echarán
las manos a la cabeza al ver que no existen ni la mitad de las parafernalias
que hay que armar para inicializar el formulario en el que mostrar "algo"
en OpenGL... y es que eso lo hace Delphi por nosotros.
En cuestiones de velocidad,
no puedo decir que sean más rápidas las ejecuciones en Delphi
que en algún compilador de C++ cuando usamos OpenGL, ya que ni siquiera
me he preocupado de usar el C++ para esto. Pero lo que sí se puede
asegurar es que como mínimo, Delphi irá igual de rápido...
y ya no hablemos del tiempo dedicado a la programación, que en este
caso, con absoluta seguridad, Delphi consume menos tiempo... y menos quebraderos
de cabeza.
¡Codigo, quiero Código!
Pues aquí lo tenéis,
y en el cual podréis ver cómo se utiliza OpenGL.
En esta unidad que viene
a continuación lo que se hace es mostrar un conjunto de cubos que
forman un cubo a su vez, es decir, un cubo formado por 3 cubitos de ancho
y 3 de largo bajo los efectos de un foco de luz y que rota sobre uno de
sus ejes.
Si se redimensiona el formulario,
el tamaño de los cubos cambia de forma proporcional.
Los que sepan OpenGL, podrán
notar el uso del renderizado de listas de objetos.
El código tiene un
nivel muy avanzado, no apto para los no iniciados, ya que hace uso del
sistema de captura de mensajes de Windows, desde Delphi, así como
de opciones avanzadas del propio Windows.
Si se quiere utilizar este
código para ver los resultados, descargarlo aquí: Demo.zip
(No olvidar grabar los ficheros
GL.PAS y GLU.PAS en el directorio Lib de Delphi, y descargarlas aquí:
GL.zip)
¡Pues aquí tenéis
el código!
unit demo;
interface
uses
Windows, Messages, SysUtils,
Classes, Graphics, Controls, Forms, Dialogs, GL, GLU, ExtCtrls;
const
DRAWCUBE = 1;
type
TfrmCubo = class(TForm)
Timer: TTimer;
procedure FormCreate(Sender:
TObject);
procedure FormResize(Sender:
TObject);
procedure FormDestroy(Sender:
TObject);
procedure TimerTimer(Sender:
TObject);
private
DC: HDC;
hrc: HGLRC;
Palette: HPALETTE;
Angle: GLfloat; // Lo usaremos
para saber el ángulo de rotación en
// el que se encuentran
las figuras
procedure CreateDisplayList;
// Aquí crearemos la estructura de cubos
procedure DrawScene;
// Para dibujar la estructura
procedure InitializeRC;
procedure SetDCPixelFormat;
protected
// Cazamos los mensajes
que Windows manda al formulario
procedure WMPaint(var
Msg: TWMPaint); message WM_PAINT;
procedure WMQueryNewPalette(var
Msg: TWMQueryNewPalette); message WM_QUERYNEWPALETTE;
procedure WMPaletteChanged(var
Msg: TWMPaletteChanged); message WM_PALETTECHANGED;
public
end;
var
frmCubo: TfrmCubo;
implementation
{$R *.DFM}
procedure TfrmCubo.SetDCPixelFormat;
var
hHeap: THandle;
nColors, i: Integer;
lpPalette: PLogPalette;
byRedMask, byGreenMask,
byBlueMask: Byte;
nPixelFormat: Integer;
pfd: TPixelFormatDescriptor;
begin
FillChar(pfd, SizeOf(pfd),
0);
with pfd do
begin
nSize := sizeof(pfd);
// Tamaño de esta estructura
nVersion := 1; // Número
de Versión
dwFlags := PFD_DRAW_TO_WINDOW
or
PFD_SUPPORT_OPENGL or
PFD_DOUBLEBUFFER; // Flags
iPixelType:= PFD_TYPE_RGBA;
// Valores RGBA pixel
cColorBits:= 24; // 24-bit
color
cDepthBits:= 32; // 32-bit
depth buffer
iLayerType:= PFD_MAIN_PLANE;
// Tipo de Layer
end;
nPixelFormat :=
ChoosePixelFormat(DC, @pfd);
SetPixelFormat(DC, nPixelFormat,
@pfd);
DescribePixelFormat(DC,
nPixelFormat, sizeof(TPixelFormatDescriptor), pfd);
if ((pfd.dwFlags
and PFD_NEED_PALETTE) <> 0) then
begin
nColors := 1 shl
pfd.cColorBits;
hHeap := GetProcessHeap;
lpPalette := HeapAlloc(hHeap,
0, sizeof(TLogPalette) + (nColors * sizeof(TPaletteEntry)));
lpPalette^.palVersion :=
$300;
lpPalette^.palNumEntries
:= nColors;
byRedMask := (1 shl pfd.cRedBits)
- 1;
byGreenMask := (1 shl pfd.cGreenBits)
- 1;
byBlueMask := (1 shl pfd.cBlueBits)
- 1;
for i := 0 to
nColors - 1 do
begin
lpPalette^.palPalEntry[i].peRed
:= (((i shr pfd.cRedShift) and byRedMask) * 255) DIV byRedMask;
lpPalette^.palPalEntry[i].peGreen
:= (((i shr pfd.cGreenShift) and byGreenMask) * 255) DIV byGreenMask;
lpPalette^.palPalEntry[i].peBlue
:= (((i shr pfd.cBlueShift) and byBlueMask) * 255) DIV byBlueMask;
lpPalette^.palPalEntry[i].peFlags
:= 0;
end;
Palette := CreatePalette(lpPalette^);
HeapFree(hHeap, 0, lpPalette);
if (Palette <>
0) then
begin
SelectPalette(DC,
Palette, False);
RealizePalette(DC);
end;
end;
end;
procedure TfrmCubo.InitializeRC;
const
glfLightAmbient
: Array[0..3] of GLfloat = (0.1, 0.1, 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.0, 0.0, 0.0, 1.0);
begin
// Habilita "Depth
testing" y "Backface culling"
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
// Ponemos una luz en la
escena, para que se vean los objetos
glLightfv(GL_LIGHT0, GL_AMBIENT,
@glfLightAmbient);
glLightfv(GL_LIGHT0, GL_DIFFUSE,
@glfLightDiffuse);
glLightfv(GL_LIGHT0, GL_SPECULAR,@glfLightSpecular);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
end;
procedure TfrmCubo.CreateDisplayList;
const
glfMaterialColor:
Array[0..3] of GLfloat = (0.0, 0.0, 1.0, 1.0);
begin
// Creamos la lista
de representación
glNewList(DRAWCUBE, GL_COMPILE);
// Definimos las propiedades
de reflexión de las caras de cada cubo
glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE,
@glfMaterialColor);
// Un cubo tiene 6 caras,
así que vamos a montarlo
// Cada cara es un polígono
glBegin(GL_POLYGON);
glNormal3f(0.0,
0.0, 1.0);
glVertex3f(1.0, 1.0, 1.0);
glVertex3f(-1.0, 1.0, 1.0);
glVertex3f(-1.0, -1.0, 1.0);
glVertex3f(1.0, -1.0, 1.0);
glEnd;
glBegin(GL_POLYGON);
glNormal3f(0.0,
0.0, -1.0);
glVertex3f(1.0, 1.0, -1.0);
glVertex3f(1.0, -1.0, -1.0);
glVertex3f(-1.0, -1.0, -1.0);
glVertex3f(-1.0, 1.0, -1.0);
glEnd;
glBegin(GL_POLYGON);
glNormal3f(-1.0,
0.0, 0.0);
glVertex3f(-1.0, 1.0, 1.0);
glVertex3f(-1.0, 1.0, -1.0);
glVertex3f(-1.0, -1.0, -1.0);
glVertex3f(-1.0, -1.0, 1.0);
glEnd;
glBegin(GL_POLYGON);
glNormal3f(1.0,
0.0, 0.0);
glVertex3f(1.0, 1.0, 1.0);
glVertex3f(1.0, -1.0, 1.0);
glVertex3f(1.0, -1.0, -1.0);
glVertex3f(1.0, 1.0, -1.0);
glEnd;
glBegin(GL_POLYGON);
glNormal3f(0.0,
1.0, 0.0);
glVertex3f(-1.0, 1.0, -1.0);
glVertex3f(-1.0, 1.0, 1.0);
glVertex3f(1.0, 1.0, 1.0);
glVertex3f(1.0, 1.0, -1.0);
glEnd;
glBegin(GL_POLYGON);
glNormal3f(0.0,
-1.0, 0.0);
glVertex3f(-1.0, -1.0, -1.0);
glVertex3f(1.0, -1.0, -1.0);
glVertex3f(1.0, -1.0, 1.0);
glVertex3f(-1.0, -1.0, 1.0);
glEnd;
glEndList();
end;
procedure TfrmCubo.DrawScene;
var
i, j, k: GLdouble;
begin
// Hay que crear
los bufers de profundidad y color
glClear(GL_COLOR_BUFFER_BIT
or GL_DEPTH_BUFFER_BIT);
// Y definir el modelo de
transformación
glMatrixMode(GL_MODELVIEW);
glLoadIdentity;
glTranslatef(0.0, 0.0, -32.0);
glRotatef(30.0, 1.0, 0.0,
0.0);
// Rotamos la escena con
el ángulo apropiado
glRotatef(Angle, 0.0, 1.0,
0.0);
// Ahora dibujamos un array
tridimensional de cubos
i := -3.0;
while (i <= 3.0)
do
begin
j := -3.0;
while (j <= 3.0)
do
begin
k := -3.0;
while (k <= 3.0)
do
begin
glPushMatrix;
glTranslatef(i, j, k);
glCallList(DRAWCUBE);
glPopMatrix;
k := k + 3.0;
end;
j := j + 3.0;
end;
i := i + 3.0;
end;
// Intercambiamos los buffers
de representación
SwapBuffers(DC);
end;
procedure TfrmCubo.FormCreate(Sender:
TObject);
begin
// Primero hay que
crear un contexto para renderizar
Angle := 0;
DC := GetDC(Handle);
SetDCPixelFormat;
hrc := wglCreateContext(DC);
wglMakeCurrent(DC, hrc);
InitializeRC;
CreateDisplayList;
Timer.Enabled := True;
end;
procedure TfrmCubo.FormResize(Sender:
TObject);
var gldAspect : GLdouble;
begin
// Si se cambia
el tamaño del formulario, hay que cambiar el tamaño de
// la representación
gldAspect := Width / Height;
glMatrixMode(GL_PROJECTION);
glLoadIdentity;
gluPerspective(30.0, //
Angulo del campo de visión
gldAspect, // Aspect
ratio del volumem de visionado
1.0, // Distancia al plano
de corte cercano
100.0); // Distancia al
plano de corte lejano
glViewport(0, 0, Width, Height);
InvalidateRect(Handle, nil,
False);
end;
procedure TfrmCubo.WMPaint(var
Msg: TWMPaint);
var ps : TPaintStruct;
begin
// En cada evento
de repintado del formulario, representamos la escena
BeginPaint(Handle, ps);
DrawScene;
EndPaint(Handle, ps);
end;
procedure TfrmCubo.FormDestroy(Sender:
TObject);
begin
// Antes de cerrar
el formulario, hay que liberar los recursos utilizados
Timer.Enabled := False;
wglMakeCurrent(0, 0);
wglDeleteContext(hrc);
ReleaseDC(Handle, DC);
if (Palette <>
0) then
DeleteObject(Palette);
end;
procedure TfrmCubo.WMQueryNewPalette(var
Msg : TWMQueryNewPalette);
begin
// En el caso en
que el programa esté usando una paleta de color
// (en configuraciones de
color pequeñas como 256 colores en Windows)
// hay que "realizar" la
paleta y actualizar el área de cliente cuando
// el formulario recibe
el foco, ya que si no, se mantendrían los colores
// de la paleta de la otra
ventana que estaba activa antes, y se verían
// los colores horrorosos.
if (Palette <>
0) then
begin
Msg.Result := RealizePalette(DC);
if (Msg.Result <> GDI_ERROR)
then
InvalidateRect(Handle, nil,
False);
end;
end;
procedure TfrmCubo.WMPaletteChanged(var
Msg : TWMPaletteChanged);
begin
// Igual que antes,
pero en este caso es para cuando otro programa
// "realiza" su paleta
if ((Palette <>
0) and (THandle(TMessage(Msg).wParam) <> Handle)) then
begin
if (RealizePalette(DC)
<> GDI_ERROR) then
UpdateColors(DC);
Msg.Result := 0;
end;
end;
procedure TfrmCubo.TimerTimer(Sender:
TObject);
begin
// En cada evento
del timer, se establece un nuevo ángulo de rotación
// y se fuerza el repintado
de la escena
Angle := Angle + 2.0;
if (Angle >= 90.0)
then
Angle := 0.0;
InvalidateRect(Handle, nil,
False);
end;
end.
¿Puedo hacer algo
más con este código?
Para empezar, sirve como
ilustración de que OpenGL funciona en Delphi ^_^
Se puede juguetear con él,
modificando los colores de los cubos, cambiando éstos por pirámides,
añadiendo una barra de scroll (TScrollBar) que cambie el valor del
temporizador del TTimer para aumentar o disminuir la velocidad de rotación.
Tambien se puede hacer que no solo rote en un eje, sino en los otros dos...
Probando es como se aprende.
Ya disponéis de los
elementos necesarios para usar OpenGL en Delphi. Ahora es el turno para
que cada uno aprenda por su cuenta, ya que este artículo tan solo
ha tratado de hacer una introducción.
En internet existen muchos
sitios donde encontrar información adicional sobre OpenGL y Delphi
para profundizar lo comenzado aquí. Tan solo queda poner ganas y
tiempo.
Hasta el próximo artículo.
ÚLTIMA
REVISIÓN EN JULIO
DE
1999