OpenGL Tutorial/es

From Lazarus wiki
Revision as of 14:48, 24 March 2012 by Vincent (talk | contribs) (Text replace - "delphi>" to "syntaxhighlight>")
Jump to navigationJump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

Deutsch (de) English (en) español (es) français (fr) 日本語 (ja) 한국어 (ko) русский (ru) 中文(中国大陆)‎ (zh_CN)

   OpenGL es el principal entorno para el diseño de aplicaciones 2D y 3D portables. Desde su introducción en 1992, OpenGL ha llegado a ser la interfaz de programación de aplicaciones (API) gráficas 2D y 3D más ampliamente utilizadao en la industria, aportando miles de aplicaciones a una gran variedad de plataformas de computación. OpenGL promueve la innovación y acelera el diseño de aplicaciones mediante la incorporación de un amplio conjunto de acabados, aplicACIÓN de texturas, efectos especiales y otras potentes funciones de visualización. Los desarrolladores pueden llevar la potencia de OpenGL a través de todos los escritorios más extendidos y plataformas de estaciones de trabajo, asegurando un amplio desarrollo de aplicaciones.

   Puedes encontrar más información acerca de OpenGL aquí.

GLUT

   GLUT es el conjunto de utilidades OpenGL, un conjunto de utilidades de ventana independiente para escribir programas OpenGL. Implementa una interfaz gráfica simple de programación de aplicaciones (API) para OpenGL. GLUT hace que sea considerablemente más fácil de aprender y explorar la programación con OpenGL aportando una API portable de forma que permite escribir un programa que funcionará en todos los PC y estaciones de trabajo en las diferentes plataformas existentes.

   Se puede encontrar más información acerca de GLUT aquí.

   Algunos Sistemas Operativos llevan preinstalado GLUT, pero en el caso de que no sea así se puede encontrar fácilmente utilizando Google.

   Los binarios para Windows se pueden descargar desde www.xmission.com.

   La información de las unidades de FPC para GLUT se encuentran aquí OpenGL.

LCL

   La librería de componente de Lazarus también se puede utilizar con OpenGL. Lazarus incluye un control "TOpenGLControl" - un control LCL con un contexto OpenGL. Se puede encontrar el paquete LazOpenGLContext en lazarus/components/opengl/lazopenglcontext.lpk. Además podemos encontrar un ejemplo en lazarus/examples/openglcontrol/openglcontrol_demo.lpi.

LCL / GLUT

   ¿Cuando utilizar GLUT, y cuando LCL?

  • GLUT es mejor si deseas dibujar todo tu mismo.
  • LCL es mejor para las aplicaciones habituales. Por ejemplo un editor 3D necesita una pocas ventanas OpenGL y el resto es una aplicación normal utilizando botones, cajas de edición, ventanas, ventanas modales, etc.

   La parte OpenGL es otro tanto de lo mismo. GLUT necesita una dll en windows, mientras la LCL no requiere ninguna, pero el ejecutable LCL es bastante más grande.

Creando tu primer programa GLUT

   Para utilizar GLUT, primero hay que iniciarlo. Esto se hace utilizando la función glutInit. Esta función analiza la línea de ordenes y pasa los parámetros a la ventana principal, pero espera la entrada en estilo C/C++. Tendrás que escribir tu propia función para realizar la conversión de ParamCount y ParamStr a parámetros al estilo C/C++ de línea de ordenes.

 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;

   En esencia lo que hace es crear una matriz y rellenarla con las cadenas (strings) de ParamStr. Este procedimiento recibe un parámetro que permite controlar lo que se pasa a glutInit, bien la línea de mandatos completa o bien sólo el nombre del fichero ejecutable.

   ToDo: probablemente glutInit(@argc, @argv); sea suficiente.

   Más acerca de glutInit: http://www.opengl.org/resources/libraries/glut/spec3/node10.html

   Ahora, necesitas crear una ventana principal. Ajusta el modo de visualización de la ventana principal con glutInitDisplayMode. Únicamente recibe un parámetro, que es una combinación de indicadores. En general todo lo que se necesita es GLUT_DOUBLE o GLUT_RGB o GLUT_DEPTH.

   Más acerca de glutInitDisplayMode: http://www.opengl.org/resources/libraries/glut/spec3/node12.html

   La posición y el tamaño de la ventana se controla utilizando glutInitWindowPosition y glutInitWindowSize. Toman 2 parámetros. Las coordenada X e Y en el former, y width (ancho) y height (alto) en el latter. Se puede utilizar glutGet para obtener el tamaño de pantalla y centrar la ventana.

   Más acerca de glutInitWindowPosition, glutInitWindowSize y glutGet: http://www.opengl.org/resources/libraries/glut/spec3/node11.html http://www.opengl.org/documentation/specs/glut/spec3/node70.html

   Finalmente se debería crear la ventana utilizando la función glutCreateWindow. Esto generará la ventana y establecerá su caption a través de un parámetro. Como resultado se retornará el manejador (handler) de ventana. Esto se puede utilizar con otras funciones que lo requieran.

   Más acerca de glutCreateWindow: http://www.opengl.org/resources/libraries/glut/spec3/node16.html

   Antes de que el programa pueda entrar en el bucle principal, se deben establecer algunas callbacks. Se necesitarán las callback para dibujar la ventana, para su redimensionado y para obtener las entradas desde el teclado. Estas callbacks se establecen utilizando glutDisplayFunc, glutReshapeFunc y glutKeyboardFunc.

   Más acerca de las callbacks: http://www.opengl.org/resources/libraries/glut/spec3/node45.html#SECTION00080000000000000000

   Tu función de dibujo podría parecerse a lo siguiente:

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

   Esto solamente borra la ventana con el color de fondo y realiza un reset del zbuffer (no te preocupes acerca de que puede ser el zbuffer...más acerca de esto más adelante).

   La función de redimensionado podría parecerse a esto:

 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;

   Con este código, se le indica a OpenGL donde debe dibujar en la ventana y establecer las matrices a los valores deseados (las funciones de matriz se explicarán más adelante).

   La entrada de teclado (Keyboard) se evalúa con la siguiente callback:

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

   Esta función indica al programa que debe salir del mismo si se presiona la tecla ESC. GLUT está orientado a eventos y el único medio de salir del programa es llamar Halt desde dentro de una de las funciones callback. Si se cierra la ventana de algún otro modo, desaparecerá, pero el programa continuará en el bucle dentro de la rutina principal indefinidamente. Para comenzar el bucle principal hay que llamar a la función glutMainLoop. Esto iniciará un bucle que nunca finalizará y que realizará las llamadas a todas las funciones callback.

   La porción principal del programa puede parecerse a lo siguiente:

 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.

   La siguiente tutoría añade un poco de código que dibuja una figura sencilla.

   Descarga el código fuente o un ejecutable Linux / Windows desde Lazarus CCR SourceForge.

Dibujando una figura simple

Nota: Las siguientes partes tratan principalmente sobre OpenGL, por lo que utilizan las librerías GLUT y LCL. Las funciones específicas de GLUT se reconocen porque llevan el prefijo 'glu'.

   Esta vez solo añadiremos unas pocas líneas de código y nos centraremos en explicar algunas de las funciones de OpenGL.

   Expliquemos el código que ya tenemos escrito.

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

   Usando la función glMatrixMode usted elige que matriz quiere cambiar. OpenGL trabaja con 3 matrices: GL_MODELVIEW: Esta se usa para mover el vértice al espacio del modelo. GL_PROJECTION: Esta se usa para convertir las coordenadas 3d en coordenadas 2d para determinar la posición final del pixel. GL_TEXTURE: Esta se usa para alterar las coordenadas de la textura.

   Una vez que usted elige la matriz que quiere cambiar, puede llamar a las funciones que afectan a los valores de la matriz. glLoadIdentity reinicializa la matriz por lo que no afectará a la posición del vértice. Puesto que casi todas las funciones de matrices multiplican la matriz actual por una generada, a veces necesitará limpiar la matriz con esta función.

   Para establecer una matriz de perspectiva, puedes usar la función gluPerspective. Cuatro parámetros presentan el punto de vista, aspecto (aspect radio), distancia la plano. Es así de simple.

   Ahora, vas a cambiar la matriz modelo... esta vez la ajustamos a la identidad.

   Bien... y ahora, el código para dibujar la primera figura:

 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;

   Ya hemos usado la función glClear. Reseteará los buffers. Vamos a salatar las dos siguientes funciones, y vayamos a las de dibujo.

   glBegin marca el inicio de un bloque de dibujo. Después de esta función puedes empezar a introducir vértices. Los parámetros describen como van a ser usados los vértices cuando se dibujen.

  • GL_POINTS: Trata cada vértices como un único punto. Vertex n define el punto n. Pueden dibujarse n.
  • GL_LINES: Trata cada par de vérices como un segmento independiente. Los vértices 2n-1 y 2n definen la línea n. Se dibujan n/2 lineas.
  • GL_LINE_STRIP: Dibuja un grupo de segmentos de línea conectados entre sí. Se dibujan n-1 lineas.
  • GL_LINE_LOOP: Dibuja un grupo de segmentos de lineas conectados entre sí. El primer vértice y el último también están conectados, formando un lazo. Los vertices n y n+1 definen la línea n. La última linea es definida por los vertices n y 1. Se dibujan n líneas.
  • GL_TRIANGLES: Trata cada triplete de vértices como un triángulo independiente. Los vértices 3n-2, 3n-1 y 3n definen el triángulo. Se dibujan n/3 triángulos.
  • GL_TRIANGLE_STRIP: Dibuja un grupo de triángulos conectados. Un triángulo es definido por cada vértice creado después de los dos primeros vértices. 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. Se dibujan n-2 triángulos.
  • GL_TRIANGLE_FAN: Dibuja un grupo de triángulos conectados. Un triángulo se define con cada vértice creado después de los dos primeros vértices. Los vértices 1. n+1 y n+2 definen el triángulo n. Se dibujan n-2 triángulos.
  • GL_QUADS: Trata cada grupo de 4 vértices como un cuadrilátero independiente. Los vértices 4n-3, 4n-2, 4n-1 y 4n definen

el cuadrilátero n. Se dibujan n/4 cuadriláteros.

  • GL_QUAD_STRIP: Dibuja un grupo de cuadriláteros conectados. Un cuadrilátero es definido por cada par de vértices creados después del primer par. Los vértices 2n-1, 2n, 2n+2 y 2n+1 definen el cuadrilátero n. Se dibujan n/2-1 cuadriláteros. 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, textura... Por simplicidad you'll just specify color para cada vértice en este tutorial. Color is set usando la función glColor3f. glColor tambien puede tomar un conjunto de parámetros diferentes como glVertex.

   As we look through code podemos ver que Z is set to 0 para todos los vértices. 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.

Usando Listas de despliegue

   A veces necesitaras dibujar algunos objetos varias veces en la escena. OpenGL tiene la habiliad de construir Listas de despliegue (display lists) con las cuales dibujar se hace un poco más rápido. Crear una lista de despliegue es muy fácil... dibuja los vértices como hiciste en el tutorial anterior, y enciérralos con las llamadas glNewList y glEndList.

 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 crea una nueva lista de despliegue y todas las funciones de dibujo serán grabadas hasta que se llame a glEndList. El primer parámetro para la función glNewList es list ID. Toda lista es definida por su ID. Si una lista con un ID dado ya está creada, será borrada antes de grabar. Si el segundo parámetro es GL_COMPILE entonces todas las funciones solamente son grabadas, pero si es GL_COMPILE_AND_EXECUTE entonces son guardadas y ejecutadas automáticamente.

   glIsList es una función que puede ayudar con tus listas de despliegue. Puede decirte si un ID de una lista ya contiene datos.

   Otra función útil es glGenLists'. Creará múltiples listas. Puedes pasar el número de listas de despliegue que necesitas y obtendrás el ID del primero. Si necesitas n listas, y obtienes un ID, listas de despliegue son: r, r+1, r+2,..., r+n-1

   Todas las listas deben ser borradas antes de que el programa acabe:

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

   glDeleteLists tiene 2 parámetros, ID de lista de despliegue y un número de listas a borrar. Si el ID es r, y el número de listas a borrar es n, las litas borradas son: r, r+1, r+2,..., r+n-1

   Ahora conoces como crear y borrar listas, vamos a ver como dibujarlas:

 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

   Usando glCallList puedes dibujar solo un display list. En este tutorial, antes de display list, puedes cambiar la matriz modelo, y dibujar en diferentes lugares.

   A veces te gustaria dibujar multiples listas de una vez. Esto es posible usando la función glCallLists. Coge el número de listas que quieres dibujar, es un array que contiene los ID de display list. El tipo de lista puede ser una de los siguientes:

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

   Esto es todo por ahora. En el siguiente tutorial, mostraré como crear un pequeño sistema planetario. Hablaremos sobre las matrices y como hacer una escena animada que no dependa del número de cuadros por segundo.

   Descarga el codido fuente, linux ejecutable o windows ejecutable de 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.

Luz

   Este tutorial introducirá algo de luz en la escena. You'll make rotating cube and one light which will add some realism to the scene, but first let's make some utility unit.

   Por el momento tendremos sólamente funciones básicas para ayudarnos a obtener los tiempos current y delta (tiempo transcurrido desde la llamada de un render a otro) y para calcular los frames por segundo.

 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.

   Como puedes observar, no hay complejidades en esta unit. Se salva simplente el tiempo entre las llamadas y se retorna la diferencia. Se debe hacer una llamada a FrameRendered cada vez que se dibuja una escena de manera que la función pueda calcular los FPS (Frames por segundo:Frames Per Second).

   Ahora, divirtámonos con las luces.

   OpenGL tiene varios tipos de luz... ambiente, difusa, point, spot, especular y emissive.

   La luz ambiente es algo así como la del Sol. Cuando los rayos solares pasan a través de la ventana de una habitación inciden sobre las paredes y tiene lugar su reflexión y su dispersión en todas las direcciones lo cual averagely brightens up la habitación completa. Todos los vertices son lit con luz ambiente.

   La Luz Difusa puede representarse como rayos de luz paralelos llegando desde puntos lejanos. They will lit only vertices that are oriented towards the light source.

   Point light lights all around it. Es como una bola de fuego, it send light rays all around it and lights vertices that are oriented towards light source and that are close enough.

   Spot light es como la luz de una linterna. Es simplemente un punto de luz source con un cono de luz de radio pequeño. 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.

   Por simplicidad utilizaremos únicamente luz difusa en este tutorial. Con el tiempo puede que aparezcan otros tipos de luz pero por ahora nos centraremos en este. :)

   Veamos como habilitar la luz en la escena:

 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 que parámetro de la luz cambiar, y este último es el nuevo parámetro para la luz. You'll set just diffuse color for light en este tutorial. After that, you can enable light and there will be light en la escena... pero... eso no es todo.

   Para saber más sobre glLightfv: http://www.opengl.org//documentation/specs/man_pages/hardcopy/GL/html/gl/light.html

   Si necesitas utilizar luces no basta simplemente con establecer el color de los vértices... debes establecer el material de los vértices. Establezcamos el material para el dibujo:

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

   ¿Esperabas hacer algo más complicado? :) Bien, este código nos permitirá utilizar la función glColor para configurar los materiales de los vértices. Utilizando la función glEnable y la bandera GL_COLOR_MATERIAL, puedes definir que propiedades de los materiales, cambiará glColor. glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE) le dice a OpenGL que glColor cambia las propiedades del material ambient y diffuse. Discutiremos los materiales más tarde en estos tutoriales.

   Una cosa más que es importante cuando utilizamos luces... cada vértice debe tener asociada una "normal". La "Normal" se utiliza para encontrar la dirección del vértice de forma que la luz se puede calcular de la forma apropiada. Utilizarás la función GLUT para dibujar un cubo que nos aportará las "normales" de por sí, so this time we'll just walk by normals.

   Después de establecer todo esto, la luz brillará en el cubo :)

   Parte de lo que sigue está copiado de The OpenGL Light Bible

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

Fuentes Bitmap

   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.

Texturas

   Es tiempo de usar texturas :)

   Este tutorial mostrará como dibujar polígonos con textura y como to blend textures using multipass technic. Since OpenGL has no builtin mechanism for loading textures, we'll use external library: Vampyre Imaging Library. We'll use just OpenGL helper functions, but you may find this lib handy for some other things to.

   Let's get started... we'll create display list for drawing textured rectangle:

 procedure CreateList;
 begin
   glNewList(LIST_OBJECT, GL_COMPILE);
     glBegin(GL_QUADS);
       glTexCoord2f(1, 0);
       glVertex3f( 2, 2, 0);
       glTexCoord2f(0, 0);
       glVertex3f(-2, 2, 0);
       glTexCoord2f(0, 1);
       glVertex3f(-2,-2, 0);
       glTexCoord2f(1, 1);
       glVertex3f( 2,-2, 0);
     glEnd;
   glEndList;
 end;

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

   Texture loading is extremely easy with Vampyre Imaging Library:

 var
   Tex1, Tex2: GLuint;
 
 procedure InitializeGL;
 begin
   glClearColor(0, 0, 0, 0);
   Tex1 := LoadGLTextureFromFile('ashwood.bmp');
   Tex2 := LoadGLTextureFromFile('Flare.bmp');
   glEnable(GL_TEXTURE_2D);
 end;

   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.

   To draw textured polygon you have to bind texture and setup texture coordinations (texture coordinations are set in display list in this tutorial):

   ...
   glLoadIdentity;
   glTranslatef(-5, 0, -15);
   glBindTexture(GL_TEXTURE_2D, Tex1);
   glCallList(LIST_OBJECT);
   ...

   glBindTexture function is used to select texture. When you draw polygins they will have selected texture on them. It's that easy :)

   So, 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:

   ...
   glLoadIdentity;
   glTranslatef(5, 0, -15);
   glBindTexture(GL_TEXTURE_2D, Tex1);
   glCallList(LIST_OBJECT);
 
   glEnable(GL_BLEND);
   glBlendFunc(GL_ZERO, GL_SRC_COLOR);
   glLoadIdentity;
   glTranslatef(5, 0, -15);
   glBindTexture(GL_TEXTURE_2D, Tex2);
   glCallList(LIST_OBJECT);
   glDisable(GL_BLEND);
 ...

   As 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:

  • DrawingColor * SRCBLEND + BackgroundColor * DESTBLEND

SRCBLEND 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:

   DrawingColor * 0 + BackgroundColor * DrawingColor

TexturesPic1.jpg

   This 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

   Next time, we'll use extensions to show how to use singlepass multitexturing.

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

Multitexturing (extensions)

   When 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. Todo lo que hay que hacer es configurar las etapas de textura y renderizar el objeto.

   Veamos como queda el código:

 procedure InitializeGL;
 begin
   Load_GL_ARB_multitexture;
   glClearColor(0, 0, 0, 0);
   Tex1 := LoadGLTextureFromFile('Lazarus.bmp');
   Tex2 := LoadGLTextureFromFile('Mask.bmp');
   glActiveTextureARB(GL_TEXTURE0_ARB);
   glEnable(GL_TEXTURE_2D);
   glBindTexture(GL_TEXTURE_2D, Tex1);
   glActiveTextureARB(GL_TEXTURE1_ARB);
   glEnable(GL_TEXTURE_2D);
   glBindTexture(GL_TEXTURE_2D, Tex2);
 end;

   Primero necesitamos cargar la extensión de OpenGL que permitirá utilizar las funciones de multitextura. Load_GL_ARB_multitexture tratará de cargar estas extensiones retornando TRUE en caso de éxito.

   Para seleccionar la etapa de textura que queremos establecer utilizamos la función glActiveTextureARB. Toma únicamente un parámetro para definir la etapa que se necesita. Después de esto todas las funciones de textura (enabling, disabling, binding, creating...) afectarán esta etapa.

   Después de establecer todo en la función de inicialización, todo lo que resta por hacer es dibujar el objeto:

 procedure DrawGLScene; cdecl;
 begin
   glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
 
   glLoadIdentity;
   glTranslatef(0, 0, -5);
 
   glBegin(GL_QUADS);
     glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 1, 0);
     glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 1, 0);
     glVertex3f(2.516, 2, 0);
     glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0, 0);
     glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 0, 0);
     glVertex3f(-2.516, 2, 0);
     glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0, 1);
     glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 0, 1);
     glVertex3f(-2.516,-2, 0);
     glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 1, 1);
     glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 1, 1);
     glVertex3f(2.516,-2, 0);
   glEnd;
 
   glutSwapBuffers;
 end;
MultitexturePic1.jpg

   Como puedes observar, la diferencia está únicamente en la definiciónde las coordenadas de la textura. We now use glMultiTexCoord2fARB function that takes texture stage and texture coordinations. Every thing else is unchanged.

   Hoy días prácticamente todas las tarjetas gráficas soportan al menos 2 etapas de textura. 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.

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

Render to texture

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

   First, we must create empty texture which we'll use to capture scene:

 procedure SetupRenderTexture;
 var
   Data: Pointer;
 begin
   GetMem(Data, 256*256*3);
   glGenTextures(1, @RenderTexture);
   glBindTexture(GL_TEXTURE_2D, RenderTexture);
   glTexImage2D(GL_TEXTURE_2D, 0, 3, 256, 256, 0, GL_RGB, GL_UNSIGNED_BYTE, Data);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
   FreeMem(Data);
 end;

   Buffer for 256*256 RGB image is created and it is used to setup 2D texture.

   Main part is in drawing function:

 procedure DrawGLScene; cdecl;
 var
   TotalTime: Single;
 begin
   glClearColor(0, 0, 0, 0);
   glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
   glEnable(GL_LIGHTING);
   glDisable(GL_TEXTURE_2D);
   glViewport(0, 0, 256, 256);
 
   TotalTime := GetTotalTime;
 
   glLoadIdentity;
   glTranslatef(0, 0, -5);
   glRotatef(50 * TotalTime, 1, 0, 0);
   glRotatef(100 * TotalTime, 0, 1, 0);
   glRotatef(50 * TotalTime, 0, 0, 1);
 
   glColor3f(1, 1, 1);
   glutSolidCube(2);
 
   glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, 256, 256, 0);
 
   glClearColor(0.18, 0.20, 0.66, 0);
   glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
   glDisable(GL_LIGHTING);
   glEnable(GL_TEXTURE_2D);
   glViewport(0, 0, AppWidth, AppHeight);
 
   glLoadIdentity;
   glTranslatef(0, 0, -7);
   glRotatef(20 * TotalTime, 1, 0, 0);
   glRotatef(50 * TotalTime, 0, 1, 0);
 
   glBegin(GL_QUADS);
     glTexCoord2f(1, 0);
     glVertex3f(2, 2, 0);
     glTexCoord2f(0, 0);
     glVertex3f(-2, 2, 0);
     glTexCoord2f(0, 1);
     glVertex3f(-2,-2, 0);
     glTexCoord2f(1, 1);
     glVertex3f(2,-2, 0);
   glEnd;
 
   glutSwapBuffers;
 end;
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.

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

   P.S. Captured texture can be saved using SaveGLTextureToFile function from Vampyre Imaging Library.

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

Vertex array

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

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

   First, let's define some types and constants:

 type
   TVertex3f = record
     X, Y, Z: Single;
   end;
 
   TColor3f = record
    R, G, B: Single;
   end;
 
   VertexBuffer: array [0..5] of TVertex3f = (
     (X : 1; Y : 1; Z : 0),
     (X : -1; Y : 1; Z : 0),
     (X : -1; Y : -1; Z : 0),
     (X : 1; Y : 1; Z : 0),
     (X : -1; Y : -1; Z : 0),
     (X : 1; Y : -1; Z : 0)
   );
   ColorBuffer: array [0..5] of TColor3f = (
     (R : 1; G : 0; B : 1),
     (R : 0; G : 0; B : 1),
     (R : 0; G : 1; B : 0),
     (R : 1; G : 0; B : 1),
     (R : 0; G : 1; B : 0),
     (R : 1; G : 1; B : 0)
   );

   We have two buffers. One for vertex coordinates and one for vertex colors. This 6 vertices defines 2 triangles that forms rectangle.

   Drawing primitives using buffers is easy:

   glEnableClientState(GL_VERTEX_ARRAY);
   glEnableClientState(GL_COLOR_ARRAY);
   glVertexPointer(3, GL_FLOAT, 0, @VertexBuffer[0]);
   glColorPointer(3, GL_FLOAT, 0, @ColorBuffer[0]);
 
   glDrawArrays(GL_TRIANGLES, 0, Length(VertexBuffer));
 
   glDisableClientState(GL_VERTEX_ARRAY);
   glDisableClientState(GL_COLOR_ARRAY);

   First 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:

 type
   TBufferData = record
     DataBefore: TDataBefore;
     Vertex: TVertex;
     DataAfter: TDataAfter;
   end;
Bytes between elements = SizeOf(TDataBefore) + SizeOf(TDataAfter)

   Last parameter if pointer to the begginig of buffer.

   When 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).

   When buffers are not needed you can disable them.

   To demonstrate indexed mode, I made some simple mesh class that can load vertex, color and index data from external files:

 type
   TMesh = class
   private
     FVertices: array of TVertex3f;
     FColors: array of TColor3f;
     FIndices: array of Integer;
     procedure FreeBuffers;
   public
     constructor Create;
     destructor Destroy; override;
     procedure LoadMesh(FileName: String);
     procedure DrawMesh;
   end;

   FVertices will contain data about vertices, FColors data about color and FIndices data about indices when external file is loaded.

   First we'll write some code that deals with creation and destruction of class:

 procedure TMesh.FreeBuffers;
 begin
   FVertices := nil;
   FColors := nil;
   FIndices := nil;
 end;
 
 constructor TMesh.Create;
 begin
   FreeBuffers;
 end;
 
 destructor TMesh.Destroy;
 begin
   FreeBuffers;
   inherited Destroy;
 end;

   File 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:

3 3
-1 -1 0 1 1 1
1 -1 0 1 1 1
0 1 0 1 1 1
0
1
2

   This 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).

   Code for loading this data will loke like this:

 procedure TMesh.LoadMesh(FileName: String);
 var
   MeshFile: TextFile;
   VertexCount, IndexCount: Integer;
   iV, iI: Integer;
 begin
   FreeBuffers;
 
   AssignFile(MeshFile, FileName);
   Reset(MeshFile);
 
   ReadLn(MeshFile, VertexCount, IndexCount);
 
   SetLength(FVertices, VertexCount);
   SetLength(FColors, VertexCount);
   SetLength(FIndices, IndexCount);
 
   for iV := 0 to VertexCount - 1 do
     ReadLn(MeshFile,
       FVertices[iV].X, FVertices[iV].Y, FVertices[iV].Z,
       FColors[iV].R, FColors[iV].G, FColors[iV].B);
 
   for iI := 0 to IndexCount - 1 do
     ReadLn(MeshFile, FIndices[iI]);
 
   CloseFile(MeshFile);
 end;

   After loading data, we have everything for drawing:

 procedure TMesh.DrawMesh;
 begin
   glEnableClientState(GL_VERTEX_ARRAY);
   glEnableClientState(GL_COLOR_ARRAY);
   glVertexPointer(3, GL_FLOAT, 0, @FVertices[0]);
   glColorPointer(3, GL_FLOAT, 0, @FColors[0]);
 
   glDrawElements(GL_TRIANGLES, Length(FIndices), GL_UNSIGNED_INT, @FIndices[0]);
 
   glDisableClientState(GL_VERTEX_ARRAY);
   glDisableClientState(GL_COLOR_ARRAY);
 end;

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

   

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:

4 6
1 1 0 1 0 1
-1 1 0 0 0 1
-1 -1 0 0 1 0
1 -1 0 1 1 0
0
1
2
0
2
3

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

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


Creating bindings for C libraries