Sign in Join
Wiki copytemporary

OpenGL Tutorial: what changed

Revision 2 to 3. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11## Introduction
22
33OpenGL 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.
44
55You can find more information about OpenGL [here](http://www.opengl.org/about/overview/).
66
77### GLUT
88
99**Note:** GLUT is deprecated.
1010
1111GLUT (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.
1212
1313You can find more information about GLUT [here](http://www.opengl.org/resources/libraries/glut/).
1414
1515Many Operating Systems come with preinstalled GLUT, but if yours does not have one you can easily find it using [Google](http://www.google.com/).
1616
1717Windows binaries can be downloaded from [www.xmission.com](http://www.xmission.com/~nate/glut.html).
1818
1919The GLUT FPC units information is here [[OpenGL]].
2020
2121### GLFW
2222
2323See <http://www.glfw.org/>
2424
2525### LCL
2626
2727The Lazarus Component Library can be used with OpenGL too. Lazarus includes a TOpenGLControl - a LCL control with an OpenGL context. The lazarus package LazOpenGLContext can be found lazarus/components/opengl/lazopenglcontext.lpk. An example can be found in lazarus/examples/openglcontrol/openglcontrol_demo.lpi.
2828
2929### LCL / GLFW / GLUT
3030
3131When should you use GLUT, when LCL?
3232
3333- GLUT is better if you want to draw everything yourself.
3434- LCL is better for normal applications. For example a 3D editor needs a few OpenGL windows and the rest is a normal application using normal buttons, comboboxes, windows, modal windows, etc.
3535
3636The OpenGL part is pretty much the same. GLUT needs a dll under windows, where LCL typically runs out of the box, but a LCL executable is bigger.
3737
3838## Code samples
3939
4040### Creating your first LCL program
4141
4242Using LCL is typically the easiest way to access OpenGL with Lazarus. Since GLUT is deprecated, using the LCL is generally a good idea for a new OpenGL Lazarus project. Most of the code samples described below for GLUT are easy to translate into the LCL code, though you will have to find equivalents for the functions with the 'glut' prefix, for example instead of "glutSwapBuffers" we will use the LCL's "SwapBuffers" property to display our rendering. The one great feature that GLUT provides that is hard to do with the LCL is showing text on the screen (see GLUT's "Bitmap Fonts" section below). However, since this is your first LCL program, we will keep it simple by not showing any text.
4343
4444Lazarus comes with a example OpenGL program, you can find it in the folder Lazarus/Examples/openglcontrol. That example demonstrates many powerful features for creating an animated OpenGL image. However, it is also a relatively complicated program. Below is a minimal Lazarus project that mimics some of the features described in the GLUT samples described below. To create this, launch Lazarus and choose Project/NewProject to create a new application. Choose the Project/ProjectInspector menu item, click the 'Add..' button, go to the 'New Requirement' and and the "LazOpenGLContext" package. Next, paste the code below into your 'unit1.pas'. Then, click on your form and in the events tab of the object inspector link the "OnCreate" event to the function "FormCreate". You should now be able to run your new application by choosing the Run/Run menu item.
4545
4646The code creates a new OpenGL panel that fills the form. OpenGL draws a simple triangle in the form. Note that when you run the application you can resize the form and the triangle rescales proportionally to fill the form.
4747
4848``` pascal
4949unit Unit1;
5050
5151{$mode objfpc}{$H+}
5252
5353interface
5454
5555uses
5656 Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, OpenGLContext, gl;
5757
5858type
5959 TForm1 = class(TForm)
6060 procedure FormCreate(Sender: TObject);
6161 procedure GLboxPaint(Sender: TObject);
6262 private
6363 GLBox: TOpenGLControl;
6464 public
6565 end;
6666
6767var
6868 Form1: TForm1;
6969
7070implementation
7171
7272{$R *.lfm}
7373
7474procedure TForm1.GLboxPaint(Sender: TObject);
7575begin
7676 glClearColor(0.27, 0.53, 0.71, 1.0); // Set blue background
7777 glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
7878 glLoadIdentity;
7979 glBegin(GL_TRIANGLES);
8080 glColor3f(1, 0, 0);
8181 glVertex3f( 0.0, 1.0, 0.0);
8282 glColor3f(0, 1, 0);
8383 glVertex3f(-1.0,-1.0, 0.0);
8484 glColor3f(0, 0, 1);
8585 glVertex3f( 1.0,-1.0, 0.0);
8686 glEnd;
8787 GLbox.SwapBuffers;
8888end;
8989
9090procedure TForm1.FormCreate(Sender: TObject);
9191begin
9292 GLbox := TOpenGLControl.Create(Self);
9393 GLbox.AutoResizeViewport := true;
9494 GLBox.Parent := Self;
9595 GLBox.MultiSampling := 4;
9696 GLBox.Align := alClient;
9797 GLBox.OnPaint := @GLboxPaint; // for "mode delphi" this would be "GLBox.OnPaint := GLboxPaint"
9898 GLBox.invalidate;
9999end;
100100
101101end.
102102```
103103
104104### Creating your first GLUT program
105105
106106In order to use GLUT, you must first include **unit** glut and then initialize it. This is done using glutInit function. This function can parse the command line and set parameters for the main window, but it expects input in C/C++ style. You'll have to write your own function to make the conversion from ParamCount and ParamStr to C/C++ like command line parameters.
107107
108108``` pascal
109109procedure glutInitPascal(ParseCmdLine: Boolean);
110110var
111111 Cmd: array of PChar;
112112 CmdCount, I: Integer;
113113begin
114114 if ParseCmdLine then
115115 CmdCount := ParamCount + 1
116116 else
117117 CmdCount := 1;
118118 SetLength(Cmd, CmdCount);
119119 for I := 0 to CmdCount - 1 do
120120 Cmd[I] := PChar(ParamStr(I));
121121 glutInit(@CmdCount, @Cmd);
122122end;
123123```
124124
125125In essence, you create an array and fill it with strings from ParamStr. This function also takes a parameter that can control what is passed to glutInit -- either the whole command line or just the executable file name.
126126
127127ToDo: probably **glutInit(@argc, @argv);** is enough.
128128
129129More about glutInit: <http://www.opengl.org/resources/libraries/glut/spec3/node10.html>
130130
131131Next, you need to create a main window. Set the display mode for the main window using glutInitDisplayMode. It only takes one parameter which is a combination of flags. Usually GLUT_DOUBLE or GLUT_RGB or GLUT_DEPTH is all you will need.
132132
133133More about glutInitDisplayMode: <http://www.opengl.org/resources/libraries/glut/spec3/node12.html>
134134
135135The position and size of the window is controlled using glutInitWindowPosition and glutInitWindowSize. They take 2 parameters. X and Y coordinates in the former, and width and height in the latter. You can use glutGet to find the screen size and center the window.
136136
137137More 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>
138138
139139Finally, the window should be created using the glutCreateWindow function. It will create the window and set its caption through a parameter. As a result it will return the window's handle. This can be used with other functions that require it.
140140
141141More about glutCreateWindow: <http://www.opengl.org/resources/libraries/glut/spec3/node16.html>
142142
143143Before your program can enter the main loop, you must set some callbacks. You will a need callback for drawing the window, for resizing and for getting keyboard input. These callbacks are set using glutDisplayFunc, glutReshapeFunc and glutKeyboardFunc.
144144
145145More about setting callbacks: <http://www.opengl.org/resources/libraries/glut/spec3/node45.html#SECTION00080000000000000000>
146146
147147Your drawing function might look like this:
148148
149149``` pascal
150150procedure DrawGLScene; cdecl;
151151begin
152152 glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
153153 glutSwapBuffers;
154154end;
155155```
156156
157157This will only clear the window to the background color and reset the zbuffer (don't worry about zbuffer... more about that later).
158158
159159Your resize function might look like this:
160160
161161``` pascal
162162procedure ReSizeGLScene(Width, Height: Integer); cdecl;
163163begin
164164 if Height = 0 then
165165 Height := 1;
166166
167167 glViewport(0, 0, Width, Height);
168168 glMatrixMode(GL_PROJECTION);
169169 glLoadIdentity;
170170 gluPerspective(45, Width / Height, 0.1, 1000);
171171
172172 glMatrixMode(GL_MODELVIEW);
173173 glLoadIdentity;
174174end;
175175```
176176
177177With this code, you tell OpenGL where in the window it should draw and set matrices to the desired values (matrix functions will be explained later).
178178
179179Keyboard input is evaluated with the following callback:
180180
181181``` pascal
182182procedure GLKeyboard(Key: Byte; X, Y: Longint); cdecl;
183183begin
184184 if Key = 27 then
185185 Halt(0);
186186end;
187187```
188188
189189This function will instruct your program to exit if you press ESC key. GLUT is event driven and the only way to terminate your program is to call Halt inside one of your callback functions. If you close the window in some other way, it will disappear, but the program will continue to loop through the main routine indefinitely.
190190
191191To start the main loop, call glutMainLoop. It will enter a loop that never ends, which calls all your callback functions.
192192
193193The main part of your program might look like this:
194194
195195``` pascal
196196const
197197 AppWidth = 640;
198198 AppHeight = 480;
199199
200200procedure InitializeGL;
201201begin
202202 glClearColor(0.18, 0.20, 0.66, 0);
203203end;
204204
205205var
206206 ScreenWidth, ScreenHeight: Integer;
207207begin
208208 glutInitPascal(True);
209209 glutInitDisplayMode(GLUT_DOUBLE or GLUT_RGB or GLUT_DEPTH);
210210 glutInitWindowSize(AppWidth, AppHeight);
211211 ScreenWidth := glutGet(GLUT_SCREEN_WIDTH);
212212 ScreenHeight := glutGet(GLUT_SCREEN_HEIGHT);
213213 glutInitWindowPosition((ScreenWidth - AppWidth) div 2,
214214 (ScreenHeight - AppHeight) div 2);
215215 glutCreateWindow('OpenGL Tutorial 1');
216216
217217 InitializeGL;
218218
219219 glutDisplayFunc(@DrawGLScene);
220220 glutReshapeFunc(@ReSizeGLScene);
221221 glutKeyboardFunc(@GLKeyboard);
222222
223223 glutMainLoop;
224224end.
225225```
226226
227227The next tutorial will add some code that will draw a simple shape.
228228
229229Download source code or a linux/windows executable from [Lazarus CCR SourceForge](http://sourceforge.net/projects/lazarus-ccr/files/OpenGL%20Tutorial/).
230230
231231### Drawing a simple shape
232232
233233**Note:** The following parts are almost only OpenGL, so they run under GLUT and LCL. You can recognize GLUT specific functions with the prefix 'glu'.
234234
235235This time we shall add just a few lines of code and focus on explanation of some of the OpenGL functions.
236236
237237Let us explain code you already have.
238238
239239``` pascal
240240 ...
241241 glMatrixMode(GL_PROJECTION);
242242 glLoadIdentity;
243243 gluPerspective(45, Width / Height, 0.1, 1000);
244244
245245 glMatrixMode(GL_MODELVIEW);
246246 glLoadIdentity;
247247end;
248248```
249249
250250Using **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.
251251
252252Once 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.
253253
254254In 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.
255255
256256Now, you'll change model matrix... for this time, you just set it to identity.
257257
258258OK... and now, the code for drawing the first shape:
259259
260260``` pascal
261261procedure DrawGLScene; cdecl;
262262begin
263263 glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
264264
265265 glLoadIdentity;
266266 glTranslatef(0, 0, -5);
267267
268268 glBegin(GL_TRIANGLES);
269269 glColor3f(1, 0, 0);
270270 glVertex3f(-1, -1, 0);
271271
272272 glColor3f(0, 1, 0);
273273 glVertex3f(1, -1, 0);
274274
275275 glColor3f(0, 0, 1);
276276 glVertex3f(0, 1, 0);
277277 glEnd;
278278
279279 glutSwapBuffers;
280280end;
281281```
282282
283283We have allready used glClear function. It will just reset buffers. We'll skip next two functions and head for drawing ones.
284284
285285**glBegin** marks beginning of drawing block. After this function you can start entering vertices. Parameter describes how are vertices used when drawing:
286286
287287GL_POINTS: Treats each vertex as a single point. Vertex n defines point n. N points are drawn.
288288
289289GL_LINES: Treats each pair of vertices as an independent line segment. Vertices 2n-1 and 2n define line n. n/2 lines are drawn.
290290
291291GL_LINE_STRIP: Draws a connected group of line segments from the first vertex to the last. n-1 lines are drawn.
292292
293293GL_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.
294294
295295GL_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.
296296
297297GL_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.
298298
299299GL_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.
300300
301301GL_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.
302302
303303GL_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.
304304
305305GL_POLYGON: Draws a single, convex polygon. Vertices 1 through n define this polygon.
306306
307307![](/wiki-images/f/f3/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.
308308
309309Before 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.
310310
311311As 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.
312312
313313In the end you call glEnd functions that finishes drawing. You could now start another drawing block with new glBegin function if you wish.
314314
315315Download source code, linux executable or windows executable from [Lazarus CCR SourceForge](http://sourceforge.net/projects/lazarus-ccr/files/OpenGL%20Tutorial/).
316316
317317### Using display lists
318318
319319Sometimes 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.
320320
321321``` pascal
322322const
323323 LIST_OBJECT = 1;
324324
325325procedure CreateList;
326326begin
327327 glNewList(LIST_OBJECT, GL_COMPILE);
328328 glBegin(GL_TRIANGLE_FAN);
329329 glColor3f(1, 0, 0);
330330 glVertex3f(0, 0.5, 0);
331331
332332 glColor3f(1, 1, 0);
333333 glVertex3f(-0.5, -0.5, 0.5);
334334
335335 glColor3f(1, 1, 1);
336336 glVertex3f(0.5, -0.5, 0.5);
337337
338338 glColor3f(0, 1, 1);
339339 glVertex3f(0.5, -0.5, -0.5);
340340
341341 glColor3f(0, 0, 1);
342342 glVertex3f(-0.5, -0.5, -0.5);
343343
344344 glColor3f(0, 1, 0);
345345 glVertex3f(-0.5, -0.5, 0.5);
346346 glEnd;
347347
348348 glBegin(GL_QUADS);
349349 glColor3f(1, 1, 0);
350350 glVertex3f(-0.5, -0.5, 0.5);
351351
352352 glColor3f(1, 1, 1);
353353 glVertex3f(0.5, -0.5, 0.5);
354354
355355 glColor3f(0, 1, 1);
356356 glVertex3f(0.5, -0.5, -0.5);
357357
358358 glColor3f(0, 0, 1);
359359 glVertex3f(-0.5, -0.5, -0.5);
360360
361361 glColor3f(0, 1, 0);
362362 glVertex3f(-0.5, -0.5, 0.5);
363363 glEnd;
364364 glEndList;
365365end;
366366```
367367
368368**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 automatically.
369369
370370**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
371371
372372All created lists should be deleted. You will do that before program exits:
373373
374374``` pascal
375375procedure GLKeyboard(Key: Byte; X, Y: Longint); cdecl;
376376begin
377377 if Key = 27 then
378378 begin
379379 glDeleteLists(LIST_OBJECT, 1);
380380 Halt(0);
381381 end;
382382end;
383383```
384384
385385**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
386386
387387Now you know how to create and delete display lists... let's see how to draw them:
388388
389389``` pascal
390390procedure DrawGLScene; cdecl;
391391begin
392392 glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
393393
394394 glLoadIdentity;
395395 glTranslatef(-2, 0, -5);
396396 glRotatef(40, 1, 0, 1);
397397 glCallList(LIST_OBJECT);
398398
399399 glLoadIdentity;
400400 glTranslatef(1, -2, -10);
401401 glRotatef(62, 0, 1, 0);
402402 glCallList(LIST_OBJECT);
403403
404404 glLoadIdentity;
405405 glTranslatef(-4, 0.5, -15);
406406 glRotatef(200, 1, 0, 0);
407407 glCallList(LIST_OBJECT);
408408
409409 glutSwapBuffers;
410410end;
411411```
412412
413413![](/wiki-images/a/a5/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:
414414
415415GL_BYTE: list is treated as an array of signed bytes, each in the range -128 through 127.
416416
417417GL_UNSIGNED_BYTE: list is treated as an array of unsigned bytes, each in the range 0 through 255.
418418
419419GL_SHORT: list is treated as an array of signed two-byte integers, each in the range -32768 through 32767.
420420
421421GL_UNSIGNED_SHORT: list is treated as an array of unsigned two-byte integers, each in the range 0 through 65535.
422422
423423GL_INT: lists is treated as an array of signed four-byte integers.
424424
425425GL_UNSIGNED_INT: list is treated as an array of unsigned four-byte integers.
426426
427427GL_FLOAT: list is treated as an array of four-byte floating-point values.
428428
429429GL_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.
430430
431431GL_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.
432432
433433GL_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.
434434
435435That 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.
436436
437437Download source code, linux executable or windows executable from [Lazarus CCR SourceForge](http://sourceforge.net/projects/lazarus-ccr/files/OpenGL%20Tutorial/).
438438
439439### Full screen animation
440440
441441Entering full screen mode is easy with GLUT. Let's change main part of the program:
442442
443443``` pascal
444444const
445445 FSMode = '800x600:32@75';
446446
447447begin
448448 glutInitPascal(False);
449449 glutInitDisplayMode(GLUT_DOUBLE or GLUT_RGB or GLUT_DEPTH);
450450 glutGameModeString(FSMode);
451451 glutEnterGameMode;
452452 glutSetCursor(GLUT_CURSOR_NONE);
453453
454454 InitializeGL;
455455
456456 glutDisplayFunc(@DrawGLScene);
457457 glutReshapeFunc(@ReSizeGLScene);
458458 glutKeyboardFunc(@GLKeyboard);
459459 glutIdleFunc(@DrawGLScene);
460460
461461 glutMainLoop;
462462end.
463463```
464464
465465Since 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:
466466
467-`[width "x" height][":" bpp]["@" hertz]`
467+```text
468+[width "x" height][":" bpp]["@" hertz]
469+```
468470
469471In 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.
470472
471473Usually 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:
472474
473-`GLUT_CURSOR_RIGHT_ARROW`\
474-`GLUT_CURSOR_LEFT_ARROW`\
475-`GLUT_CURSOR_INFO`\
476-`GLUT_CURSOR_DESTROY`\
477-`GLUT_CURSOR_HELP`\
478-`GLUT_CURSOR_CYCLE`\
479-`GLUT_CURSOR_SPRAY`\
480-`GLUT_CURSOR_WAIT`\
481-`GLUT_CURSOR_TEXT`\
482-`GLUT_CURSOR_CROSSHAIR`\
483-`GLUT_CURSOR_UP_DOWN`\
484-`GLUT_CURSOR_LEFT_RIGHT`\
485-`GLUT_CURSOR_TOP_SIDE`\
486-`GLUT_CURSOR_BOTTOM_SIDE`\
487-`GLUT_CURSOR_LEFT_SIDE`\
488-`GLUT_CURSOR_RIGHT_SIDE`\
489-`GLUT_CURSOR_TOP_LEFT_CORNER`\
490-`GLUT_CURSOR_TOP_RIGHT_CORNER`\
491-`GLUT_CURSOR_BOTTOM_RIGHT_CORNER`\
492-`GLUT_CURSOR_BOTTOM_LEFT_CORNER`\
493-`GLUT_CURSOR_FULL_CROSSHAIR`\
494-`GLUT_CURSOR_NONE`\
495-`GLUT_CURSOR_INHERIT`
475+```text
476+GLUT_CURSOR_RIGHT_ARROW
477+GLUT_CURSOR_LEFT_ARROW
478+GLUT_CURSOR_INFO
479+GLUT_CURSOR_DESTROY
480+GLUT_CURSOR_HELP
481+GLUT_CURSOR_CYCLE
482+GLUT_CURSOR_SPRAY
483+GLUT_CURSOR_WAIT
484+GLUT_CURSOR_TEXT
485+GLUT_CURSOR_CROSSHAIR
486+GLUT_CURSOR_UP_DOWN
487+GLUT_CURSOR_LEFT_RIGHT
488+GLUT_CURSOR_TOP_SIDE
489+GLUT_CURSOR_BOTTOM_SIDE
490+GLUT_CURSOR_LEFT_SIDE
491+GLUT_CURSOR_RIGHT_SIDE
492+GLUT_CURSOR_TOP_LEFT_CORNER
493+GLUT_CURSOR_TOP_RIGHT_CORNER
494+GLUT_CURSOR_BOTTOM_RIGHT_CORNER
495+GLUT_CURSOR_BOTTOM_LEFT_CORNER
496+GLUT_CURSOR_FULL_CROSSHAIR
497+GLUT_CURSOR_NONE
498+GLUT_CURSOR_INHERIT
499+```
496500
497501**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.
498502
499503Now, let's look at the program termination where you need to exit full screen mode:
500504
501505``` pascal
502506procedure GLKeyboard(Key: Byte; X, Y: Longint); cdecl;
503507begin
504508 if Key = 27 then
505509 begin
506510 glutLeaveGameMode;
507511 Halt(0);
508512 end;
509513end;
510514```
511515
512516As you can see, all you need to do is to call glutLeaveGameMode.
513517
514518Now, we'll introduce some new matrix functions. First, let's change ReSizeGLScene function:
515519
516520``` pascal
517521procedure ReSizeGLScene(Width, Height: Integer); cdecl;
518522begin
519523 ...
520524 glMatrixMode(GL_MODELVIEW);
521525 glLoadIdentity;
522526 gluLookAt(0, 20, 25, 0, 0, 0, 0, 1, 0);
523527end;
524528```
525529
526530**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.
527531
528532OK, 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:
529533
530534``` pascal
531535procedure DrawGLScene; cdecl;
532536var
533537 T: Single;
534538begin
535539 T := glutGet(GLUT_ELAPSED_TIME) / 1000;
536540 glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
537541
538542 glPushMatrix;
539543 glRotatef(5 * T, 0, 1, 0);
540544 glColor3f(1, 1, 0);
541545 glutWireSphere(2, 20, 20);
542546 glPopMatrix;
543547
544548 glPushMatrix;
545549 glRotatef(90 * T, 0, 1, 0);
546550 glTranslatef(5, 0, 0);
547551 glRotatef(40 * T, 0, 1, 0);
548552 glColor3f(1, 0, 0);
549553 glutWireSphere(0.6, 10, 10);
550554 glPopMatrix;
551555
552556 glPushMatrix;
553557 glRotatef(60 * T, 0, 1, 0);
554558 glTranslatef(-3, 0, 9);
555559 glRotatef(50 * T, 0, 1, 0);
556560 glColor3f(0, 1, 0);
557561 glutWireSphere(1, 16, 16);
558562
559563 glPushMatrix;
560564 glRotatef(360 * T, 0, 1, 0);
561565 glTranslatef(-1.7, 0, 0);
562566 glRotatef(50 * T, 0, 1, 0);
563567 glColor3f(0, 0, 1);
564568 glutWireSphere(0.4, 10, 10);
565569 glPopMatrix;
566570
567571 glPopMatrix;
568572
569573 glutSwapBuffers;
570574end;
571575```
572576
573577![](/wiki-images/5/51/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.
574578
575579You 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.
576580
577581glRotatef 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.
578582
579583Download source code, linux executable or windows executable from [Lazarus CCR SourceForge](http://sourceforge.net/projects/lazarus-ccr/files/OpenGL%20Tutorial/).
580584
581585### Light
582586
583587This 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.
584588
585589For 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.
586590
587591``` pascal
588592unit utils;
589593
590594{$mode objfpc}{$H+}
591595
592596interface
593597
594598uses
595599 glut;
596600
597601function GetTotalTime: Single;
598602function GetDeltaTime: Single;
599603procedure FrameRendered(Count: Integer = 1);
600604function GetFPS: Single;
601605
602606implementation
603607
604608var
605609 OldTime: Integer = 0;
606610 FPSTime: Integer = 0;
607611 FPSCount: Integer = 0;
608612
609613function GetTotalTime: Single;
610614begin
611615 Result := glutGet(GLUT_ELAPSED_TIME) / 1000;
612616end;
613617
614618function GetDeltaTime: Single;
615619var
616620 NewTime: Integer;
617621begin
618622 NewTime := glutGet(GLUT_ELAPSED_TIME);
619623 Result := (NewTime - OldTime) / 1000;
620624 OldTime := NewTime;
621625end;
622626
623627procedure FrameRendered(Count: Integer);
624628begin
625629 Inc(FPSCount, Count);
626630end;
627631
628632function GetFPS: Single;
629633var
630634 NewTime: Integer;
631635begin
632636 NewTime := glutGet(GLUT_ELAPSED_TIME);
633637
634638 Result := FPSCount / ((NewTime - FPSTime) / 1000);
635639
636640 FPSTime := NewTime;
637641 FPSCount := 0;
638642end;
639643
640644end.
641645```
642646
643647As 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.
644648
645649Now, let's have fun with lights.
646650
647651OpenGL have several types of light... ambient, diffuse, point, spot, specular and emissive light.
648652
649653Ambient 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.
650654
651655Diffuse light can be represented as parallel light rays comming from far away. They will lit only vertices that are oriented towards the light source.
652656
653657Point 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.
654658
655659Spot 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.
656660
657661Just 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.
658662
659663Emissive 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.
660664
661665For simplicity we'll use only diffuse light in this tutorial. Later on, some other lights may appear in tutorials :)
662666
663667Let's see how to enable light in scene:
664668
665669``` pascal
666670const
667671 DiffuseLight: array[0..3] of GLfloat = (0.8, 0.8, 0.8, 1);
668672
669673 glEnable(GL_LIGHTING);
670674 glLightfv(GL_LIGHT0, GL_DIFFUSE, DiffuseLight);
671675 glEnable(GL_LIGHT0);
672676```
673677
674678As 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.
675679
676680More about glLightfv: <http://www.opengl.org//documentation/specs/man_pages/hardcopy/GL/html/gl/light.html>
677681
678682If 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:
679683
680684``` pascal
681685glEnable(GL_COLOR_MATERIAL);
682686glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
683687```
684688
685689![](/wiki-images/e/ed/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.
686690
687691One 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.
688692
689693After all those setting ups, light will shine up your cube :)
690694
691695Part of the text is copied from [The OpenGL Light Bible](http://www.falloutsoftware.com/tutorials/gl/gl8.htm)
692696
693697Download source code, linux executable or windows executable from [Lazarus CCR SourceForge](http://sourceforge.net/projects/lazarus-ccr/files/OpenGL%20Tutorial/).
694698
695699### Bitmap fonts
696700
697701Games and programs usually need to write some text on screen. GLUT provides several functions for drawing chars that are platform independent.
698702
699703First, we'll show how to use default bitmap fonts. Almost all code additions will be made to utils.pas unit.
700704
701705Since text will be drawn in 2D, we'll need to know width and height of viewport... so, we'll write two functions for that:
702706
703707``` pascal
704708function glGetViewportWidth: Integer;
705709var
706710 Rect: array[0..3] of Integer;
707711begin
708712 glGetIntegerv(GL_VIEWPORT, @Rect);
709713 Result := Rect[2] - Rect[0];
710714end;
711715
712716function glGetViewportHeight: Integer;
713717var
714718 Rect: array[0..3] of Integer;
715719begin
716720 glGetIntegerv(GL_VIEWPORT, @Rect);
717721 Result := Rect[3] - Rect[1];
718722end;
719723```
720724
721725We just get left/right, top/bottom and calculate width/height by subtracting them.
722726
723727There must be functions for entering and leaving 2D mode:
724728
725729``` pascal
726730procedure glEnter2D;
727731begin
728732 glMatrixMode(GL_PROJECTION);
729733 glPushMatrix;
730734 glLoadIdentity;
731735 gluOrtho2D(0, glGetViewportWidth, 0, glGetViewportHeight);
732736
733737 glMatrixMode(GL_MODELVIEW);
734738 glPushMatrix;
735739 glLoadIdentity;
736740
737741 glDisable(GL_DEPTH_TEST);
738742end;
739743
740744procedure glLeave2D;
741745begin
742746 glMatrixMode(GL_PROJECTION);
743747 glPopMatrix;
744748 glMatrixMode(GL_MODELVIEW);
745749 glPopMatrix;
746750
747751 glEnable(GL_DEPTH_TEST);
748752end;
749753```
750754
751755When 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.
752756
753757Leaving 2D mode just returns old matrices and enable ZBuffer.
754758
755759Now, we can create function for text drawing:
756760
757761``` pascal
758762procedure glWrite(X, Y: GLfloat; Font: Pointer; Text: String);
759763var
760764 I: Integer;
761765begin
762766 glRasterPos2f(X, Y);
763767 for I := 1 to Length(Text) do
764768 glutBitmapCharacter(Font, Integer(Text[I]));
765769end;
766770```
767771
768772**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.
769773
770774Character 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.
771775
772776This new functions will make text drawing very easy:
773777
774778``` pascal
775779procedure DrawGLScene; cdecl;
776780begin
777781 glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
778782
779783 glLoadIdentity;
780784 glTranslatef(0, 0, -5);
781785 glRotatef(GetTotalTime * 10, 0, 0.5, 0.5);
782786
783787 glColor3f(1, 0, 0);
784788 glutSolidCube(2);
785789
786790 glEnter2D;
787791
788792 glColor3f(0.2, 0.8 + 0.2 * Sin(GetTotalTime * 5), 0);
789793 glWrite(20, glGetViewportHeight - 20, GLUT_BITMAP_8_BY_13,
790794 Format('OpenGL Tutorial :: Bitmap Fonts :: FPS - %.2f FPS', [FPS]));
791795
792796 glColor3f(1, 1, 1);
793797 glWrite(50, glGetViewportHeight - 60, GLUT_BITMAP_9_BY_15, 'GLUT_BITMAP_9_BY_15');
794798 glWrite(50, glGetViewportHeight - 90, GLUT_BITMAP_8_BY_13, 'GLUT_BITMAP_8_BY_13');
795799 glWrite(50, glGetViewportHeight - 120, GLUT_BITMAP_TIMES_ROMAN_10, 'GLUT_BITMAP_TIMES_ROMAN_10');
796800 glWrite(50, glGetViewportHeight - 150, GLUT_BITMAP_TIMES_ROMAN_24, 'GLUT_BITMAP_TIMES_ROMAN_24');
797801 glWrite(50, glGetViewportHeight - 180, GLUT_BITMAP_HELVETICA_10, 'GLUT_BITMAP_HELVETICA_10');
798802 glWrite(50, glGetViewportHeight - 210, GLUT_BITMAP_HELVETICA_12, 'GLUT_BITMAP_HELVETICA_12');
799803 glWrite(50, glGetViewportHeight - 240, GLUT_BITMAP_HELVETICA_18, 'GLUT_BITMAP_HELVETICA_18');
800804
801805 glColor3f(0.5, 0.5, 1);
802806 glWrite(
803807 glGetViewportWidth - glutBitmapLength(GLUT_BITMAP_9_BY_15, LazText) - 5,
804808 10, GLUT_BITMAP_9_BY_15, LazText);
805809
806810 glLeave2D;
807811
808812 glutSwapBuffers;
809813
810814 FrameRendered;
811815end;
812816```
813817
814818![](/wiki-images/b/ba/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.
815819
816820Note: See how cube looks without light.
817821
818822Download source code, linux executable or windows executable from [Lazarus CCR SourceForge](http://sourceforge.net/projects/lazarus-ccr/files/OpenGL%20Tutorial/).
819823
820824### Textures
821825
822826It's time to use textures :)
823827
824828This tutorial will show how to draw textured polygons and how to blend textures using multipass technic. Since OpenGL has no builtin mechanism for loading textures, we'll use external library: [Vampyre Imaging Library](http://imaginglib.sourceforge.net/). We'll use just OpenGL helper functions, but you may find this lib handy for some other things to.
825829
826830Let's get started... we'll create display list for drawing textured rectangle:
827831
828832``` pascal
829833procedure CreateList;
830834begin
831835 glNewList(LIST_OBJECT, GL_COMPILE);
832836 glBegin(GL_QUADS);
833837 glTexCoord2f(1, 0);
834838 glVertex3f( 2, 2, 0);
835839 glTexCoord2f(0, 0);
836840 glVertex3f(-2, 2, 0);
837841 glTexCoord2f(0, 1);
838842 glVertex3f(-2,-2, 0);
839843 glTexCoord2f(1, 1);
840844 glVertex3f( 2,-2, 0);
841845 glEnd;
842846 glEndList;
843847end;
844848```
845849
846850Notice glTexCoord functions. They are used to specify which part of texture is assigned to vertex. Coordinates defined in this functions are from 0 to 1 (values greater than 1 are allowed but can generate different results). 0 is first pixel and 1 is last pixel. So, 0.5 will be right in the middle of texture.
847851
848852Texture loading is extremely easy with Vampyre Imaging Library:
849853
850854``` pascal
851855var
852856 Tex1, Tex2: GLuint;
853857
854858procedure InitializeGL;
855859begin
856860 glClearColor(0, 0, 0, 0);
857861 Tex1 := LoadGLTextureFromFile('ashwood.bmp');
858862 Tex2 := LoadGLTextureFromFile('Flare.bmp');
859863 glEnable(GL_TEXTURE_2D);
860864end;
861865```
862866
863867**LoadGLTextureFromFile** loads texture from file and returns it's ID. When texture is loaded it is allready setup for rendering. Last line just enables 2D textures.
864868
865869To draw textured polygon you have to bind texture and setup texture coordinations (texture coordinations are set in display list in this tutorial):
866870
867871``` pascal
868872 ...
869873 glLoadIdentity;
870874 glTranslatef(-5, 0, -15);
871875 glBindTexture(GL_TEXTURE_2D, Tex1);
872876 glCallList(LIST_OBJECT);
873877 ...
874878```
875879
876880**glBindTexture** function is used to select texture. When you draw polygins they will have selected texture on them. It's that easy :)
877881
878882So, using one texture is easy... but how to blend two textures. Basicly you draw polygon once with one texture, setup blending parameters, and draw polygon once more time with other texture. You can blend houndreds of textures this way. Let's see how code for this looks:
879883
880884``` pascal
881885 ...
882886 glLoadIdentity;
883887 glTranslatef(5, 0, -15);
884888 glBindTexture(GL_TEXTURE_2D, Tex1);
885889 glCallList(LIST_OBJECT);
886890
887891 glEnable(GL_BLEND);
888892 glBlendFunc(GL_ZERO, GL_SRC_COLOR);
889893 glLoadIdentity;
890894 glTranslatef(5, 0, -15);
891895 glBindTexture(GL_TEXTURE_2D, Tex2);
892896 glCallList(LIST_OBJECT);
893897 glDisable(GL_BLEND);
894898 ...
895899```
896900
897901As you can see, polygon is drawn first time like we allready know. Before second drawing we enable blending by calling glEnable(GL_BLEND). Blending means that finall pixel color is calculated like this:
898902
899-`DrawingColor * SRCBLEND + BackgroundColor * DESTBLEND`
903+```text
904+DrawingColor * SRCBLEND + BackgroundColor * DESTBLEND
905+```
900906
901907SRCBLEND and DESTBLEND are defined using glBlendFunc function. In this tutorial we set SRCBLEND to GL_ZERO (zero) and DESTBLENT to GL_SRC_COLOR (DrawingColor) and finall color is then:
902908
903-`DrawingColor * 0 + BackgroundColor * DrawingColor`
909+```text
910+DrawingColor * 0 + BackgroundColor * DrawingColor
911+```
904912
905913![](/wiki-images/d/d4/TexturesPic1.jpg)
906914
907915This means that background will get darker when you draw with dark colors... when you draw with white color, background color will not be changed. The result will look like this
908916
909917Next time, we'll use extensions to show how to use singlepass multitexturing.
910918
911919Download source code, linux executable or windows executable from [Lazarus CCR SourceForge](http://sourceforge.net/projects/lazarus-ccr/files/OpenGL%20Tutorial/).
912920
913921### Multitexturing (extensions)
914922
915923When youknow multipass multi texturing, singlepass is very easy. Texturing is separated in stages. First stage setup and draw first texture, second stage draws another one and so on. All you have to do is to setup texture stages and to render object.
916924
917925Let's see how code looks like:
918926
919927``` pascal
920928procedure InitializeGL;
921929begin
922930 Load_GL_ARB_multitexture;
923931 glClearColor(0, 0, 0, 0);
924932 Tex1 := LoadGLTextureFromFile('Lazarus.bmp');
925933 Tex2 := LoadGLTextureFromFile('Mask.bmp');
926934 glActiveTextureARB(GL_TEXTURE0_ARB);
927935 glEnable(GL_TEXTURE_2D);
928936 glBindTexture(GL_TEXTURE_2D, Tex1);
929937 glActiveTextureARB(GL_TEXTURE1_ARB);
930938 glEnable(GL_TEXTURE_2D);
931939 glBindTexture(GL_TEXTURE_2D, Tex2);
932940end;
933941```
934942
935943First we need load OpenGL extension that will allow us to use multitexture functions. Load_GL_ARB_multitexture will try to load those extensions and will return TRUE if operation was successful.
936944
937945To select texture stage you want to work on, use glActiveTextureARB function. It takes only one parameter that define which stage you want. After that all texture functions (enabling, disabling, binding, creating...) will affect that stage.
938946
939947Since we setup every thing in initialization function, all we have to do is to draw object:
940948
941949``` pascal
942950procedure DrawGLScene; cdecl;
943951begin
944952 glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
945953
946954 glLoadIdentity;
947955 glTranslatef(0, 0, -5);
948956
949957 glBegin(GL_QUADS);
950958 glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 1, 0);
951959 glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 1, 0);
952960 glVertex3f(2.516, 2, 0);
953961 glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0, 0);
954962 glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 0, 0);
955963 glVertex3f(-2.516, 2, 0);
956964 glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0, 1);
957965 glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 0, 1);
958966 glVertex3f(-2.516,-2, 0);
959967 glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 1, 1);
960968 glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 1, 1);
961969 glVertex3f(2.516,-2, 0);
962970 glEnd;
963971
964972 glutSwapBuffers;
965973end;
966974```
967975
968976![](/wiki-images/5/52/MultitexturePic1.jpg) As you can see, difference is only in defining texture coordinations. We now use glMultiTexCoord2fARB function that takes texture stage and texture coordinations. Every thing else is unchanged.
969977
970978Today almost all graphic cards supports at least 2 texture stages. Using singlepass multitexturing is faster than multipass version since you draw objects only once. If hardware supports singlepass multitexturing (Load_GL_ARB_multitexture returns TRUE) use it.
971979
972980Download source code, linux executable or windows executable from [Lazarus CCR SourceForge](http://sourceforge.net/projects/lazarus-ccr/files/OpenGL%20Tutorial/).
973981
974982### Render to texture
975983
976984This one will be short. OpenGL can capture current scene to texture so you can use it for texturing other objects (TV screen, mirror or some thing else). Well just render scene to texture and apply it to rotating plane.
977985
978986First, we must create empty texture which we'll use to capture scene:
979987
980988``` pascal
981989procedure SetupRenderTexture;
982990var
983991 Data: Pointer;
984992begin
985993 GetMem(Data, 256*256*3);
986994 glGenTextures(1, @RenderTexture);
987995 glBindTexture(GL_TEXTURE_2D, RenderTexture);
988996 glTexImage2D(GL_TEXTURE_2D, 0, 3, 256, 256, 0, GL_RGB, GL_UNSIGNED_BYTE, Data);
989997 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
990998 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
991999 FreeMem(Data);
9921000end;
9931001```
9941002
9951003Buffer for 256*256 RGB image is created and it is used to setup 2D texture.
9961004
9971005Main part is in drawing function:
9981006
9991007``` pascal
10001008procedure DrawGLScene; cdecl;
10011009var
10021010 TotalTime: Single;
10031011begin
10041012 glClearColor(0, 0, 0, 0);
10051013 glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
10061014 glEnable(GL_LIGHTING);
10071015 glDisable(GL_TEXTURE_2D);
10081016 glViewport(0, 0, 256, 256);
10091017
10101018 TotalTime := GetTotalTime;
10111019
10121020 glLoadIdentity;
10131021 glTranslatef(0, 0, -5);
10141022 glRotatef(50 * TotalTime, 1, 0, 0);
10151023 glRotatef(100 * TotalTime, 0, 1, 0);
10161024 glRotatef(50 * TotalTime, 0, 0, 1);
10171025
10181026 glColor3f(1, 1, 1);
10191027 glutSolidCube(2);
10201028
10211029 glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, 256, 256, 0);
10221030
10231031 glClearColor(0.18, 0.20, 0.66, 0);
10241032 glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
10251033 glDisable(GL_LIGHTING);
10261034 glEnable(GL_TEXTURE_2D);
10271035 glViewport(0, 0, AppWidth, AppHeight);
10281036
10291037 glLoadIdentity;
10301038 glTranslatef(0, 0, -7);
10311039 glRotatef(20 * TotalTime, 1, 0, 0);
10321040 glRotatef(50 * TotalTime, 0, 1, 0);
10331041
10341042 glBegin(GL_QUADS);
10351043 glTexCoord2f(1, 0);
10361044 glVertex3f(2, 2, 0);
10371045 glTexCoord2f(0, 0);
10381046 glVertex3f(-2, 2, 0);
10391047 glTexCoord2f(0, 1);
10401048 glVertex3f(-2,-2, 0);
10411049 glTexCoord2f(1, 1);
10421050 glVertex3f(2,-2, 0);
10431051 glEnd;
10441052
10451053 glutSwapBuffers;
10461054end;
10471055```
10481056
10491057![](/wiki-images/6/6e/RenderToTexturePic1.jpg) First, everything is setup for scene that will be captured. Viewport is reduced to 256*256 so it will fit into texture and scene is drawn. glCopyTexImage2D is used to capture scene to currently selected texture.
10501058
10511059When we have scene captured to texture, everything can be cleared again, viewport can be returned to original size and final scene is drawn using previous scene as texture.
10521060
10531061P.S. Captured texture can be saved using SaveGLTextureToFile function from [Vampyre Imaging Library](http://imaginglib.sourceforge.net/).
10541062
10551063Download source code, linux executable or windows executable from [Lazarus CCR SourceForge](http://sourceforge.net/projects/lazarus-ccr/files/OpenGL%20Tutorial/).
10561064
10571065### Vertex array
10581066
10591067OpenGL is capable of rendering primitives using data that is stored in buffers insted of calling glVertex. Buffers can be used to define vertex and texture coordinates, and colors (index and RGBA), normals and edge flags.
10601068
10611069In this tutorial well use only vertex and color buffers, and we'll show non-indexed and indexed drawing. Non-indexed mode draws buffers as streams. Indexed mode will draw buffer elements in order that is defined in index buffer. But enough talking... let's start coding.
10621070
10631071First, let's define some types and constants:
10641072
10651073``` pascal
10661074type
10671075 TVertex3f = record
10681076 X, Y, Z: Single;
10691077 end;
10701078
10711079 TColor3f = record
10721080 R, G, B: Single;
10731081 end;
10741082
10751083 VertexBuffer: array [0..5] of TVertex3f = (
10761084 (X : 1; Y : 1; Z : 0),
10771085 (X : -1; Y : 1; Z : 0),
10781086 (X : -1; Y : -1; Z : 0),
10791087 (X : 1; Y : 1; Z : 0),
10801088 (X : -1; Y : -1; Z : 0),
10811089 (X : 1; Y : -1; Z : 0)
10821090 );
10831091 ColorBuffer: array [0..5] of TColor3f = (
10841092 (R : 1; G : 0; B : 1),
10851093 (R : 0; G : 0; B : 1),
10861094 (R : 0; G : 1; B : 0),
10871095 (R : 1; G : 0; B : 1),
10881096 (R : 0; G : 1; B : 0),
10891097 (R : 1; G : 1; B : 0)
10901098 );
10911099```
10921100
10931101We have two buffers. One for vertex coordinates and one for vertex colors. This 6 vertices defines 2 triangles that forms rectangle.
10941102
10951103Drawing primitives using buffers is easy:
10961104
10971105``` pascal
10981106 glEnableClientState(GL_VERTEX_ARRAY);
10991107 glEnableClientState(GL_COLOR_ARRAY);
11001108 glVertexPointer(3, GL_FLOAT, 0, @VertexBuffer[0]);
11011109 glColorPointer(3, GL_FLOAT, 0, @ColorBuffer[0]);
11021110
11031111 glDrawArrays(GL_TRIANGLES, 0, Length(VertexBuffer));
11041112
11051113 glDisableClientState(GL_VERTEX_ARRAY);
11061114 glDisableClientState(GL_COLOR_ARRAY);
11071115```
11081116
11091117First we enable buffers we want to use using glEnableClientState function. Than we can select buffers we want to use. Every buffer type has own function for selecting (glColorPointer, glEdgeFlagPointer, glIndexPointer, glNormalPointer, glTexCoordPointer, glVertexPointer). First parameter in those functions defines how many numbers every element contains. For example, let's take vertex buffer. If this parameter is 2 than OpenGL expects that every element in buffer contains x and y coordinate. If this parameter is, for example, 4, than every element should contains x, y, z and w coordinate. Next parameter defines what type of data element contains (GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT, GL_FLOAT or GL_DOUBLE). Next one defines how many bytes are between each element. This way you can have buffer that contains vertex coordinates and some custom data. For arbitrary data type, this parameter can be calculated like this:
11101118
11111119``` pascal
11121120type
11131121 TBufferData = record
11141122 DataBefore: TDataBefore;
11151123 Vertex: TVertex;
11161124 DataAfter: TDataAfter;
11171125 end;
11181126```
11191127
1120-`Bytes between elements = SizeOf(TDataBefore) + SizeOf(TDataAfter)`
1128+```text
1129+Bytes between elements = SizeOf(TDataBefore) + SizeOf(TDataAfter)
1130+```
11211131
11221132Last parameter if pointer to the begginig of buffer.
11231133
11241134When buffers are selected we can draw them using glDrawArrays functions. All enabled buffers are used to draw primitives. What kind of polygons are being generated is defined in first parameter (same as in glBegin function). Next two defines subset of buffer which is used for drawing (start and count).
11251135
11261136When buffers are not needed you can disable them.
11271137
11281138To demonstrate indexed mode, I made some simple mesh class that can load vertex, color and index data from external files:
11291139
11301140``` pascal
11311141type
11321142 TMesh = class
11331143 private
11341144 FVertices: array of TVertex3f;
11351145 FColors: array of TColor3f;
11361146 FIndices: array of Integer;
11371147 procedure FreeBuffers;
11381148 public
11391149 constructor Create;
11401150 destructor Destroy; override;
11411151 procedure LoadMesh(FileName: String);
11421152 procedure DrawMesh;
11431153 end;
11441154```
11451155
11461156FVertices will contain data about vertices, FColors data about color and FIndices data about indices when external file is loaded.
11471157
11481158First we'll write some code that deals with creation and destruction of class:
11491159
11501160``` pascal
11511161procedure TMesh.FreeBuffers;
11521162begin
11531163 FVertices := nil;
11541164 FColors := nil;
11551165 FIndices := nil;
11561166end;
11571167
11581168constructor TMesh.Create;
11591169begin
11601170 FreeBuffers;
11611171end;
11621172
11631173destructor TMesh.Destroy;
11641174begin
11651175 FreeBuffers;
11661176 inherited Destroy;
11671177end;
11681178```
11691179
11701180File that will contain mesh data is simple text file. First row will contain number of vertices and indices separated by space character. After that row will come rows for every vertex and color. X, Y, Z, R, G and B all separated by space character. In the end, there will be rows for indices... every index number is written in its own row... so, for one triangle, data file will look like this:
11711181
1172-`3 3`\
1173-`-1 -1 0 1 1 1`\
1174-`1 -1 0 1 1 1`\
1175-`0 1 0 1 1 1`\
1176-`0`\
1177-`1`\
1178-`2`
1182+```text
1183+3 3
1184+-1 -1 0 1 1 1
1185+1 -1 0 1 1 1
1186+0 1 0 1 1 1
1187+0
1188+1
1189+2
1190+```
11791191
11801192This means that there is 3 vertices and 3 indices defined in file. First vrtex is at -1, -1, 0 and has color 1, 1, 1 and so on. Indices defines that order in which vertices are drawn (in this case vertices are drawn in the same order as they are defined).
11811193
11821194Code for loading this data will loke like this:
11831195
11841196``` pascal
11851197procedure TMesh.LoadMesh(FileName: String);
11861198var
11871199 MeshFile: TextFile;
11881200 VertexCount, IndexCount: Integer;
11891201 iV, iI: Integer;
11901202begin
11911203 FreeBuffers;
11921204
11931205 AssignFile(MeshFile, FileName);
11941206 Reset(MeshFile);
11951207
11961208 ReadLn(MeshFile, VertexCount, IndexCount);
11971209
11981210 SetLength(FVertices, VertexCount);
11991211 SetLength(FColors, VertexCount);
12001212 SetLength(FIndices, IndexCount);
12011213
12021214 for iV := 0 to VertexCount - 1 do
12031215 ReadLn(MeshFile,
12041216 FVertices[iV].X, FVertices[iV].Y, FVertices[iV].Z,
12051217 FColors[iV].R, FColors[iV].G, FColors[iV].B);
12061218
12071219 for iI := 0 to IndexCount - 1 do
12081220 ReadLn(MeshFile, FIndices[iI]);
12091221
12101222 CloseFile(MeshFile);
12111223end;
12121224```
12131225
12141226After loading data, we have everything for drawing:
12151227
12161228``` pascal
12171229procedure TMesh.DrawMesh;
12181230begin
12191231 glEnableClientState(GL_VERTEX_ARRAY);
12201232 glEnableClientState(GL_COLOR_ARRAY);
12211233 glVertexPointer(3, GL_FLOAT, 0, @FVertices[0]);
12221234 glColorPointer(3, GL_FLOAT, 0, @FColors[0]);
12231235
12241236 glDrawElements(GL_TRIANGLES, Length(FIndices), GL_UNSIGNED_INT, @FIndices[0]);
12251237
12261238 glDisableClientState(GL_VERTEX_ARRAY);
12271239 glDisableClientState(GL_COLOR_ARRAY);
12281240end;
12291241```
12301242
12311243As you can see, allmost everything is the same as for non-indexed drawing, except function that actually draw polygons. In this case we use glDrawElements function. For this one we specify what kind of polygons we want, how many indices are in index buffer, type of data in index buffer and pointer to the beginning of index buffer.
12321244
12331245![](/wiki-images/7/77/VertexArrayPic1.jpg)Full source code comes with mesh data file that this class can use to generates rectangle that is identical with one that is drawn using non-indexed mode. Mesh data file looks like this:
12341246
1235-`4 6`\
1236-`1 1 0 1 0 1`\
1237-`-1 1 0 0 0 1`\
1238-`-1 -1 0 0 1 0`\
1239-`1 -1 0 1 1 0`\
1240-`0`\
1241-`1`\
1242-`2`\
1243-`0`\
1244-`2`\
1245-`3`
1247+```text
1248+4 6
1249+1 1 0 1 0 1
1250+-1 1 0 0 0 1
1251+-1 -1 0 0 1 0
1252+1 -1 0 1 1 0
1253+0
1254+1
1255+2
1256+0
1257+2
1258+3
1259+```
12461260
12471261As you can see, there is data for only 4 vertices and 6 indices. So, first triangle is defined by vertices 0, 1 and 2, and the seccond one by vertices 0, 2 and 3. By using indexed mode we don't have to duplicate vertices.
12481262
12491263Download source code, linux executable or windows executable from [Lazarus CCR SourceForge](http://sourceforge.net/projects/lazarus-ccr/files/OpenGL%20Tutorial/).
12501264
12511265## Modern OpenGL using macOS
12521266
12531267> **Note:** the macOS OpenGL implementation is stuck at version 4.1 from 2010 and was deprecated by Apple on the release of macOS 10.14 (Mojave) in September 2018.
12541268
12551269A great feature of Lazarus is "write once, compile anywhere", where code should just work on macOS, Linux or Windows. However, to support all three of these systems you need to decide whether to only support legacy OpenGL or to use the leander "OpenGL Core" profile. For Linux and Windows users, recent versions of OpenGL are a superset of older versions. Therefore, a Linux user can mix and match old OpenGL code with modern shaders. This is **not** the case for Macintosh macOS (nee OS X) users.
12561270
12571271macOS provides two forms of support: the legacy mode supports all OpenGL features up to 2.1 (and GLSL 1.2). Alternatively, the user can select a modern **core** version of OpenGL (whereas most Linux and Windows users have access to the **compatibility** versions of OpenGL). The **core** versions of OpenGL [remove many deprecated legacy functions](http://github.prideout.net/modern-opengl-prezo/). This means that all the tutorials above will only compile in macOS's legacy mode. The core mode has no fixed function pipeline and therefore the developer must write their own shaders. In addition, the Core mode removes some [basic primitives like GL_QUADS](https://www.opengl.org/wiki/Legacy_OpenGL), so a quad needs to be replaced by two triangles (this actually makes sense: all vertices of a triangle are coplanar, but this is not necessarily the case for a quad, just like each leg of a 3-legged stool will always touch the ground, while a 4-legged stool might wobble if one leg is shorter than the others).
12581272
12591273The benefit of the core model is that it is lean, and generally easy to adapt to mobile devices (which use a similar, embedded form of OpenGL). See [Apple's website](https://developer.apple.com/opengl/capabilities/) to see the OpenGL supported by your operating system. A further wrinkle is that the Core modes of OpenGL are only supported on the Cocoa widgetset, so you can not use the Carbon widgetset. Three demo projects are available on [Github](https://github.com/neurolabusc/OpenGLCoreTutorials), and will compile using Lazarus 1.6.2 or later on Linux, Windows or macOS.
12601274
12611275## See also
12621276
12631277- [[Creating bindings for C libraries]]
12641278- [[OpenGL]]
12651279
12661280## External links
12671281
12681282- [OpenGL Light Tutorial](http://www.falloutsoftware.com/tutorials/gl/gl8.htm) - An OpenGL tutorial outlining how to create and manipulate lights, as well as deal with polygon surface reflections using normals.
12691283- [Apple OpenGL Documentation](https://developer.apple.com/library/archive/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_pg_concepts/opengl_pg_concepts.html).
12701284
12711285---
12721286*From the Free Pascal wiki, page "OpenGL Tutorial", as it stood on 2021-04-16 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history