OpenGL
Delphi Tutorial One Getting
Started with OpenGL and Delphi
This
tutorial is based on the tutorial OpenGL
with Delphi by Alex Semichastny. I have expanded it to include more information
about openGL and using the Delphi environment.
OpenGL
is a low-level graphics library specification originally developed by Silicon
Graphics Inc. Your system's particular implementation of this specification, often
called OpenGL driver, allows you to use a set of geometric primitives (points,
lines, polygons, images, and so on) to describe the scene you wish to draw. Visualizing
a scene of moderate complexity takes mere milliseconds, which means the library
has sufficient performance to support you in the creation of animations and virtual
worlds.
The
OpenGL driver is generally provided as a library in binary format. It can be linked
to your program dynamically. On the Windows platform, it will be a DLL (check
for opengl.dll in your system directory). Since Delphi can use any DLL, it is
as simple as with any other language to program OpenGL 3D graphics. This article
will help you to get acquainted with OpenGL techniques in Delphi. MATHEMATICAL
FUNDAMENTALS
OpenGL
is based on strong mathematical fundamentals so you are limited only by your imagination.
Rather then getting into axioms and lemmas, let's just proceed with a simple 3D
coordinate system, as used by most 3D programmers. Here it is: This
is how you should imagine the placement of your screen (the blue square) in your
scene. The point from which the four rays extend to create the screen is your
"point of view" in the imaginary world.OpenGL lets you define this situation
with two simple function calls
In
these calls, -0.1, 0.1, -0.1, 0.1 define the size of virtual screen in left, right,
bottom, top sequence; 0.3 defines viewpoint-to-screen distance (same as near clipping
plane) and 25.0 defines the far clipping plane. Anything in front of the near
clipping plane or behind the far clipping plane will be invisible. Of course,
you can play with these numbers to suit your needs for your scene dimensions.
FROM
PRIMITIVES TO OBJECTS
Now
comes the challenging part: the objects. OpenGL supports only the following geometrical
primitives: points, lines and polygons. No surface of higher order (such as sphere)
can be drawn as a primitive. But it can be perfectly approximated with polygons.
Take a look at any modern 3D game and you'll find it is completely built from
triangles. So we won't be limited by this restriction.
The
object drawing is very similar to Pascal programming. Each block should be surrounded
by a begin-end pair...or actually, a glBegin() and glEnd() pair. Take a look at
this:
It's
a simple triangle. It is five units away from your viewpoint; it is one unit tall
and two units wide.
Here
is a screenshot:
Even
if it doesn't look much like 3D, it is our starting piece. Below you will find
the source code for the example.
A
few words should be said prior to letting you dig into the code. Every OpenGL
program contains some OS-specific code that initializes the output device. If
you use Win32, you'll need to set up the pixel format and create a rendering context
out of the windows device context. If you are not so good in system-level Windows
programming, just use following program as a template. For more info read the
help on each function called in FormCreate.
type
TForm1 = class(TForm) procedure
FormCreate(Sender: TObject); procedure FormPaint(Sender:
TObject); private procedure
Draw; //Draws an OpenGL scene on request public end;
var
Form1: TForm1;
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:24;
//
preferred 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 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 exit; if (SetPixelFormat(DC, pixelFormat, @pfd) <>
TRUE) then exit; end;
procedure
GLInit; begin // set viewing projection
glMatrixMode(GL_PROJECTION); glFrustum(-0.1,
0.1, -0.1, 0.1, 0.3, 25.0); // position viewer
glMatrixMode(GL_MODELVIEW); glEnable(GL_DEPTH_TEST); end;
procedure
TForm1.FormCreate(Sender: TObject); var DC:HDC; RC:HGLRC;
i:integer; begin DC:=GetDC(Handle);
//Actually,
you can use any windowed control here SetupPixelFormat(DC);
RC:=wglCreateContext(DC); //makes
OpenGL window out of DC wglMakeCurrent(DC, RC); //makes
OpenGL window active GLInit; //initialize OpenGL end;
OK,
lets do real 3D now. Using the previous program as a skeleton, we add a few lines
of code to create a flat shaded tetrahedron. How can we build it out of available
primitives? We will use four triangles. One for the base and three on the sides.
Here is the code that produces it:
Even
if it looks a little complex, it is easier to understand when you have a picture
in front of you.
We
define the vertices a1 - a4 and build triangles out of them by specifying triples
of points. Whenever you define your triangles (or other polygons), use the following
rule: always specify points in counterclockwise order as if you were looking at
each side from the outside. According to this rule, we specify a1-a2-a4, a1-a3-a2
(looking from beneath), a2-a3-a4, and a3-a1-a4.
Just
replacing TForm1.Draw() in Tri.pas will not make a lot of changes. It still does
not look three-dimensional. That's because we have not defined any lights yet.
LIGHTS!
CAMERA! OPENGL!
The
lighting model in OpenGL has two parts: the lights themselves (color, intensity
etc.) and materials of the bodies. Material, in turn, contains colors, some physical
parameters (like opacity or glossiness) and textures. It is a huge world to play
with, we'll go step by step.
Two
constants were needed. One defines the light position (to the left, top, back
from viewer) and another defines ambient lighting. It produces some small amount
of scattered light, which allows you to see something even if it is completely
in shadow.
Although
you enabled lighting and specified a light source, the body is still not drawn
shaded. This is because OpenGL needs to know "normal" to each polygon
you specify to do lighting math. (Normal is a vector perpendicular to the surface.)
If you have no library of vector functions of your own, just use the following
method to calculate a normal having 3 points of triangle. This function needs
them to be specified counterclockwise, because the normal is produced as a cross
product of the vectors and it will point inside the tetrahedron if you don't obey
this rule.
function
getNormal(p1,p2,p3:TGLArrayf3):TGLArrayf3; var a,b:TGLArrayf3; begin
//make two vectors a[0]:=p2[0]-p1[0];
a[1]:=p2[1]-p1[1]; a[2]:=p2[2]-p1[2]; b[0]:=p3[0]-p1[0];
b[1]:=p3[1]-p1[1]; b[2]:=p3[2]-p1[2]; //calculate
cross-product result[0]:=a[1]*b[2]-a[2]*b[1];
result[1]:=a[2]*b[0]-a[0]*b[2]; result[2]:=a[0]*b[1]-a[1]*b[0]; end;
Using
this function, you can now specify all the information needed to calculate the
lighting:
And
now let us use something offered by Delphi VCL. Put the timer on your window,
specify class member "angle:single" and increment it by 1.0 each time
the timer ticks:
procedure
TForm1.Timer1Timer(Sender: TObject); begin angle:=angle+1.0;
Draw; end;
You
are just one line away from making an OpenGL animation:
glRotatef(angle,
0.0, 1.0, 0.0);
Put
this line just before you start your triangles in a glBegin() and your rotating
shaded-body application is finished.
If
you like this stuff, you can play a little with this application. You can download
it from CodeCentral: Step1,
2.6K Try to understand how it works and why you use this code or that. If
you want to go further, there are plenty of books and tutorials. Check out www.opengl.org
if you haven't done so yet.