Aula Macedonia


Curso de Programación Visual en Delphi.
Curso de programación.


Artículo realizado por
José Antonio Suárez.





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




AULA MACEDONIA
a
MACEDONIA Magazine