Difference between revisions of "OpenGL Tutorial"

From Free Pascal wiki
Jump to navigationJump to search
(adjusted links to download page)
Line 576: Line 576:
  
 
Part of the text is copied from [http://www.falloutsoftware.com/tutorials/gl/gl8.htm The OpenGL Light Bible]
 
Part of the text is copied from [http://www.falloutsoftware.com/tutorials/gl/gl8.htm The OpenGL Light Bible]
 +
 +
Download source code, linux executable or windows executable from [http://sourceforge.net/project/showfiles.php?group_id=92177&package_id=199145 Lazarus CCR SourceForge].
 +
 +
=Bitmap fonts=
 +
 +
Games and programs usually need to write some text on screen. GLUT provides several functions for drawing chars that are platform independent.
 +
 +
First, we'll show how to use default bitmap fonts. Almost all code additions will be made to utils.pas unit.
 +
 +
Since text will be drawn in 2D, we'll need to know width and height of viewport... so, we'll write two functions for that:
 +
 +
function glGetViewportWidth: Integer;
 +
var
 +
  Rect: array[0..3] of Integer;
 +
begin
 +
  glGetIntegerv(GL_VIEWPORT, @Rect);
 +
  Result := Rect[2] - Rect[0];
 +
end;
 +
 +
function glGetViewportHeight: Integer;
 +
var
 +
  Rect: array[0..3] of Integer;
 +
begin
 +
  glGetIntegerv(GL_VIEWPORT, @Rect);
 +
  Result := Rect[3] - Rect[1];
 +
end;
 +
 +
We just get left/right, top/bottom and calculate width/height by subtracting them.
 +
 +
There must be functions for entering and leaving 2D mode:
 +
 +
procedure glEnter2D;
 +
begin
 +
  glMatrixMode(GL_PROJECTION);
 +
  glPushMatrix;
 +
  glLoadIdentity;
 +
  gluOrtho2D(0, glGetViewportWidth, 0, glGetViewportHeight);
 +
 +
  glMatrixMode(GL_MODELVIEW);
 +
  glPushMatrix;
 +
  glLoadIdentity;
 +
 +
  glDisable(GL_DEPTH_TEST);
 +
end;
 +
 +
procedure glLeave2D;
 +
begin
 +
  glMatrixMode(GL_PROJECTION);
 +
  glPopMatrix;
 +
  glMatrixMode(GL_MODELVIEW);
 +
  glPopMatrix;
 +
 +
  glEnable(GL_DEPTH_TEST);
 +
end;
 +
 +
When entering 2D mode, we save current matrices and set 2D matrix using <b>gluOrtho2D</b> function. This way if we draw some thing on 100, 100 it will be drawn on exactly 100 pixels from left edge of window, and 100 pixels form bottom edge (positive Y is up). Also, we disable ZBuffer. This way text won't alter ZBuffer.
 +
 +
Leaving 2D mode just returns old matrices and enable ZBuffer.
 +
 +
Now, we can create function for text drawing:
 +
 +
procedure glWrite(X, Y: GLfloat; Font: Pointer; Text: String);
 +
var
 +
  I: Integer;
 +
begin
 +
  glRasterPos2f(X, Y);
 +
  for I := 1 to Length(Text) do
 +
    glutBitmapCharacter(Font, Integer(Text[I]));
 +
end;
 +
 +
<b>glutBitmapCharacter</b> can draw only one character of selected font. First parameter is desired font (GLUT_BITMAP_9_BY_15, GLUT_BITMAP_8_BY_13, GLUT_BITMAP_TIMES_ROMAN_10, GLUT_BITMAP_TIMES_ROMAN_24, GLUT_BITMAP_HELVETICA_10, GLUT_BITMAP_HELVETICA_12 or GLUT_BITMAP_HELVETICA_18) and other one is character.
 +
 +
Character will be drawn at current raster position. To set desired raster position we call <b>glRasterPos</b> function. glRasterPos can handle different number and types of parameters just like glVertex function. Coordinate specified is transformed by model and projection matrix to get 2D coordinate where new raster position will be. Since we entered 2D mode, X and Y coordinates are actual 2D coordinates where drawing will occur.
 +
 +
This new functions will make text drawing very easy:
 +
 +
procedure DrawGLScene; cdecl;
 +
begin
 +
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
 +
 +
  glLoadIdentity;
 +
  glTranslatef(0, 0, -5);
 +
  glRotatef(GetTotalTime * 10, 0, 0.5, 0.5);
 +
 +
  glColor3f(1, 0, 0);
 +
  glutSolidCube(2);
 +
 +
  glEnter2D;
 +
 +
  glColor3f(0.2, 0.8 + 0.2 * Sin(GetTotalTime * 5), 0);
 +
  glWrite(20, glGetViewportHeight - 20, GLUT_BITMAP_8_BY_13, Format('OpenGL Tutorial :: Bitmap Fonts :: FPS - %.2f FPS',
 +
    [FPS]));
 +
 +
  glColor3f(1, 1, 1);
 +
  glWrite(50, glGetViewportHeight - 60, GLUT_BITMAP_9_BY_15, 'GLUT_BITMAP_9_BY_15');
 +
  glWrite(50, glGetViewportHeight - 90, GLUT_BITMAP_8_BY_13, 'GLUT_BITMAP_8_BY_13');
 +
  glWrite(50, glGetViewportHeight - 120, GLUT_BITMAP_TIMES_ROMAN_10, 'GLUT_BITMAP_TIMES_ROMAN_10');
 +
  glWrite(50, glGetViewportHeight - 150, GLUT_BITMAP_TIMES_ROMAN_24, 'GLUT_BITMAP_TIMES_ROMAN_24');
 +
  glWrite(50, glGetViewportHeight - 180, GLUT_BITMAP_HELVETICA_10, 'GLUT_BITMAP_HELVETICA_10');
 +
  glWrite(50, glGetViewportHeight - 210, GLUT_BITMAP_HELVETICA_12, 'GLUT_BITMAP_HELVETICA_12');
 +
  glWrite(50, glGetViewportHeight - 240, GLUT_BITMAP_HELVETICA_18, 'GLUT_BITMAP_HELVETICA_18');
 +
 +
  glColor3f(0.5, 0.5, 1);
 +
  glWrite(
 +
    glGetViewportWidth - glutBitmapLength(GLUT_BITMAP_9_BY_15, LazText) - 5,
 +
    10, GLUT_BITMAP_9_BY_15, LazText);
 +
 +
  glLeave2D;
 +
 +
  glutSwapBuffers;
 +
 +
  FrameRendered;
 +
end;
 +
[[Image:BitmapFontsPic1.jpg|thumb]]
 +
We draw red cube and rotate it, and some text to show how various bitmap fonts look like.
 +
<b>glutBitmapLength</b> function is used to find width of string so it could be aligned to right. Code can easily be altered to center text.
 +
 +
Note: See how cube looks without light.
  
 
Download source code, linux executable or windows executable from [http://sourceforge.net/project/showfiles.php?group_id=92177&package_id=199145 Lazarus CCR SourceForge].
 
Download source code, linux executable or windows executable from [http://sourceforge.net/project/showfiles.php?group_id=92177&package_id=199145 Lazarus CCR SourceForge].

Revision as of 14:10, 6 August 2006

OpenGL is the premier environment for developing portable, interactive 2D and 3D graphics applications. Since its introduction in 1992, OpenGL has become the industry's most widely used and supported 2D and 3D graphics application programming interface (API), bringing thousands of applications to a wide variety of computer platforms. OpenGL fosters innovation and speeds application development by incorporating a broad set of rendering, texture mapping, special effects, and other powerful visualization functions. Developers can leverage the power of OpenGL across all popular desktop and workstation platforms, ensuring wide application deployment.

You can find more information about OpenGL here.

GLUT (pronounced like the glut in gluttony) is the OpenGL Utility Toolkit, a window system independent toolkit for writing OpenGL programs. It implements a simple windowing application programming interface (API) for OpenGL. GLUT makes it considerably easier to learn about and explore OpenGL programming. GLUT provides a portable API so you can write a single OpenGL program that works across all PC and workstation OS platforms.

You can find more information about GLUT here.

Many OS comes with preinstalled GLUT, but if yours don’t have one you can easily find it using Google.

Windows binaries can be downloaded from www.xmission.com.

Creating first GLUT program

In order to use GLUT, you must first initialize it. This is done using glutInit function. This function can parse command line and set parameters for main window, but it expect input in C/C++ like style. You'll have to write your own function to make conversation from ParamCount and ParamStr to C/C++ like command line parameters.

procedure glutInitPascal(ParseCmdLine: Boolean); 
var
  Cmd: array of PChar;
  CmdCount, I: Integer;
begin
  if ParseCmdLine then
    CmdCount := ParamCount + 1
  else
    CmdCount := 1;
  SetLength(Cmd, CmdCount);
  for I := 0 to CmdCount - 1 do
    Cmd[I] := PChar(ParamStr(I));
  glutInit(@CmdCount, @Cmd);
end;

Basically, you create an array and fill it with strings from ParamStr. This function also takes one parameter that can control what is passed to glutInit... whole command line or just executable file name.

More about glutInit: http://www.opengl.org/resources/libraries/glut/spec3/node10.html

Next, you need to create main window. You'll set display mode for main window using glutInitDisplayMode. It takes only one parameter which is combination of flags. Usually GLUT_DOUBLE or GLUT_RGB or GLUT_DEPTH combination is all you need.

More about glutInitDisplayMode: http://www.opengl.org/resources/libraries/glut/spec3/node12.html

Position and size is controled using glutInitWindowPosition and glutInitWindowSize. They take 2 parameters. X and Y coordination, and width and height. You can also use glutGet to find screen size and set window to center of screen.

More about glutInitWindowPosition, glutInitWindowSize and glutGet: http://www.opengl.org/resources/libraries/glut/spec3/node11.html http://www.opengl.org/documentation/specs/glut/spec3/node70.html

Finnaly, window can be created using glutCreateWindow function. It will create window and set it caption to string you pass as parameter. As a result it will return window handle that can be used with other functions that require it.

More about glutCreateWindow: http://www.opengl.org/resources/libraries/glut/spec3/node16.html

Before program can enter main loop, you must set some callbacks. This time you need callback for drawing window, resizing and for getting keyboard input. This callbacks are set using glutDisplayFunc, glutReshapeFunc and glutKeyboardFunc.

More about setting callbacks: http://www.opengl.org/resources/libraries/glut/spec3/node45.html#SECTION00080000000000000000

Drawing function will look like this:

procedure DrawGLScene; cdecl;
begin
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
  glutSwapBuffers;
end;

This will only clear window to background color and reset zbuffer (don't worry about zbuffer... we'll tell more about it later).

Resize function will look like this:

procedure ReSizeGLScene(Width, Height: Integer); cdecl;
begin
  if Height = 0 then
    Height := 1;

  glViewport(0, 0, Width, Height);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity;
  gluPerspective(45, Width / Height, 0.1, 1000);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity;
end;

With this code, you tell OpenGL where it should draw and set matrices to desired value (matrice functions will be explained later).

You get keyboard input with this code:

procedure GLKeyboard(Key: Byte; X, Y: Longint); cdecl;
begin
  if Key = 27 then
    Halt(0);
end;

This function will instruct program to exit if you press ESC key. GLUT is made to be event driven and the only way to terminate program is to call Halt inside one of callback functions. If you close window on some other way, it will disapere but prugram will continue to loop througt main loop infinitly.

To start main loop you call glutMainLoop. It will enter loop that never ends and call all yours callback functions.

Main part of program looks like this:

const 
  AppWidth = 640; 
  AppHeight = 480; 

procedure InitializeGL; 
begin 
  glClearColor(0.18, 0.20, 0.66, 0); 
end; 

var 
  ScreenWidth, ScreenHeight: Integer; 
begin 
  glutInitPascal(True); 
  glutInitDisplayMode(GLUT_DOUBLE or GLUT_RGB or GLUT_DEPTH); 
  glutInitWindowSize(AppWidth, AppHeight); 
  ScreenWidth := glutGet(GLUT_SCREEN_WIDTH); 
  ScreenHeight := glutGet(GLUT_SCREEN_HEIGHT); 
  glutInitWindowPosition((ScreenWidth - AppWidth) div 2,
    (ScreenHeight - AppHeight) div 2); 
  glutCreateWindow('OpenGL Tutorial 1'); 

  InitializeGL; 

  glutDisplayFunc(@DrawGLScene); 
  glutReshapeFunc(@ReSizeGLScene); 
  glutKeyboardFunc(@GLKeyboard); 

  glutMainLoop; 
end.

Next tutorial will add some code that will draw simple shape.

Download source code, linux executable or windows executable from Lazarus CCR SourceForge.

Drawing a simple shape

This time we shall add just a few lines of code and focus on explanation of some of the OpenGL functions.

Let us explain code you allready have.

  .
  .
  .
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity;
  gluPerspective(45, Width / Height, 0.1, 1000);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity;
end;

Using glMatrixMode function you chose which matrix you want to change. OpenGL works with 3 matrices: GL_MODELVIEW: this one is used to move vertex to model space. GL_PROJECTION: this one is used to convert 3d coordinate to 2d coordinate for finall pixel position. GL_TEXTURE: this one is used to alter texture coordinates.

Once you chose matrix you want to change, you can call functions that affect matrix values. glLoadIdentity will reset matrix so it doesn't affect vertex position. Since almost all matrix functions multiply current matrix with a generated one, you sometimes need to clear matrix with this function.

In order to set perspective matrix, you can use gluPerspective function. Four parameters present the field of view, aspect ratio, near and far plane. It's that simple.

Now, you'll change model matrix... for this time, you just set it to identity.

OK... and now, the code for drawing the first shape:

procedure DrawGLScene; cdecl;
begin
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);

  glLoadIdentity;
  glTranslatef(0, 0, -5);

  glBegin(GL_TRIANGLES);
    glColor3f(1, 0, 0);
    glVertex3f(-1, -1, 0);

    glColor3f(0, 1, 0);
    glVertex3f(1, -1, 0);

    glColor3f(0, 0, 1);
    glVertex3f(0, 1, 0);
  glEnd;

  glutSwapBuffers;
end;

We have allready used glClear function. It will just reset buffers. We'll skip next two functions and head for drawing ones.

glBegin marks beginning of drawing block. After this function you can start entering vertices. Parameter describes how are vertices used when drawing: GL_POINTS: Treats each vertex as a single point. Vertex n defines point n. N points are drawn.

GL_LINES: Treats each pair of vertices as an independent line segment. Vertices 2n-1 and 2n define line n. n/2 lines are drawn.

GL_LINE_STRIP: Draws a connected group of line segments from the first vertex to the last. n-1 lines are drawn.

GL_LINE_LOOP: Draws a connected group of line segments from the first vertex to the last, then back to the first. Vertices n and n+1 define line n. The last line, however, is defined by vertices n and 1. n lines are drawn.

GL_TRIANGLES: Treats each triplet of vertices as an independent triangle. Vertices 3n-2, 3n-1 and 3n define triangle n. n/3 triangles are drawn.

GL_TRIANGLE_STRIP: Draws a connected group of triangles. One triangle is defined for each vertex presented after the first two vertices. For odd n, vertices n, n+1 and n+2 define triangle n. For even n, vertices n+1, n and n+2 define triangle n. n-2 triangles are drawn.

GL_TRIANGLE_FAN: Draws a connected group of triangles. One triangle is defined for each vertex presented after the first two vertices. Vertices 1. n+1 and n+2 define triangle n. n-2 triangles are drawn.

GL_QUADS: Treats each group of four vertices as an independent quadrilateral. Vertices 4n-3, 4n-2, 4n-1 and 4n define quadrilateral n. n/4 quadrilaterals are drawn.

GL_QUAD_STRIP: Draws a connected group of quadrilaterals. One quadrilateral is defined for each pair of vertices presented after the first pair. Vertices 2n-1, 2n, 2n+2 and 2n+1 define quadrilateral n. n/2-1 quadrilaterals are drawn. Note that the order in which vertices are used to construct a quadrilateral from strip data is different from that used with independent data.

GL_POLYGON: Draws a single, convex polygon. Vertices 1 through n define this polygon.

SimpleShapePic1.jpg

You'll draw single triangle and for that GL_TRIANGLES flag will do the trick. glVertex3f function defines the position of a vertex you want to draw. There are more glVertex* functions. Only difference is number and type of parameters they take. For instance... glVertex2i takes two parameters (x and y) of integer type. glVertex3f will almost always be just what you need.

Before glVertex you can set color, material, texture... For simplicity you'll just specify color for each vertex in this tutorial. Color is set using glColor3f function. glColor can also take different set of parameters like glVertex.

As we look through code we can see that Z is set to 0 for all vertices. Since you set near plane to 0.1, triangle will not be visible. That is where those two functions we skipped in the beginning jump in. We already know that glLoadIdentity reset matrix. glTranslatef moves triangles by X, Y and Z values you provide. Since you set Z to -5 (negative Z is farther from camera) all vertices will be drawn 5 units far from point of view and will be visible.

In the end you call glEnd functions that finishes drawing. You could now start another drawing block with new glBegin function if you wish.

Download source code, linux executable or windows executable from Lazarus CCR SourceForge.

Using display lists

Sometimes you'll need to draw some object multiple times on scene. OpenGL has ability to build display lists which make drawing a bit faster. Creating display list is very easy... just draw vertices as you did in previous tutorial and enclose them with glNewList and glEndList calls.

const
  LIST_OBJECT = 1;

procedure CreateList;
begin
  glNewList(LIST_OBJECT, GL_COMPILE);
    glBegin(GL_TRIANGLE_FAN);
      glColor3f(1, 0, 0);
      glVertex3f(0, 0.5, 0);

      glColor3f(1, 1, 0);
      glVertex3f(-0.5, -0.5, 0.5);

      glColor3f(1, 1, 1);
      glVertex3f(0.5, -0.5, 0.5);

      glColor3f(0, 1, 1);
      glVertex3f(0.5, -0.5, -0.5);

      glColor3f(0, 0, 1);
      glVertex3f(-0.5, -0.5, -0.5);

      glColor3f(0, 1, 0);
      glVertex3f(-0.5, -0.5, 0.5);
    glEnd;

    glBegin(GL_QUADS);
      glColor3f(1, 1, 0);
      glVertex3f(-0.5, -0.5, 0.5);

      glColor3f(1, 1, 1);
      glVertex3f(0.5, -0.5, 0.5);

      glColor3f(0, 1, 1);
      glVertex3f(0.5, -0.5, -0.5);

      glColor3f(0, 0, 1);
      glVertex3f(-0.5, -0.5, -0.5);

      glColor3f(0, 1, 0);
      glVertex3f(-0.5, -0.5, 0.5);
    glEnd;
  glEndList;
end;

glNewList creates new display list and all drawing functions will be recorded until glEndList is called. The first parameter for glNewList function is list ID. Every list is defined by it's ID. If list with given ID is already created it fill be cleared before recording. If the second parameter is GL_COMPILE then all drawing functions are just recorded, but if it is GL_COMPILE_AND_EXECUTE then they are recorded and executed automaticly.

glIsList function can help you with display lists. It can tell if some list ID is already filled with data. Another useful function is glGenLists. It will create multiple empty display lists. You pass number of display lists you need and you get ID of the first one. If you require n lists, and get r ID, generated display lists are: r, r+1, r+2,..., r+n-1

All created lists should be deleted. You will do that before program exits:

procedure GLKeyboard(Key: Byte; X, Y: Longint); cdecl;
begin
  if Key = 27 then
  begin
    glDeleteLists(LIST_OBJECT, 1);
    Halt(0);
  end;
end;

glDeleteLists takes 2 parameters, ID of display list and number of lists to delete. If ID is r, and number of lists to delete is n, deleted lists are: r, r+1, r+2,..., r+n-1

Now you know how to create and delete display lists... let's see how to draw them:

procedure DrawGLScene; cdecl;
begin
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);

  glLoadIdentity;
  glTranslatef(-2, 0, -5);
  glRotatef(40, 1, 0, 1);
  glCallList(LIST_OBJECT);

  glLoadIdentity;
  glTranslatef(1, -2, -10);
  glRotatef(62, 0, 1, 0);
  glCallList(LIST_OBJECT);

  glLoadIdentity;
  glTranslatef(-4, 0.5, -15);
  glRotatef(200, 1, 0, 0);
  glCallList(LIST_OBJECT);

  glutSwapBuffers;
end;
DisplayListsPic1.jpg

Using glCallList you can draw only one display list. In this tutorial, before drawing display list, you change model matrix and draw object in different places.

Some times you would like to draw multiple lists at once. That is possible using glCallLists function. It takes number of lists you want to draw, type of array that contains display list IDs and array with display list IDs. Type of list can be one of the following:

GL_BYTE: list is treated as an array of signed bytes, each in the range -128 through 127.

GL_UNSIGNED_BYTE: list is treated as an array of unsigned bytes, each in the range 0 through 255.

GL_SHORT: list is treated as an array of signed two-byte integers, each in the range -32768 through 32767.

GL_UNSIGNED_SHORT: list is treated as an array of unsigned two-byte integers, each in the range 0 through 65535.

GL_INT: lists is treated as an array of signed four-byte integers.

GL_UNSIGNED_INT: list is treated as an array of unsigned four-byte integers.

GL_FLOAT: list is treated as an array of four-byte floating-point values.

GL_2_BYTES: list is treated as an array of unsigned bytes. Each pair of bytes specifies a single display list ID. The value of the pair is computed as 256 times the unsigned value of the first byte plus the unsigned value of the second byte.

GL_3_BYTES: list is treated as an array of unsigned bytes. Each triplet of bytes specifies a single display list ID. The value of the triplet is computed as 65536 times the unsigned value of the first byte, plus 256 times the unsigned value of the second byte, plus the unsigned value of the third byte.

GL_4_BYTES: list is treated as an array of unsigned bytes. Each quadruplet of bytes specifies a single display list ID. The value of the quadruplet is computed as 16777216 times the unsigned value of the first byte, plus 65536 times the unsigned value of the second byte, plus 256 times the unsigned value of the third byte, plus the unsigned value of the fourth byte.

That is for now. Next tutorial will show how to create little planetary system. We'll talk about matrices and how to make animated scene that doesn't depend of number of frames per second.

Download source code, linux executable or windows executable from Lazarus CCR SourceForge.

Full screen animation

Entering full screen mode is easy with GLUT. Let's change main part of the program:

const
  FSMode = '800x600:32@75';

begin
  glutInitPascal(False);
  glutInitDisplayMode(GLUT_DOUBLE or GLUT_RGB or GLUT_DEPTH);
  glutGameModeString(FSMode);
  glutEnterGameMode;
  glutSetCursor(GLUT_CURSOR_NONE);

  InitializeGL;

  glutDisplayFunc(@DrawGLScene);
  glutReshapeFunc(@ReSizeGLScene);
  glutKeyboardFunc(@GLKeyboard);
  glutIdleFunc(@DrawGLScene);

  glutMainLoop;
end.

Since we don't want GLUT to parse command line this time we call glutInitPascal with False parameter. As you can see, there is no code for window creation. GLUT have glutEnterGameMode that create full screen window. To specify what kind of full screen mode you want, you call glutGameModeString function which takes string that defines mode you like. Format of that string is:

[width "x" height][":" bpp]["@" hertz]

In FSMode string we declared that full screen mode should be 800x600, with 32bit pallete and 75Hz refresh. It is possible to skip one of the group. If you omit size, GLUT will try to use current one or first smaller that can work. That policy is used and for other parameters.

Usually in full screen mode cursor is not visible. To hide cursor you use glutSetCursor function. It takes only one parameter which describes cursor you would like to see:

GLUT_CURSOR_RIGHT_ARROW
GLUT_CURSOR_LEFT_ARROW
GLUT_CURSOR_INFO
GLUT_CURSOR_DESTROY
GLUT_CURSOR_HELP
GLUT_CURSOR_CYCLE
GLUT_CURSOR_SPRAY
GLUT_CURSOR_WAIT
GLUT_CURSOR_TEXT
GLUT_CURSOR_CROSSHAIR
GLUT_CURSOR_UP_DOWN
GLUT_CURSOR_LEFT_RIGHT
GLUT_CURSOR_TOP_SIDE
GLUT_CURSOR_BOTTOM_SIDE
GLUT_CURSOR_LEFT_SIDE
GLUT_CURSOR_RIGHT_SIDE
GLUT_CURSOR_TOP_LEFT_CORNER
GLUT_CURSOR_TOP_RIGHT_CORNER
GLUT_CURSOR_BOTTOM_RIGHT_CORNER
GLUT_CURSOR_BOTTOM_LEFT_CORNER
GLUT_CURSOR_FULL_CROSSHAIR
GLUT_CURSOR_NONE
GLUT_CURSOR_INHERIT

glutIdleFunc defines callback function that you want to be called every time you program has no messages to process. Since we just want to render new frame if there is nothing to do, just set idle function to DrawGLScene. Some other tutorials show that idle function should send refresh message insted of drawing, but that way I have 50-100 frames less than using method I described.

Now, let's look at the program termination where you need to exit full screen mode:

procedure GLKeyboard(Key: Byte; X, Y: Longint); cdecl;
begin
  if Key = 27 then
  begin
    glutLeaveGameMode;
    Halt(0);
  end;
end;

As you can see, all you need to do is to call glutLeaveGameMode.

Now, we'll introduce some new matrix functions. First, let's change ReSizeGLScene function:

procedure ReSizeGLScene(Width, Height: Integer); cdecl;
begin
  .
  .
  .
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity;
  gluLookAt(0, 20, 25, 0, 0, 0, 0, 1, 0);
end;

gluLookAt create matrix that will define from where are you look to objects. First 3 parameters are X, Y and Z coordinate of position of camera. Next 3 parameters are X, Y and Z coordinate of point where camera look at, and last 3 parameters defines "up" vector (where is "up" for the camera). Usually, up is positive y axis.

OK, let's draw now. Since you set matrix with gluLookAt that should be used with all objects, you can't just use glLoadIdentity to reset matrix for next object... you'll save previous matrix state and restore it after object is drawn:

procedure DrawGLScene; cdecl;
var
  T: Single;
begin
  T := glutGet(GLUT_ELAPSED_TIME) / 1000;
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);

  glPushMatrix;
    glRotatef(5 * T, 0, 1, 0);
    glColor3f(1, 1, 0);
    glutWireSphere(2, 20, 20);
  glPopMatrix;

  glPushMatrix;
    glRotatef(90 * T, 0, 1, 0);
    glTranslatef(5, 0, 0);
    glRotatef(40 * T, 0, 1, 0);
    glColor3f(1, 0, 0);
    glutWireSphere(0.6, 10, 10);
  glPopMatrix;

  glPushMatrix;
    glRotatef(60 * T, 0, 1, 0);
    glTranslatef(-3, 0, 9);
    glRotatef(50 * T, 0, 1, 0);
    glColor3f(0, 1, 0);
    glutWireSphere(1, 16, 16);

    glPushMatrix;
      glRotatef(360 * T, 0, 1, 0);
      glTranslatef(-1.7, 0, 0);
      glRotatef(50 * T, 0, 1, 0);
      glColor3f(0, 0, 1);
      glutWireSphere(0.4, 10, 10);
    glPopMatrix;

  glPopMatrix;

  glutSwapBuffers;
end;
FullScreenAnimationPic1.jpg

glPushMatrix i glPopMatrix are used to save and restore matrix state. As you can see, we save matrix state, then change matrix in order to draw object in right place, and then restore old matrix state.

You may wonder what is T variable for. Well, it is used to determen animation speed. Every change that depends on time is multiplied with T. That way animation speed is constant on every frame rate. glutGet function with GLUT_ELAPSED_TIME parameter returns time in milliseconds from glutInit is called. By dividing that value with 1000, we get time in seconds.

glRotatef function create rotation matrix. First parameter is angle in degrees, and last 3 parameters defines axis around which rotation will be done. Since you multiplied angle with T, object will be rotated by that angle in exactly 1 second.

Download source code, linux executable or windows executable from Lazarus CCR SourceForge.

Light

This tutorial will introduce some light to the scene. You'll make rotating cube and one light which will add some realism to the scene, but first let's make some utility unit.

For now it will have only basic functions to help us getting current and delta (time that elapsed from one render to other render call) times and for calculating frames per second.

unit utils;

{$mode objfpc}{$H+}

interface

uses
  glut;

function GetTotalTime: Single;
function GetDeltaTime: Single;
procedure FrameRendered(Count: Integer = 1);
function GetFPS: Single;

implementation

var
  OldTime: Integer = 0;
  FPSTime: Integer = 0;
  FPSCount: Integer = 0;

function GetTotalTime: Single;
begin
  Result := glutGet(GLUT_ELAPSED_TIME) / 1000;
end;

function GetDeltaTime: Single;
var
  NewTime: Integer;
begin
  NewTime := glutGet(GLUT_ELAPSED_TIME);
  Result := (NewTime - OldTime) / 1000;
  OldTime := NewTime;
end;

procedure FrameRendered(Count: Integer);
begin
  Inc(FPSCount, Count);
end;

function GetFPS: Single;
var
  NewTime: Integer;
begin
  NewTime := glutGet(GLUT_ELAPSED_TIME);

  Result := FPSCount / ((NewTime - FPSTime) / 1000);

  FPSTime := NewTime;
  FPSCount := 0;
end;

end.

As you can see, there is nothing complicated in this unit. Time is simply saved betwen calls and difference is returned. FrameRendered should be called every time you draw scene so function can calculate FPS.

Now, let's have fun with lights.

OpenGL have several types of light... ambient, diffuse, point, spot, specular and emissive light.

Ambient light is something like Sun. When sun rays pass through the window of a room they hit the walls and are reflected and scattered into all different directions which averagely brightens up the whole room. All vertices are lit with ambient light.

Diffuse light can be represented as parallel light rays comming from far away. They will lit only vertices that are oriented towards the light source.

Point light lights all around it. It is like a fire ball, it send light rays all around it and lights vertices that are oriented towards light source and that are close enough.

Spot light is like light from flashlight. It is simply a point light source with a small light cone radius. All vertices that falls inside of cone and are close enough are lit.

Just like Diffuse light, Specular light is a directional type of light. It comes from one particular direction. The difference between the two is that specular light reflects off the surface in a sharp and uniform way. The rendering of specular light relies on the angle between the viewer and the light source. From the viewer’s standpoint specular light creates a highlighted area on the surface of the viewed object known as specular highlight or specular reflection.

Emissive light is a little different than any other previously explained light components. This light comes out of object you draw but don't lit other objects in nearby.

For simplicity we'll use only diffuse light in this tutorial. Later on, some other lights may appear in tutorials :)

Let's see how to enable light in scene:

const
  DiffuseLight: array[0..3] of GLfloat = (0.8, 0.8, 0.8, 1);

  glEnable(GL_LIGHTING);
  glLightfv(GL_LIGHT0, GL_DIFFUSE, DiffuseLight);
  glEnable(GL_LIGHT0);

As you see, we enable lighting in OpenGL so lights affect scene you are rendering. Light parameters are set with glLightfv function. It takes 3 parameters... one for light number you want to change (OpenGL suports up to 8 lights), next tells OpenGL what light parameter to change, and the last one is new parameter for light. You'll set just diffuse color for light in this tutorial. After that, you can enable light and there will be light in the scene... but... that is not all.

More about glLightfv: http://www.opengl.org//documentation/specs/man_pages/hardcopy/GL/html/gl/light.html

If you want to use lights you can't just set color for vertex... you must set material for vertices. Let's setup material for drawing:

glEnable(GL_COLOR_MATERIAL);
glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
LightPic1.jpg

You expected something more complicated, do you? :) Well, this code allows us to use glColor function to set material to vertices. By using glEnable function and GL_COLOR_MATERIAL flag, you can define what material properties will glColor change. glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE) tells OpenGL that glColor changes ambient and diffuse material. We'll discus materials more in later tutorials.

One more thing that is important when using lights... every vertex must have normal associated with it. Normal is used to find the direction of vertex so light can be calculated properly. You'll use GLUT function to draw cube and it provides normals for us, so this time we'll just walk by normals.

After all those setting ups, light will shine up your cube :)

Part of the text is copied from The OpenGL Light Bible

Download source code, linux executable or windows executable from Lazarus CCR SourceForge.

Bitmap fonts

Games and programs usually need to write some text on screen. GLUT provides several functions for drawing chars that are platform independent.

First, we'll show how to use default bitmap fonts. Almost all code additions will be made to utils.pas unit.

Since text will be drawn in 2D, we'll need to know width and height of viewport... so, we'll write two functions for that:

function glGetViewportWidth: Integer;
var
  Rect: array[0..3] of Integer;
begin
  glGetIntegerv(GL_VIEWPORT, @Rect);
  Result := Rect[2] - Rect[0];
end;

function glGetViewportHeight: Integer;
var
  Rect: array[0..3] of Integer;
begin
  glGetIntegerv(GL_VIEWPORT, @Rect);
  Result := Rect[3] - Rect[1];
end;

We just get left/right, top/bottom and calculate width/height by subtracting them.

There must be functions for entering and leaving 2D mode:

procedure glEnter2D;
begin
  glMatrixMode(GL_PROJECTION);
  glPushMatrix;
  glLoadIdentity;
  gluOrtho2D(0, glGetViewportWidth, 0, glGetViewportHeight);

  glMatrixMode(GL_MODELVIEW);
  glPushMatrix;
  glLoadIdentity;

  glDisable(GL_DEPTH_TEST);
end;

procedure glLeave2D;
begin
  glMatrixMode(GL_PROJECTION);
  glPopMatrix;
  glMatrixMode(GL_MODELVIEW);
  glPopMatrix;

  glEnable(GL_DEPTH_TEST);
end;

When entering 2D mode, we save current matrices and set 2D matrix using gluOrtho2D function. This way if we draw some thing on 100, 100 it will be drawn on exactly 100 pixels from left edge of window, and 100 pixels form bottom edge (positive Y is up). Also, we disable ZBuffer. This way text won't alter ZBuffer.

Leaving 2D mode just returns old matrices and enable ZBuffer.

Now, we can create function for text drawing:

procedure glWrite(X, Y: GLfloat; Font: Pointer; Text: String);
var
  I: Integer;
begin
  glRasterPos2f(X, Y);
  for I := 1 to Length(Text) do
    glutBitmapCharacter(Font, Integer(Text[I]));
end;

glutBitmapCharacter can draw only one character of selected font. First parameter is desired font (GLUT_BITMAP_9_BY_15, GLUT_BITMAP_8_BY_13, GLUT_BITMAP_TIMES_ROMAN_10, GLUT_BITMAP_TIMES_ROMAN_24, GLUT_BITMAP_HELVETICA_10, GLUT_BITMAP_HELVETICA_12 or GLUT_BITMAP_HELVETICA_18) and other one is character.

Character will be drawn at current raster position. To set desired raster position we call glRasterPos function. glRasterPos can handle different number and types of parameters just like glVertex function. Coordinate specified is transformed by model and projection matrix to get 2D coordinate where new raster position will be. Since we entered 2D mode, X and Y coordinates are actual 2D coordinates where drawing will occur.

This new functions will make text drawing very easy:

procedure DrawGLScene; cdecl;
begin
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);

  glLoadIdentity;
  glTranslatef(0, 0, -5);
  glRotatef(GetTotalTime * 10, 0, 0.5, 0.5);

  glColor3f(1, 0, 0);
  glutSolidCube(2);

  glEnter2D;

  glColor3f(0.2, 0.8 + 0.2 * Sin(GetTotalTime * 5), 0);
  glWrite(20, glGetViewportHeight - 20, GLUT_BITMAP_8_BY_13, Format('OpenGL Tutorial :: Bitmap Fonts :: FPS - %.2f FPS',
    [FPS]));

  glColor3f(1, 1, 1);
  glWrite(50, glGetViewportHeight - 60, GLUT_BITMAP_9_BY_15, 'GLUT_BITMAP_9_BY_15');
  glWrite(50, glGetViewportHeight - 90, GLUT_BITMAP_8_BY_13, 'GLUT_BITMAP_8_BY_13');
  glWrite(50, glGetViewportHeight - 120, GLUT_BITMAP_TIMES_ROMAN_10, 'GLUT_BITMAP_TIMES_ROMAN_10');
  glWrite(50, glGetViewportHeight - 150, GLUT_BITMAP_TIMES_ROMAN_24, 'GLUT_BITMAP_TIMES_ROMAN_24');
  glWrite(50, glGetViewportHeight - 180, GLUT_BITMAP_HELVETICA_10, 'GLUT_BITMAP_HELVETICA_10');
  glWrite(50, glGetViewportHeight - 210, GLUT_BITMAP_HELVETICA_12, 'GLUT_BITMAP_HELVETICA_12');
  glWrite(50, glGetViewportHeight - 240, GLUT_BITMAP_HELVETICA_18, 'GLUT_BITMAP_HELVETICA_18');

  glColor3f(0.5, 0.5, 1);
  glWrite(
    glGetViewportWidth - glutBitmapLength(GLUT_BITMAP_9_BY_15, LazText) - 5,
    10, GLUT_BITMAP_9_BY_15, LazText);

  glLeave2D;

  glutSwapBuffers;

  FrameRendered;
end;
BitmapFontsPic1.jpg

We draw red cube and rotate it, and some text to show how various bitmap fonts look like. glutBitmapLength function is used to find width of string so it could be aligned to right. Code can easily be altered to center text.

Note: See how cube looks without light.

Download source code, linux executable or windows executable from Lazarus CCR SourceForge.