Chapter 8
Triangles
The Plane Problem
The time has come for building more interesting shapes than just a cube. The first step is to 
examine a problem that goes with the main primitive you have used so far. It is possible, 
with the OpenGL commands you have learned so far, to draw polygons that OpenGL cannot 
handle well. The following project will demonstrate. Note that in all projects you should add 
OpenGL to the interface uses clause.
Create a new directory under OpenGL and call it Chapter.8. Under Chapter.8 create a 
directory called NotPlane. Create a new Delphi project, and save the unit as Plane1.Pas in 
the NotPlane directory. Save the project as Plane.Dpr in the same directory. Place a 
TOpenGL component on the form. Below it place a TTransform component. To the right of 
that, place a TLight component. Place another TTransform component and two TMaterial 
components to the right of it. Figure 8-1 shows the design-mode form.


PAGE 161
 Create an OnPaint event handler for TTransform1 and an OnPaint event handler for 
TTransform2. Use two TMaterial components because both the front and back faces of the 
object need separate attention.

procedure TForm1.Transform1Paint(Sender: TObject);	
begin	
  Light1.Apply;	
end;	
	
procedure TForm1.Transform2Paint(Sender: TObject);	
begin	
  Material1.Apply;	
  Material2.Apply;	
  glBegin(GL_POLYGON);	
    glVertex3f(-0.4,-0.6,+0.0);	
    glVertex3f(+0.4,+0.2,+0.0);	
    glVertex3f(+0.4,-0.8,-0.0);	
    glVertex3f(-0.4,+0.4,-0.0);	
  glEnd;	
end;	
Here are a couple of declarations you need at this point. The i suffix indicates the procedure 
uses integer parameters. The param value is 0 or 1 for true or false, on or off.

GL_LIGHT_MODEL_TWO_SIDE = $B52;	
GL_TRUE                 = 1;	
GL_FALSE                = 0;	
	
procedure glLightModeli(pname:GLenum;param:GLint); stdcall;;	
Create an OnCreate, an OnPaint, and an OnResize event handler for the form. In the 
OnCreate handler, set the light model to allow two-sided lighting. See Listing 8-1.

procedure TForm1.FormCreate(Sender: TObject);
begin
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);
end;

procedure TForm1.FormPaint(Sender: TObject);
begin
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
  Transform1.Apply;
  Transform2.Apply;
  glFlush;
  OpenGL1.ErrorCheck('FormPaint');
end;

procedure TForm1.FormResize(Sender: TObject);


PAGE 162
 begin
  if OpenGL1.Smaller then
    FormPaint(Sender);
end;
Listing 8-1. Event handlers for non-planar project.
Listing 8-2 shows the form as text to reveal the component property settings. Notice that 
cullFace is not in the Enable list. This is important; otherwise the back face would not be 
visible. Notice also the values for AxisX and Angle for the Transform2 component, which 
mean the object rotates 130 degrees about the x-axis. Those values, plus AxisY, for the 
Transform1 component mean the spotlight rotates 15 degrees, mostly about the y-axis.

object Form1: TForm1
  Left = 309
  Top = 100
  Width = 328
  Height = 347
  Caption = 'Form1'
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OnCreate = FormPaint
  OnPaint = FormPaint
  OnResize = FormResize
  PixelsPerInch = 96
  TextHeight = 13
  object OpenGL1: TOpenGL
    pfdFlags = [Draw_To_Window, Support_OpenGL]
    PixelType = RGBA
    Projection = Perspective
    Enable = [depthTest, Lighting]
    ClearRed = 0.6
    ClearGreen = 0.8
    ClearBlue = 0.8
    prxLeft = -1
    prxRight = 1
    prxBottom = -1
    prxTop = 1
    prxNear = 2
    prxFar = 9
    ColorBits = 24
    DepthBits = 32
    Left = 4
    Top = 4


PAGE 163
   end
  object Transform1: TTransform
    TranX = 0.6
    TranY = -0.6
    TranZ = -2.2
    AxisX = 0.2
    AxisY = 1
    Angle = 15
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform1Paint
    Left = 4
    Top = 68
  end
  object Light1: TLight
    Which = 1
    Cutoff = 85
    AmbientRed = 0.5
    AmbientGreen = 0.5
    AmbientBlue = 0.5
    DiffuseRed = 1
    DiffuseGreen = 1
    DiffuseBlue = 1
    Direction = True
    Position = True
    Spotlight = True
    Enabled = True
    Left = 36
    Top = 68
  end
  object Transform2: TTransform
    TranZ = -2.9
    AxisX = 1
    Angle = 130
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform2Paint
    Left = 4
    Top = 36
  end
  object Material1: TMaterial
    ambRed = 1
    ambAlpha = 1
    difRed = 1
    difAlpha = 1
    aFace = faFront
    dFace = faFront


PAGE 164
     sFace = faFront
    Left = 36
    Top = 36
  end
  object Material2: TMaterial
    ambBlue = 1
    ambAlpha = 1
    difBlue = 1
    difAlpha = 1
    aFace = faBack
    dFace = faBack
    sFace = faBack
    Left = 68
    Top = 36
  end
end
Listing 8-2. Properties for the non-planar project.
Save, compile, and run. Hopefully you are remembering never to run an OpenGL 
application from the Delphi IDE. A useful trick you can do with Windows 95, Windows 98, 
and Windows NT 4 (and later) is to create a shortcut on the desktop and set its target 
property as the complete path to the .EXE file of whatever OpenGL project you are 
developing. You may find that more convenient than using the Run command from the Start 
button.
Figure 8-2 shows the resulting polygon. The front face is red and the back face is blue. You 
can readily see that the four vertices do not lie in a plane. As a result, the edges appear to 
cross each other when viewed from some directions. There is no guarantee as to how a 
particular implementation will render such a figure, if at all.

One polygon exists that will always lie in a plane. Furthermore, it will always be convex. A 
convex polygon has no indentations and no crossing edges like the polygons shown in Figure 
8-3 do. In order for a graphics library to handle all possible polygons, it would have to 
employ complex and slow algorithms. For the sake of efficiency, OpenGL only intends to 
draw convex 

PAGE 165
 polygons. Furthermore, OpenGL does not even check for a “bad” polygon. The programmer 
bears the sole responsibility for giving OpenGL convex polygons.
There is a polygon that will always lie in a plane and will always be convex. That perfect 
polygon is the triangle. Divide your squares and pentagons and other polygons into several 
triangles. You may think that a carefully designed polygon is more efficient than multiple 
triangles, but triangle rendering is so simple that it provides an offsetting efficiency. In fact, 
this simplest of polygons even has hardware support in many video cards and graphics 
accelerators. Many hardware manufacturers boast of their products’ prowess in terms of 
how many thousands or millions of triangles per second they render! Suppose you really 
want to draw a polygon that does not lie entirely in a plane, or has indentations, or has sides 
that cross each other. No problem! Any general polygon can be subdivided into triangles.
Building Shapes From Triangles
Let’s start with a simple example. Here is a square:

  glBegin(GL_POLYGON);	
    glVertex3f(-0.0.4,-0.0.4,+0.0););	
    glVertex3f(+0.4,-0.0.4,+0.0););	
    glVertex3f(+0.4,+0.4,-0.0.0););	
    glVertex3f(-0.0.4,+0.4,-0.0.0););	
  glEnd;	
Here is the same square divided into triangles:

  glBegin(GL_TRIANGLES);	
    glVertex3f(-0.4,-0.4,+0.0);	
    glVertex3f(+0.4,-0.4,+0.0);	
    glVertex3f(-0.4,+0.4,+0.0);	
	
    glVertex3f(+0.4,-0.4,+0.0);	
    glVertex3f(+0.4,+0.4,+0.0);	


PAGE 166
     glVertex3f(-0.4,+0.4,+0.0);	
  glEnd;	
Notice that with GL_TRIANGLES as the parameter passed to glBegin, you can specify more 
than one polygon between glBegin and glEnd. Since all triangles have exactly three vertices, 
OpenGL knows how to group the vertex list. This eliminates some extra calls to glBegin and 
glEnd. Here is the declaration of the new constant and its relatives:

  GL_TRIANGLES      = 4;	
  GL_TRIANGLE_STRIP = 5;	
  GL_TRIANGLE_FAN   = 6;	
A hexagon provides a little more challenge. You could divide a hexagon into four triangles, 
but using six triangles will provide a lead-in for something else. Listing 8-3 shows how to 
draw the hexagon:

  glBegin(GL_TRIANGLES);
    glVertex3f(+0.0,+0.4,+0.0);
    glVertex3f(-0.3,+0.2,+0.0);
    glVertex3f(+0.0,+0.0,+0.0);

    glVertex3f(-0.3,+0.2,+0.0);
    glVertex3f(-0.3,-0.2,+0.0);
    glVertex3f(+0.0,+0.0,+0.0);

    glVertex3f(-0.3,-0.2,+0.0);
    glVertex3f(+0.0,-0.4,+0.0);
    glVertex3f(+0.0,+0.0,+0.0);

    glVertex3f(+0.0,-0.4,+0.0);
    glVertex3f(+0.3,-0.2,+0.0);
    glVertex3f(+0.0,+0.0,+0.0);

    glVertex3f(+0.3,-0.2,+0.0);
    glVertex3f(+0.3,+0.2,+0.0);
    glVertex3f(+0.0,+0.0,+0.0);
 
    glVertex3f(+0.3,+0.2,+0.0);
    glVertex3f(+0.0,+0.4,+0.0);
    glVertex3f(+0.0,+0.0,+0.0);
  glEnd;
Listing 8-3. A hexagon from triangles.

PAGE 167
 Triangle Fans
You surely noticed that a lot of vertices were repeated when you saw code to produce 
adjacent triangles. OpenGL provides a shortcut that saves code and reduces the number of 
command calls, and is therefore more efficient. The triangle fan allows you to specify three 
vertices for the first triangle, then one more vertex to specify a second triangle that shares 
two vertices with the previous triangle. Specify another vertex for a third triangle that shares 
two vertices from the second triangle. You can specify as many triangles as you want, using 
only one vertex per triangle, plus two more for the first triangle. All the triangles must share 
one common vertex that serves as a pivot point around which the triangles form.
Order
Triangle fans start with the pivot point. The first vertex you specify is the one around which 
all the triangles fan. Give two more vertices, being careful to progress in a clockwise manner. 
After you complete the first triangle, give an additional vertex. The next triangle will be 
made from the pivot point, the last vertex of the previous triangle, and the new vertex.
Flat Fans
Create a new directory under Chapter.8 and call it Fans. Create a new project and save the 
unit in the new directory as Fan1.Pas and the project file as Fan.Dpr. Drop on a TOpenGL 
component, a TTransform component, and a TLight. Add another TTransform and two 
TMaterial components. Add still another TTransform and two TMaterial components. Create 
an OnCreate, an OnPaint, and an OnResize event handler for the form. See Listing 8-4:

procedure TForm1.FormCreate(Sender: TObject);
begin
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);
  glFrontFace(GL_CW);
end;

procedure TForm1.FormPaint(Sender: TObject);
begin
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
  Transform1.Apply;
  Transform2.Apply;
  Transform3.Apply;
  glFlush;
  OpenGL1.ErrorCheck('FormPaint');
end;



PAGE 168
 procedure TForm1.FormResize(Sender: TObject);
begin
  if OpenGL1.Smaller then
    FormPaint(Sender);
end;
Listing 8-4. Event handlers for triangle fans.
Winding and Faces
Triangle fans start with the vertex that will be the pivot point. The vertices then proceed in a 
clockwise direction for the first triangle, then each successive triangle proceeds in a 
clockwise direction from the first triangle. Since the winding for triangle fans usually is 
clockwise, the front face definition must change. That is the purpose of the glFrontFace 
command in the FormCreate method. The default value is GL_CCW for counterclockwise 
winding, but this project needs GL_CW passed for clockwise winding. 

  GL_CW  = $900;	
  GL_CCW = $901;	
	
procedure glFrontFace(mode:GLenum); stdcall;	
Create event handlers for the TTransform components as in Listing 8-5. Figure 8-4 shows the 
square and hexagon made from triangle fans. Notice that a square created with 
GL_TRIANGLE_FAN uses no more vertices than one created with GL_POLYGON. The 
hexagon, with six triangles, requires only eight vertices. Listing 8-6 shows the properties of 
the form and its components.



PAGE 169
 procedure TForm1.Transform1Paint(Sender: TObject);
begin
  Light1.Apply;
end;

procedure TForm1.Transform2Paint(Sender: TObject);
begin
  Material1.Apply;
  Material2.Apply;
  glBegin(GL_TRIANGLE_FAN);
    glVertex3f(+0.4,-0.4,+0.0);
    glVertex3f(-0.4,-0.4,+0.0);
    glVertex3f(-0.4,+0.4,+0.0);
    glVertex3f(+0.4,+0.4,+0.0);
  glEnd;
end;

procedure TForm1.Transform3Paint(Sender: TObject);
begin
  Material3.Apply;
  Material4.Apply;
  glBegin(GL_TRIANGLE_FAN);
    glVertex3f(+0.0,+0.0,+0.0);
    glVertex3f(-0.3,+0.2,+0.0);
    glVertex3f(+0.0,+0.4,+0.0);
    glVertex3f(+0.3,+0.2,+0.0);
    glVertex3f(+0.3,-0.2,+0.0);
    glVertex3f(+0.0,-0.4,+0.0);
    glVertex3f(-0.3,-0.2,+0.0);
    glVertex3f(-0.3,+0.2,+0.0);
  glEnd;
end;
Listing 8-5. TTransform event handlers.
object Form1: TForm1
  Left = 309
  Top = 100
  Width = 328
  Height = 347
  Caption = 'Form1'
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OldCreateOrder = True
  OnCreate = FormCreate


PAGE 170
   OnPaint = FormPaint
  OnResize = FormResize
  PixelsPerInch = 96
  TextHeight = 13
  object OpenGL1: TOpenGL
    pfdFlags = [Draw_To_Window, Support_OpenGL]
    PixelType = RGBA
    Projection = Perspective
    Enable = [depthTest, Lighting]
    ClearRed = 0.6
    ClearGreen = 0.8
    ClearBlue = 0.8
    prxLeft = -1
    prxRight = 1
    prxBottom = -1
    prxTop = 1
    prxNear = 2
    prxFar = 9
    ColorBits = 24
    DepthBits = 32
    StencilBits = 0
    Left = 4
    Top = 4
  end
  object Transform1: TTransform
    TranX = 0.6
    TranY = -0.6
    TranZ = -2.2
    AxisX = 0.2
    AxisY = 1
    Angle = 15
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform1Paint
    Left = 4
    Top = 36
  end
  object Light1: TLight
    Which = 1
    Cutoff = 85
    AmbientRed = 0.5
    AmbientGreen = 0.5
    AmbientBlue = 0.5
    DiffuseRed = 1
    DiffuseGreen = 1
    DiffuseBlue = 1
    Direction = True
    Position = True


PAGE 171
     Spotlight = True
    Enabled = True
    Left = 36
    Top = 36
  end
  object Transform2: TTransform
    TranX = -0.5
    TranY = 0.5
    TranZ = -2.9
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform2Paint
    Left = 4
    Top = 76
  end
  object Material1: TMaterial
    ambRed = 1
    ambAlpha = 1
    difRed = 1
    difAlpha = 1
    aFace = faFront
    dFace = faFront
    sFace = faFront
    Left = 36
    Top = 76
  end
  object Material2: TMaterial
    ambBlue = 1
    ambAlpha = 1
    difBlue = 1
    difAlpha = 1
    aFace = faBack
    dFace = faBack
    sFace = faBack
    Left = 68
    Top = 76
  end
  object Transform3: TTransform
    TranX = 0.5
    TranY = -0.5
    TranZ = -2.9
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform3Paint
    Left = 4
    Top = 112
  end


PAGE 172
   object Material3: TMaterial
    ambRed = 1
    ambAlpha = 1
    difRed = 1
    difAlpha = 1
    aFace = faFront
    dFace = faFront
    sFace = faFront
    Left = 36
    Top = 112
  end
  object Material4: TMaterial
    ambBlue = 1
    ambAlpha = 1
    difBlue = 1
    difAlpha = 1
    aFace = faBack
    dFace = faBack
    sFace = faBack
    Left = 68
    Top = 112
  end
end
Listing 8-6. Fan project properties.
Save, compile, and run. So why does this example use two-sided lighting and two different 
material colors for the two sides? If you see red, and nothing but red, you have the winding 
right, not only for the first triangle, but for all the additional ones as well. If you see any blue, 
then something is wrong. Of course, you could apply some rotation in the transformations to 
show the blue backsides on purpose.
3-D Fans
While each individual triangle in a triangle fan is, of course, planar, triangle fans as a whole 
need not stay in a single plane. You can build a simple triangular pyramid to illustrate. 
Under the Chapter.8 directory create a Pyramid directory. Create a new project and save the 
unit as Pyr1.Pas and the project as Pyr.Dpr into the new directory. Place components and set 
properties in the same manner that you did for Fan.Dpr. Listing 8-7 shows the form as text 
for the actual property values:

object Form1: TForm1
  Left = 308
  Top = 100
  Width = 328
  Height = 347


PAGE 173
   Caption = 'Form1'
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OnCreate = FormCreate
  OnPaint = FormPaint
  OnResize = FormResize
  PixelsPerInch = 96
  TextHeight = 13
  object OpenGL1: TOpenGL
    pfdFlags = [Draw_To_Window, Support_OpenGL]
    PixelType = RGBA
    Projection = Perspective
    Enable = [depthTest, Lighting]
    ClearRed = 0.6
    ClearGreen = 0.8
    ClearBlue = 0.8
    prxLeft = -1
    prxRight = 1
    prxBottom = -1
    prxTop = 1
    prxNear = 2
    prxFar = 9
    ColorBits = 24
    DepthBits = 32
    Left = 4
    Top = 4
  end
  object Transform1: TTransform
    TranX = 0.6
    TranY = -0.6
    TranZ = -2.2
    AxisX = 0.2
    AxisY = 1
    Angle = 15
    ScaleX = 0.3
    ScaleY = 0.3
    ScaleZ = 0.3
    OnPaint = Transform1Paint
    Left = 4
    Top = 36
  end
  object Light1: TLight
    Which = 1
    Cutoff = 85
    AmbientRed = 0.3
    AmbientGreen = 0.3


PAGE 174
     AmbientBlue = 0.3
    DiffuseRed = 1
    DiffuseGreen = 1
    DiffuseBlue = 1
    Direction = True
    Position = True
    Spotlight = True
    Enabled = True
    Left = 40
    Top = 36
  end
  object Transform2: TTransform
    TranX = -1.2
    TranY = 1.2
    TranZ = -5
    AxisX = 1
    Angle = 120
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform2Paint
    Left = 4
    Top = 68
  end
  object Material1: TMaterial
    ambRed = 1
    ambAlpha = 1
    difRed = 1
    difAlpha = 1
    aFace = faFront
    dFace = faFront
    sFace = faFront
    Left = 40
    Top = 68
  end
  object Material2: TMaterial
    ambBlue = 1
    ambAlpha = 1
    difBlue = 1
    difAlpha = 1
    aFace = faBack
    dFace = faBack
    sFace = faBack
    Left = 72
    Top = 68
  end
  object Transform3: TTransform
    TranX = 0.8
    TranY = -0.8


PAGE 175
     TranZ = -4
    AxisX = 1
    Angle = -120
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform3Paint
    Left = 4
    Top = 100
  end
  object Material3: TMaterial
    ambRed = 1
    ambAlpha = 1
    difRed = 1
    difAlpha = 1
    aFace = faFront
    dFace = faFront
    sFace = faFront
    Left = 40
    Top = 100
  end
  object Material4: TMaterial
    ambBlue = 1
    ambAlpha = 1
    difBlue = 1
    difAlpha = 1
    aFace = faBack
    dFace = faBack
    sFace = faBack
    Left = 72
    Top = 100
  end
end
Listing 8-7. Pyr project properties.
Create a new directory under the OpenGL directory and name it Shared. Create a new unit 
named Shapes.Pas, save it in the Shared directory, and add it to the Pyr project. Listing 8-8 
shows the new unit in all its glory:

unit Shapes;

interface

uses
  OpenGL;

procedure Pyramid;

implementation


PAGE 176
 
procedure Pyramid;
begin
  glBegin(GL_TRIANGLE_FAN);
    glVertex3f(+0.0,+0.0,+0.0);
    glVertex3f(-0.3,+0.3,-0.4);
    glVertex3f(+0.3,+0.3,-0.4);
    glVertex3f(+0.0,-0.3,-0.4);
    glVertex3f(-0.3,+0.3,-0.4);
  glEnd;
end;

end.
Listing 8-8. Shapes.Pas unit.
Listing 8-9 shows the event handlers of the Pyr project. Be sure to list Shapes in the uses 
clause in the implementation section, so you have access to the Pyramid procedure.

procedure TForm1.Transform1Paint(Sender: TObject);
begin
  Light1.Apply;
end;

procedure TForm1.Transform2Paint(Sender: TObject);
begin
  Material1.Apply;
  Material2.Apply;
  Pyramid;
end;

procedure TForm1.Transform3Paint(Sender: TObject);
begin
  Material3.Apply;
  Material4.Apply;
  Pyramid;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);
  glFrontFace(GL_CW);
end;

procedure TForm1.FormResize(Sender: TObject);
begin
  if OpenGL1.Smaller then
    FormPaint(Sender);
end;


PAGE 177
 
procedure TForm1.FormPaint(Sender: TObject);
begin
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
  Transform1.Apply;
  Transform2.Apply;
  Transform3.Apply;
  glFlush;
  OpenGL1.ErrorCheck(‘FormPaint’);
end;
Listing 8-9. Pyr project event handler methods.
Call Pyramid in both Transform2 and Transform3 OnPaint methods using different property 
settings in the two components to present different aspects of the pyramid in different 
locations. Both presentations rotate the pyramid enough so that the bottom appears. Since 
the fourth side was not drawn, looking at the bottom is to peer into the blue interior.
Triangle Strips
OpenGL provides another way to draw a series of triangles that share vertices. Triangle strips 
also start with three vertices for the first triangle and one vertex for each additional one. 
However, triangle strips do not pivot about a single vertex. To create triangle strips, use 
GL_TRIANGLE_STRIP as the parameter to pass to the glBegin command:

  glBegin(GL_TRIANGLE_STRIP);	
Winding
Another difference between triangle strips and triangle fans is the winding. Triangle strips 
use the default counterclockwise winding. You will not need to call glFrontFace before using 
triangle strips unless you have previously called the command to specify clockwise winding 
for front faces.
Order
Create a triangle in the normal manner, specifying the vertices in a counterclockwise 
manner. When you give an additional vertex, the last two vertices of the previous triangle 
belong to the new triangle, followed by the new vertex.

PAGE 178
 A Cube of Triangles
To illustrate using a triangle strip, make the old standby, the cube. Create a new directory 
named Cube under the Chapter.8 directory. Create a new project named Cubic.Dpr with the 
unit named Cube1.Pas and save them in the new directory. List Shapes in the 
implementation uses clause. Place a TOpenGL component, two TTransform components, a 
TLight component, and two TMaterial components. Use Listing 8-10 for the properties:

object Form1: TForm1
  Left = 308
  Top = 102
  Width = 328
  Height = 347
  Caption = 'Form1'
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OnCreate = FormCreate
  OnPaint = FormPaint
  OnResize = FormResize
  PixelsPerInch = 96
  TextHeight = 13
  object OpenGL1: TOpenGL
    pfdFlags = [Draw_To_Window, Support_OpenGL]
    PixelType = RGBA
    Projection = Perspective
    Enable = [depthTest, Lighting]
    ClearRed = 0.600000023841858
    ClearGreen = 0.800000011920929
    ClearBlue = 0.800000011920929
    prxLeft = -1
    prxRight = 1
    prxBottom = -1
    prxTop = 1
    prxNear = 2
    prxFar = 9
    ColorBits = 24
    DepthBits = 32
    Left = 4
    Top = 4
  end
  object Transform1: TTransform
    TranX = 0.600000023841858
    TranY = -0.600000023841858
    TranZ = -2.20000004768372
    AxisX = 0.200000002980232


PAGE 179
     AxisY = 1
    Angle = 15
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform1Paint
    Left = 4
    Top = 36
  end
  object Light1: TLight
    Which = 1
    Cutoff = 65
    AmbientRed = 0.200000002980232
    AmbientGreen = 0.200000002980232
    AmbientBlue = 0.200000002980232
    DiffuseRed = 1
    DiffuseGreen = 1
    DiffuseBlue = 1
    Direction = True
    Position = True
    Spotlight = True
    Enabled = True
    Left = 40
    Top = 36
  end
  object Transform2: TTransform
    TranX = 0.699999988079071
    TranY = -0.699999988079071
    TranZ = -3
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform2Paint
    Left = 4
    Top = 72
  end
  object Material1: TMaterial
    ambRed = 1
    ambAlpha = 1
    difRed = 1
    difAlpha = 1
    aFace = faFront
    dFace = faFront
    sFace = faFront
    Left = 36
    Top = 72
  end
  object Material2: TMaterial
    ambBlue = 1


PAGE 180
     ambAlpha = 1
    difBlue = 1
    difAlpha = 1
    aFace = faBack
    dFace = faBack
    sFace = faBack
    Left = 68
    Top = 72
  end
end
Listing 8-10. Cubic properties.
Listing 8-11 shows the event handler methods for the Cubic project. Listing 8-12 shows the 
Cube method you should add to the Shapes unit, which should also declare the Cube 
procedure in its interface section.

procedure TForm1.FormCreate(Sender: TObject);
begin
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);
end;

procedure TForm1.FormResize(Sender: TObject);
begin
  if OpenGL1.Smaller then
    FormPaint(Sender);
end;

procedure TForm1.FormPaint(Sender: TObject);
begin
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
  Transform1.Apply;
  Transform2.Apply;
  glFlush;
  OpenGL1.ErrorCheck('FormPaint');
end;

procedure TForm1.Transform1Paint(Sender: TObject);
begin
  Light1.Apply;
end;

procedure TForm1.Transform2Paint(Sender: TObject);
begin
  Material1.Apply;
  Material2.Apply;
  Cube;
end;
Listing 8-11. Cubic project event handler methods.

PAGE 181
 procedure Cube;
begin
  glBegin(GL_TRIANGLE_STRIP);
    glVertex3f(-0.4,+0.4,+0.4);
    glVertex3f(-0.4,-0.4,+0.4);
    glVertex3f(+0.4,+0.4,+0.4);
    glVertex3f(+0.4,-0.4,+0.4);
    glVertex3f(+0.4,+0.4,-0.4);
    glVertex3f(+0.4,-0.4,-0.4);
    glVertex3f(-0.4,+0.4,-0.4);
    glVertex3f(-0.4,-0.4,-0.4);
    glVertex3f(-0.4,+0.4,+0.4);
    glVertex3f(-0.4,-0.4,+0.4); 
  glEnd;
end;
Listing 8-12. The Cube procedure in the Shapes unit.

Next, of course, is a picture of the result. Figure 8-5 shows a topless, bottomless, but 
otherwise perfect cube.


PAGE 182
 Shading
So far every example program has been using smooth shading; OpenGL uses the color of 
each vertex in the polygon, as computed from the material color, lighting, distance, and 
angle. The color of any point on the polygon is computed from the vertex colors, producing 
a gradual transition from one vertex to the next. However, smooth shading is not the only 
possibility under OpenGL, although it is the default. Naturally, OpenGL has a couple of 
constants appropriate for the subject:

  GL_FLAT   = $1D00;	
  GL_SMOOTH = $1D01;	
Of course, naming some OpenGL constants does no good without a command to receive 
them. Here is the command to set the shading model:

procedure glShadeModel(mode:GLenum); stdcall;	
If you pass GL_FLAT to the command (for flat shading), OpenGL selects one vertex of the 
polygon and uses its (computed) color for the entire polygon surface. Take the Cube project 
and make one change in the FormCreate method:

procedure TForm1.FormCreate(Sender: TObject);	
begin	
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);	
  glShadeModel(GL_FLAT);	
end;	
When you compile and run this time, each side of the cube has a single (flat) color as in 
Figure 8-6:

PAGE 183
 Curves
You have seen OpenGL commands for points, lines, and polygons. You may have been 
waiting for curve commands with great anticipation. You can stop holding your breath. 
Here are the OpenGL curve commands...all zero of them. Do not panic. OpenGL allows 
you to represent curved surfaces quite nicely. Rather than using actual curve commands, the 
usual way a 3-D rendering system represents curved surfaces is by means of many small 
polygons. OpenGL is no exception to that approach. Even OpenGL commands that 
ostensibly deal with curves actually produce polygons. You should find the following 
project quite pleasing.
Cylinder
Create a new directory under Chapter.8 and name it Cylinder. Create a new project, saving 
the unit as Cyl1.Pas and the project as Cyl.Dpr. As you may guess, the object to appear in 
this project is a cylinder. A cylinder has a circular base, so the surface is curved. Add two 
procedures (to the ones already there) in the interface section of the Shapes unit:

procedure Pyramid;	
procedure Cube;	
procedure CylinderBase;	
procedure Cylinder;	
In the implementation section, declare a couple of circle variables and implement the two 
procedures:

implementation	
	
var	
  circleX,	
  circleY: array[0..35] of glFloat;	
The CylinderBase procedure does a lot of floating-point math. This would be very expensive 
for your rendering if you had to perform the math every time you drew the cylinder. 
Fortunately you do not need to do the math over and over. That is the purpose of the arrays. 
Perform the calculations once in the CylinderBase procedure and store the results in the 
array:

procedure CylinderBase;	
var	
  around: integer;	
  pi18: double;	
begin	
  pi18 := pi/18.0;	
  for around := 0 to 35 do	


PAGE 184
   begin	
    circleX[around] := 0.4*sin(around*pi18);	
    circleY[around] := 0.4*cos(around*pi18);	
  end;	
end;	
The Cylinder procedure draws the cylinder as a triangle strip guided by the circle arrays. 
Notice that you are free to use various non-OpenGL programming statements between 
glBegin and glEnd.

procedure Cylinder;	
var	
  around: integer;	
begin	
  glBegin(GL_TRIANGLE_STRIP);	
    for around := 0 to 35 do	
    begin	
      glVertex3f(circleX[around],circleY[around],0.0);	
      glVertex3f(circleX[around],circleY[around],1.0);	
    end; {for}	
    glVertex3f(circleX[0],circleY[0],0.0);	
    glVertex3f(circleX[0],circleY[0],1.0);	
  glEnd;	
end;	
For the Cyl project, place on the form a TOpenGL component, two TTransform components, 
a TLight component, and two TMaterial components. Listing 8-13 shows the properties for 
the form and its components.

object Form1: TForm1
  Left = 310
  Top = 98
  Width = 328
  Height = 347
  Caption = 'Form1'
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OnCreate = FormCreate
  OnPaint = FormPaint
  OnResize = FormResize
  PixelsPerInch = 96
  TextHeight = 13
  object OpenGL1: TOpenGL
    pfdFlags = [Draw_To_Window, Support_OpenGL]
    PixelType = RGBA
    Projection = Perspective
    Enable = [depthTest, Lighting]


PAGE 185
     ClearRed = 0.6
    ClearGreen = 0.8
    ClearBlue = 0.8
    prxLeft = -1
    prxRight = 1
    prxBottom = -1
    prxTop = 1
    prxNear = 2
    prxFar = 9
    ColorBits = 24
    DepthBits = 32
    Left = 4
    Top = 4
  end
  object Transform1: TTransform
    TranX = 0.6
    TranY = -0.6
    TranZ = -2.2
    AxisX = 0.2
    AxisY = 1
    Angle = 15
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform1Paint
    Left = 4
    Top = 36
  end
  object Light1: TLight
    Which = 1
    Cutoff = 65
    AmbientRed = 0.2
    AmbientGreen = 0.2
    AmbientBlue = 0.2
    DiffuseRed = 1
    DiffuseGreen = 1
    DiffuseBlue = 1
    Direction = True
    Position = True
    Spotlight = True
    Enabled = True
    Left = 40
    Top = 36
  end
  object Transform2: TTransform
    TranX = -0.8
    TranY = 0.8
    TranZ = -3
    ScaleX = 1


PAGE 186
     ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform2Paint
    Left = 4
    Top = 68
  end
  object Material1: TMaterial
    ambRed = 1
    ambAlpha = 1
    difRed = 1
    difAlpha = 1
    aFace = faFront
    dFace = faFront
    sFace = faFront
    Left = 40
    Top = 68
  end
  object Material2: TMaterial
    ambBlue = 1
    ambAlpha = 1
    difBlue = 1
    difGreen = 1
    difRed = 0.6
    difAlpha = 1
    aFace = faBack
    dFace = faBack
    sFace = faBack
    Left = 72
    Top = 68
  end
end
Listing 8-13. Cyl project properties.
Listing 8-14 shows the event handlers for the form and its components. This time only one 
object appears. Since this and later projects include Shapes in the implementation uses 
clause, these listings are quite enough to describe the project.

procedure TForm1.Transform1Paint(Sender: TObject);
begin
  Light1.Apply;
end;

procedure TForm1.Transform2Paint(Sender: TObject);
begin
  Material1.Apply;
  Material2.Apply;
  Cylinder;
end;



PAGE 187
 procedure TForm1.FormCreate(Sender: TObject);
begin
  CylinderBase;
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);
end;

procedure TForm1.FormResize(Sender: TObject);
begin
  if OpenGL1.Smaller then
    FormPaint(Sender);
end;

procedure TForm1.FormPaint(Sender: TObject);
begin
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
  Transform1.Apply;
  Transform2.Apply;
  glFlush;
  OpenGL1.ErrorCheck(‘FormPaint’);
end;
Listing 8-14. Cyl project event handler methods.
When you compile and run, the cylinder appears smooth and round in the upper left corner 
as in Figure 8-7:
Just for fun, make the shading flat:

procedure TForm1.FormCreate(Sender: TObject);	
begin	
  CylinderBase;	


PAGE 188
   glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);	
  glShadeModel(GL_FLAT);	
end;	
When you compile and run, some of the triangles become much more evident under a flat 
shading model as in Figure 8-8:
Cone
A cone is another kind of object that can illustrate curved surfaces under OpenGL. As an 
added bonus you can reuse the arrays and the CylinderBase procedure from the earlier 
example. In the Shapes unit, declare a Cone procedure in the interface section and develop it 
in the implementation section:

procedure Cone;	
var	
  around: integer;	
begin	
  glBegin(GL_TRIANGLE_FAN);	
    glVertex3f(0.0,0.0,0.4);	
    for around := 35 downto 0 do	
      glVertex3f(circleX[around],circleY[around],0.0);	
    glVertex3f(circleX[35],circleY[35],0.0);	
  glEnd;	
end;	
Create a directory under Chapter.8 and name it Cone. Create a new project, saving the unit 
as Cone1.Pas and the project file as Cone0.Dpr. Place components, set properties, and add 
event handlers just like the Cyl project (without the flat shading call). Change 
Transform2Paint to call Cone 

PAGE 189
 instead of Cylinder. Change two properties of Transform2. Make Angle equal to 120 and 
AxisX equal to 1. Compile and run to behold a smoothly curved cone.
Dome
For even more curvature, try making a dome. Create a new directory under the Chapter.8 
directory and name it Dome. In it create a new project named Dome0.Dpr with a main unit 
named Dome1.Pas. Drop on components, set properties, and add event handlers just as you 
did for the Cone project. Change Transform2Paint to call Dome instead of Cone.
Start with a triangle fan for a cone, but smaller and shallower. Then make a few triangle 
strips to build out from the fan, each one somewhat like the cylinder. You will also need 
some more calculations to determine the base for each strip, and you will need an array in 
the Shapes unit to store the results.

var	
  circleX,	
  circleY,	
  circleZ: array[0..35] of glFloat;	
It will be substantially more complex than previous objects, but correspondingly more fun. 
Add a Dome procedure, shown in Listing 8-15, to the Shapes unit and reference it in the 
interface section:

procedure Dome;	
var	
  olddiam,	
  oldlayer,	
  diam,	
  layer: double;	
  place,	
  around: integer;	
begin	
  for place := 1 to 9 do	
  begin	
    layer := circleY[place];	
    diam := circleX[place]*2.5;	
    if place=1 then	
    begin	
      glFrontFace(GL_CCW);	
      glBegin(GL_TRIANGLE_FAN);	
        glVertex3f(0.0,0.0,0.4);	
        for around := 35 downto 0 do	
          glVertex3f(circleX[around]*diam,circleY[around]*diam,layer);	
        glVertex3f(circleX[35]*diam,circleY[35]*diam,layer);	


PAGE 190
     end else begin	
      glFrontFace(GL_CW);	
      glBegin(GL_TRIANGLE_STRIP);	
        for around := 0 to 35 do	
        begin	
          glVertex3f(circleX[around]*olddiam,circleY[around]*olddiam,	
            oldlayer);	
          glVertex3f(circleX[around]*diam,circleY[around]*diam,layer);	
        end; {for}	
        glVertex3f(circleX[0]*olddiam,circleY[0]*olddiam,oldlayer);	
        glVertex3f(circleX[0]*diam,circleY[0]*diam,layer);	
    end;	
    glEnd;	
    olddiam := diam;	
    oldlayer := layer;	
  end; {for}	
end;	
Listing 8-15. The Dome procedure.
There are better ways to create a sphere or portion thereof, but this example shows changing 
the front face definition from one part to the next. You may have noticed that the triangle fan 
requires counterclockwise winding, and the triangle strip requires clockwise winding. These 
are the opposite of what you will usually use for those primitives. The loops for these 
portions happen to specify the vertices in an order different from the usual for such 
structures. Pay little attention to that quirk. Instead focus on the combination of primitives to 
build a larger structure.
When you compile and run, you will see that you can make very round figures from flat 
little triangles as in Figure 8-9.
Highlights
The world is full of curves. In fact, straight lines are mostly a man-made feature rarely found 
in nature. The ability of OpenGL to simulate curved surfaces is a fascinating property that 
extends the potential realism of 

PAGE 191
 OpenGL scenes dramatically. Hopefully you found these curvature demonstrations quite 
exciting. Nevertheless, no matter how impressed you may have been by them, you have to 
admit that the curved surfaces shown so far are rather, well, dull.
OpenGL can represent shiny surfaces quite nicely by means of specular highlights on curved 
surfaces. You can use glMaterial commands to set the specular color (usually white) and to 
set the shininess of the surface. OpenGL allows shininess to range from 0 for no shininess to 
a maximum of 128. With the material components you can simply set the mShine property to 
a non-zero value to invoke glMaterial commands with the proper parameters.
For this illustration, a cylinder will serve well. Create a directory named High under the 
Chaper.8 directory. Create a new project, saving the unit as High1.Pas and the project as 
HighL.Dpr. Place OpenGL components and create event handlers as before. Listing 8-16 
shows the specific settings:

object Form1: TForm1
  Left = 310
  Top = 98
  Width = 328
  Height = 347
  Caption = 'Form1'
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OnCreate = FormCreate
  OnPaint = FormPaint
  OnResize = FormResize
  PixelsPerInch = 96
  TextHeight = 13
  object OpenGL1: TOpenGL
    pfdFlags = [Draw_To_Window, Support_OpenGL]
    PixelType = RGBA
    Projection = Perspective
    Enable = [depthTest, Lighting]
    ClearRed = 0.6
    ClearGreen = 0.8
    ClearBlue = 0.8
    prxLeft = -1
    prxRight = 1
    prxBottom = -1
    prxTop = 1
    prxNear = 2
    prxFar = 37


PAGE 192
     ColorBits = 24
    DepthBits = 32
    Left = 4
    Top = 4
  end
  object Transform1: TTransform
    TranX = 0.9
    TranZ = -2.6
    AxisY = 1
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform1Paint
    Left = 4
    Top = 36
  end
  object Light1: TLight
    Which = 1
    Cutoff = 30
    AmbientRed = 0.2
    AmbientGreen = 0.2
    AmbientBlue = 0.2
    DiffuseRed = 1
    DiffuseGreen = 1
    DiffuseBlue = 1
    SpecularRed = 1
    SpecularGreen = 1
    SpecularBlue = 1
    Direction = True
    Position = True
    Spotlight = True
    Enabled = True
    Left = 40
    Top = 36
  end
  object Transform2: TTransform
    TranX = 1.4
    TranY = -0.13
    TranZ = -4
    AxisY = 1
    ScaleX = 1
    ScaleY = 1
    ScaleZ = 1
    OnPaint = Transform2Paint
    Left = 4
    Top = 68
  end
  object Material1: TMaterial
    ambRed = 1


PAGE 193
     ambAlpha = 1
    difRed = 1
    difAlpha = 1
    aFace = faFront
    dFace = faFront
    sFace = faFront
    mShine = 128
    Left = 40
    Top = 68
  end
  object Material2: TMaterial
    ambBlue = 1
    ambAlpha = 1
    difBlue = 1
    difAlpha = 1
    aFace = faBack
    dFace = faBack
    sFace = faBack
    mShine = 128
    Left = 72
    Top = 68
  end
end
Listing 8-16. HighL properties.
Notice that the transformation component moves the cylinder to the side near the light so 
that a highlight will appear. Listing 8-17 shows the event handler code that supports this 
situation:

procedure TForm1.Transform1Paint(Sender: TObject);
begin
  Light1.Apply;
end;

procedure TForm1.Transform2Paint(Sender: TObject);
begin
  Material1.Apply;
  Material2.Apply;
  Cylinder;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  CylinderBase;
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);
end;

procedure TForm1.FormResize(Sender: TObject);
begin


PAGE 194
   if OpenGL1.Smaller then
    FormPaint(Sender);
end;

procedure TForm1.FormPaint(Sender: TObject);
begin
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
  Transform1.Apply;
  Transform2.Apply;
  glFlush;
  OpenGL1.ErrorCheck('FormPaint');
end;
Listing 8-17. HighL event handlers.

Compile and run as usual. Now the cylinder appears to have a very shiny surface that 
gleams in the presence of the spotlight. That is not bad for just illuminating some pixels on a 
flat monitor screen. Figure 8-10 gives an idea, but nothing can beat seeing it on your own 
computer in full color.




Identifier Reference
Identifier			Description
glFrontFace
An OpenGL command that specifies the winding for the front faces of polygons.
glLightModel
An OpenGL command to set the lighting model.
glShadeModel
An OpenGL command to set the shade model. It accepts either GL_FLAT or GL_SMOOTH.
GL_CCW
An OpenGL constant specifying counterclockwise winding for the front faces of polygons. 
This is the default.

PAGE 195
 
Identifier			Description
GL_CW
An OpenGL constant specifying clockwise winding for the front faces of polygons.
GL_FALSE
An OpenGL constant representing a setting of false.
GL_FLAT
An OpenGL constant specifying a flat shading model in which each polygon has a single 
shade selected from one of its vertices.
GL_LIGHT_MODEL_
TWO_SIDE 
An OpenGL constant to pass to glLightModel. It specifies lighting for both front and back 
faces of polygons.
GL_SMOOTH
An OpenGL constant specifying a smooth shading model in which the shade at a given point 
on a polygon is an intermediate value calculated from each of the vertices.
GL_TRIANGLES
An OpenGL constant to pass to glBegin. All the vertices listed before glEnd connect in 
groups of three to form triangles.
GL_TRIANGLE_FAN
An OpenGL constant to pass to glBegin. All the vertices before glEnd form a triangle fan.
GL_TRIANGLE_STRIP
An OpenGL constant to pass to glBegin. All the vertices before glEnd form a triangle strip. 
GL_TRUE
An OpenGL constant representing a setting of true.
Summary
This chapter developed several key concepts relating to triangles:
? General polygons may not lie in a plane and may not be convex. For efficiency, OpenGL 
assumes convex polygons.
? Triangles always lie in a plane and are always convex.
? Combinations of triangles can build any general polygon.
? OpenGL can render some combinations of triangles more efficiently as strips and fans.
? OpenGL can simulate curved surfaces with combinations of triangles.
? OpenGL can produce realistic specular highlights on simulated curved surfaces.


PAGE 196

