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

glMatrixMode(GL_PROJECTION);
glFrustum(-0.1, 0.1, -0.1, 0.1, 0.3, 25.0);

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:

const S=1.0; D=5.0;
...
glBegin(GL_TRIANGLES);
glVertex3f( -S, 0, D); glVertex3f(S, 0, D); glVertex3f(0, S, D);
glEnd;

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*.

**FILE: Tri.pas**

**unit** Tri;
**interface**
**uses**
OpenGL, Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls, ComCtrls;
**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**;
**procedure** TForm1.Draw;
**const** S=1.0; D=5.0;
**begin**
glClear(GL_COLOR_BUFFER_BIT **or** GL_DEPTH_BUFFER_BIT);
glLoadIdentity;
glTranslatef(0.0, 0.0, -12.0);
glBegin(GL_TRIANGLES);
glVertex3f( -S, 0, D); glVertex3f(S, 0, D); glVertex3f(0, S, D);
glEnd;
SwapBuffers(wglGetCurrentDC);
**end**;
**procedure** TForm1.FormPaint(Sender: TObject);
**begin**
Draw;
**end**;
**end**.

**FILE: Tri.dfm**

**object** Form1: TForm1
BorderStyle = bsDialog
Caption = 'BASIC OpenGL Program'
ClientHeight = 318
ClientWidth = 373
OnCreate = FormCreate
OnPaint = FormPaint
**end**

### ADVENTURES IN THE THIRD DIMENSION

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:

**procedure** TForm1.Draw;
**const** D=1.5;
H1=D/1.732;
H2=D*1.732-H1; *// D/H = tg(30) = 1/sqrt(3)*
HY=3.0;
**const** *//vertexes*
a1:TGLArrayf3=(-D, 0, -H1);* **//bootom left *
a2:TGLArrayf3=( D, 0, -H1); *//bootom right*
a3:TGLArrayf3=( 0, 0, H2); *//bootom back*
a4:TGLArrayf3=( 0, HY, 0); * //top*
**begin**
glClear(GL_COLOR_BUFFER_BIT **or** GL_DEPTH_BUFFER_BIT);
glLoadIdentity;
glTranslatef(0.0, 0.0, -12.0);
glBegin(GL_TRIANGLES);
glVertex3fv(@a1); glVertex3fv(@a3); glVertex3fv(@a2);
glVertex3fv(@a1); glVertex3fv(@a2); glVertex3fv(@a4);
glVertex3fv(@a2); glVertex3fv(@a3); glVertex3fv(@a4);
glVertex3fv(@a3); glVertex3fv(@a1); glVertex3fv(@a4);
glEnd;
SwapBuffers(wglGetCurrentDC);
**end**;

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.

Defining a light source is rather easy.

**procedure** GLInit;
**const**
light0_position:TGLArrayf4=( -8.0, 8.0, -16.0, 0.0);
ambient: TGLArrayf4=( 0.3, 0.3, 0.3, 0.3);
**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);
* // set lights*
glEnable(GL_LIGHTING);
glLightfv(GL_LIGHT0, GL_POSITION, @light0_position);
glLightfv(GL_LIGHT0, GL_AMBIENT, @ambient);
glEnable(GL_LIGHT0);
**end**;

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:

**procedure** TForm1.Draw;
**const** D=1.5;
H1=D/1.732;
H2=D*1.732-H1; *// D/H = tg(30) = 1/sqrt(3)*
HY=3.0;
**const** *//vertexes*
a1:TGLArrayf3=(-D, 0, -H1);
a2:TGLArrayf3=(D, 0, -H1);
a3:TGLArrayf3=(0, 0, H2);
a4:TGLArrayf3=(0, HY, 0);
**var** n1, n2, n3, n4: TGLArrayf3; *//normals*
**begin**
n1 := getNormal(a1,a3,a2);
n2 := getNormal(a1,a2,a4);
n3 := getNormal(a2,a3,a4);
n4 := getNormal(a3,a1,a4);
glClear(GL_COLOR_BUFFER_BIT **or** GL_DEPTH_BUFFER_BIT);
glEnable(GL_NORMALIZE);
glShadeModel(GL_FLAT);
glCullFace(GL_BACK);
glLoadIdentity;
glTranslatef(0.0, 0.0, -12.0);
glBegin(GL_TRIANGLES);
glNormal3fv(@n1);
glVertex3fv(@a1); glVertex3fv(@a2); glVertex3fv(@a3);
glNormal3fv(@n2);
glVertex3fv(@a1); glVertex3fv(@a2); glVertex3fv(@a4);
glNormal3fv(@n3);
glVertex3fv(@a2); glVertex3fv(@a3); glVertex3fv(@a4);
glNormal3fv(@n4);
glVertex3fv(@a3); glVertex3fv(@a1); glVertex3fv(@a4);
glEnd;
SwapBuffers(wglGetCurrentDC);
**end**;

That's it. The code produces a view like this:

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.

If you have questions or comments about this article email me at alex@nichewo.com. Thanks for your time. I hope you enjoyed this article.

Connect with Us