Using OpenGL for Motion Graphics

Introduction

The rotating triangle, cubes and tetrahedra examples are PasSFML versions of our Smart Pascal demonstrations Triangle3D and Cubes and Tetrahedra. The last example shows you how to use shaders in OpenGL within PasSFML. It uses the dglOpenGL unit, which you can download from this English page on the Delphi OpenGL Community wiki. We acknowledge the inclusion of some of Max Foster's matrix routines from the Display unit of MrSnugglekins.

Code of OpenGL_RotatingTriangle

program OpenGL_RotatingTriangle;
{$Apptype Gui}
uses
  SysUtils, GL, GLU, SfmlGraphics, SfmlSystem, SfmlWindow;
var
  OutputWindow: TSfmlRenderWindow;
  Event : TSfmlEvent;
begin
  // Create the main window
  OutputWindow := TSfmlRenderWindow.Create(SfmlVideoMode(400, 400),
    'SFML window with  OpenGL', [sfTitleBar, sfClose]);
  // Make it the active window for OpenGL calls
  OutputWindow.SetActive(True);
  // Set the color and depth clear values
  glClearDepth(1);
  glClearColor(0, 0, 0, 1);
  // Setup a perspective projection
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity;
  gluPerspective(40, 1, 0.1, 1000);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity;
  glTranslatef(0, 0, -200);
  while OutputWindow.isOpen do
    begin
      while SfmlRenderWindowPollEvent(OutputWindow.Handle, Event) do
        begin
          if Event.EventType = sfEvtClosed then  // Window closed
            SfmlRenderWindowClose(OutputWindow.Handle);
          // Escape key pressed
          if (Event.EventType = sfEvtKeyPressed) and (Event.Key.Code = sfKeyEscape) then
            SfmlRenderWindowClose(OutputWindow.Handle);
        end;
      OutputWindow.Clear(SfmlColorFromRGBA(0, 0, 64, 255));
      // Draw the triangle
      glBegin(GL_TRIANGLES);
        glColor4f(1, 0, 0, 0.5);
        glVertex3f(-50, -50, 0);

        glColor4f(0, 1, 0, 0.5);
        glVertex3f(50, -50, 0);

        glColor4f(0, 0, 1, 0.5);
        glVertex3f(0, 50, 0);
      glEnd;
      OutputWindow.Display;
      sfmlsleep(sfmlMilliSeconds(100));
      glRotatef(5, 0, 1, 0);
    end;
end.
    

Code of OpenGL_RotatingCubes

program OpenGL_RotatingCubes;
{$Apptype GUI}
uses
  SysUtils, GL, GLU, SfmlGraphics, SfmlSystem, SfmlWindow;

const
  //http://chimera.labs.oreilly.com/books/1234000000802/ch02.html
  ColourData: array[0..107] of glFloat = (
    //front face: 0, 1, 2, 0, 2, 3
    1.0, 0.0, 0.0, //r 0
    0.0, 1.0, 0.0, //g 1
    0.0, 0.0, 1.0, //b 2
    1.0, 0.0, 0.0, //r 0
    0.0, 0.0, 1.0, //b 2
    1.0, 1.0, 1.0, //w 3
    //back face: 4, 5, 6, 4, 6, 7
    0.0, 0.0, 0.0, //black 4
    1.0, 1.0, 0.0, //yellow 5
    0.0, 1.0, 1.0, //purple 6
    0.0, 0.0, 0.0, //black 4
    0.0, 1.0, 1.0, //purple 6
    1.0, 0.0, 1.0, //magenta 7
    //top face: 5, 3, 2, 5, 2, 6
    1.0, 1.0, 0.0, //yellow 5
    1.0, 1.0, 1.0, //w 3
    0.0, 0.0, 1.0, //b 2
    1.0, 1.0, 0.0, //yellow 5
    0.0, 0.0, 1.0, //b 2
    0.0, 1.0, 1.0, // purple 6
    //bottom face: 4, 7, 1, 4, 1, 0
    0.0, 0.0, 0.0, //black 4
    1.0, 0.0, 1.0, //magenta 7
    0.0, 1.0, 0.0, //g 1
    0.0, 0.0, 0.0, //black 4
    0.0, 1.0, 0.0, //g 1
    1.0, 0.0, 0.0, //r 0
    //right face: 7, 6, 2, 7, 2, 1
    1.0, 0.0, 1.0, // magenta 7
    0.0, 1.0, 1.0, // purple 6
    0.0, 0.0, 1.0, //b 2
    1.0, 0.0, 1.0, //magenta 7
    0.0, 0.0, 1.0, //b 2
    0.0, 1.0, 0.0, //g 1
    //left face: 4, 0, 3, 4, 3, 5
    0.0, 0.0, 0.0, //black 4
    1.0, 0.0, 0.0, //r 0
    1.0, 1.0, 1.0, //w 3
    0.0, 0.0, 0.0, //black 4
    1.0, 1.0, 1.0, //w 3
    1.0, 1.0, 0.0  //yellow 5
  );
  Cube: array [0..107] of GLfloat = (
    //front face: 0, 1, 2, 0, 2, 3
    -100.0, -100.0,  100.0, //0
     100.0, -100.0,  100.0, //1
     100.0,  100.0,  100.0, //2
    -100.0, -100.0,  100.0, //0
     100.0, 100.0,  100.0,  //2
    -100.0,  100.0,  100.0, //3
    //back face: 4, 5, 6,  4, 6, 7
    -100.0, -100.0, -100.0, //4
    -100.0,  100.0, -100.0, //5
     100.0,  100.0, -100.0, //6
    -100.0, -100.0, -100.0, //4
     100.0,  100.0, -100.0, //6
     100.0, -100.0, -100.0, //7
     //top face: 8, 9, 10,  8, 10, 11
    -100.0,  100.0, -100.0, //8 same as 5
    -100.0,  100.0,  100.0, //9 same as 3
     100.0,  100.0,  100.0, //10 same as 2
    -100.0,  100.0, -100.0, //8 same as 5
     100.0,  100.0,  100.0, //10 same as 2
     100.0,  100.0, -100.0, //11 same as 6
    //bottom face: 12, 13, 14, 12, 14, 15
    -100.0, -100.0, -100.0, //12 same as 4
     100.0, -100.0, -100.0, //13 same as 7
     100.0, -100.0,  100.0, //14 same as 1
    -100.0, -100.0, -100.0, //12 same as 4
     100.0, -100.0,  100.0, //14 same as 1
    -100.0, -100.0,  100.0, //15 same as 0
    //right face: 16, 17, 18, 16, 18, 19
     100.0, -100.0, -100.0, //16 same as 7
     100.0,  100.0, -100.0, //17 same as 6
     100.0,  100.0,  100.0, //18 same as 2
     100.0, -100.0, -100.0, //16 same as 7
     100.0,  100.0,  100.0, //18 same as 2
     100.0, -100.0,  100.0, //19 same as 1
    //left face: 20, 21, 22, 20, 22, 23
    -100.0, -100.0, -100.0, //20 same as 4
    -100.0, -100.0,  100.0, //21 same as 0
    -100.0,  100.0,  100.0, //22 same as 3
    -100.0, -100.0, -100.0, //20 same as 4
    -100.0,  100.0,  100.0, //22 same as 3
    -100.0,  100.0, -100.0  //23 same as 5
  );

var
  ContextSettings: TSfmlContextSettings;
  OutputWindow: TSfmlRenderWindow;
  Event: TSfmlEvent;
  Clock: TSfmlClock;
  s: glFloat;

procedure RenderCube(x, y, z: GLfloat);
begin
  glLoadIdentity;
  glTranslatef(x, y, z);
  s := Clock.ElapsedTime.AsSeconds;
  glRotatef(60 * s, 1, 0, 0);
  glRotatef(30 * s, 0, 1, 0);
  glRotatef(20 * s, 0, 0, 1);
  glScalef(0.35, 0.35, 0.35);
  // Draw the cube
  glDrawArrays(GL_TRIANGLES, 0, 36);
end;

begin
  // Request a 32-bits depth buffer when creating the window
  ContextSettings.DepthBits := 32;
  // Create the main window
  OutputWindow := TSfmlRenderWindow.Create(SfmlVideoMode(500, 500),
    'SFML window with 3D OpenGL', [sfTitleBar, sfClose], @ContextSettings);
  // Make it the active window for OpenGL calls
  OutputWindow.SetActive(True);
  // Set the color and depth clear values
  glClearDepth(1);
  glClearColor(0.0, 0.0, 0.25, 1.0); // Set clear color to blue
  // Enable Z-buffer read and write
  glEnable(GL_DEPTH_TEST);
  glDepthMask(GL_TRUE);
  // Disable lighting and texturing
  glDisable(GL_LIGHTING);
  glDisable(GL_TEXTURE_2D);
  // Configure viewport to size of window
  glViewport(0, 0, OutputWindow.Size.X, OutputWindow.Size.Y);
  // Set up perspective projection with 45 degree field of view
  glMatrixMode(GL_PROJECTION);
  gluPerspective(45, 1, 0.1, 1000);
  // Enable the vertex and colour arrays
  glEnableClientState(GL_VERTEX_ARRAY);
  glEnableClientState(GL_COLOR_ARRAY);
  // Link array data
  glVertexPointer(3, GL_FLOAT, 3 * sizeof(GLfloat), @Cube);
  glColorPointer(3, GL_FLOAT, 3 * sizeof(GLfloat), @ColourData);
  // Disable normal vertex component
  glDisableClientState(GL_NORMAL_ARRAY);
  // Clear color and depth buffers
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);

  Clock := TSfmlClock.Create;
  while OutputWindow.isOpen do
    begin
      while SfmlRenderWindowPollEvent(OutputWindow.Handle, Event) do
        begin
          if Event.EventType = sfEvtClosed then  // Window closed
            SfmlRenderWindowClose(OutputWindow.Handle);
          // Escape key pressed
          if (Event.EventType = sfEvtKeyPressed) and (Event.Key.Code = sfKeyEscape) then
            SfmlRenderWindowClose(OutputWindow.Handle);
        end;
      glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
      OutputWindow.Clear(SfmlColorFromRGBA(0, 0, 64, 255));
      RenderCube(-60, 0, -330);
      RenderCube(60, 0, -330);
      OutputWindow.Display;
    end;
end.    


Code of PasSFML_OpenGL_Shaders

program PasSFML_OpenGL_Shaders;

uses
  SysUtils, Math, GL, GLU, dglOpenGL, SfmlGraphics, SfmlSystem, SfmlWindow;
type
  matrix4d = array[0 .. 15] of glFloat;
var
  FrameCount: glFloat;
  Event: tsfmlEvent;
  OutputWindow: TSfmlRenderWindow;
  VertexArray: array[0..17] of GLFloat = ( // Vertices of tetrahedron are alternate vertices of cube
    -1.0, -1.0,  1.0,
     1.0, -1.0, -1.0,
     1.0,  1.0,  1.0,
    -1.0,  1.0, -1.0,
    -1.0, -1.0,  1.0,  // repeating the first two vertices
     1.0, -1.0, -1.0
  );

  ColourArray: array[0..17] of GLFloat = (
    1.0, 0.0, 0.0, //r
    0.0, 1.0, 0.0, //g
    0.0, 0.0, 1.0, //b
    1.0, 1.0, 1.0, //w
    1.0, 0.0, 0.0, //repeating the first two colours to match the repeated vertices
    0.0, 1.0, 0.0
  );

  VertexShader, FragmentShader, ShaderProgram, ShaderProgram2: glHandle;
  VertexCode: PGLchar = 'attribute vec3 aVertexPosition; ' +
                        'attribute vec3 aVertexColor; ' +

                        'uniform mat4 uModelViewMatrix; ' +
                        'uniform mat4 uProjectionMatrix; ' +

                        'varying vec4 vColor; ' +

                        'void main(void) { ' +
                        '  gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aVertexPosition, 1.0); ' +
                        '  vColor = vec4(aVertexColor, 1.0); ' +
                        '} ';

  FragmentCode: PGLchar = 'precision mediump float; ' +
                          'varying vec4 vColor; ' +

                          'void main(void) { ' +
                          '  gl_FragColor = vColor; ' +
                          '} ';

  VertexPosAttrib, ColourAttrib, PM, MVM, Success: integer;
  ProjectionMatrix, ModelViewMatrix, MVMTemp: Matrix4d;


function multiplyMatrix(operandMatrix, multiplierMatrix: matrix4d): matrix4d;
begin
  result[0] := operandMatrix[0] * multiplierMatrix[0] + operandMatrix[4] * multiplierMatrix[1] +
               operandMatrix[8] * multiplierMatrix[2] +  operandMatrix[12] * multiplierMatrix[3];

  result[1] := operandMatrix[1] * multiplierMatrix[0] + operandMatrix[5] * multiplierMatrix[1] +
               operandMatrix[9] * multiplierMatrix[2] + operandMatrix[13] * multiplierMatrix[3];

  result[2] := operandMatrix[2] * multiplierMatrix[0] + operandMatrix[6] * multiplierMatrix[1] +
               operandMatrix[10] * multiplierMatrix[2] + operandMatrix[14] * multiplierMatrix[3];

  result[3] := operandMatrix[3] * multiplierMatrix[0] + operandMatrix[7] * multiplierMatrix[1] +
               operandMatrix[11] * multiplierMatrix[2] + operandMatrix[15] * multiplierMatrix[3];

  result[4] := operandMatrix[0] * multiplierMatrix[4] + operandMatrix[4] * multiplierMatrix[5] +
               operandMatrix[8] * multiplierMatrix[6] + operandMatrix[12] * multiplierMatrix[7];

  result[5] := operandMatrix[1] * multiplierMatrix[4] + operandMatrix[5] * multiplierMatrix[5] +
               operandMatrix[9] * multiplierMatrix[6] + operandMatrix[13] * multiplierMatrix[7];

  result[6] := operandMatrix[2] * multiplierMatrix[4] + operandMatrix[6] * multiplierMatrix[5] +
               operandMatrix[10] * multiplierMatrix[6] + operandMatrix[14] * multiplierMatrix[7];

  result[7] := operandMatrix[3] * multiplierMatrix[4] + operandMatrix[7] * multiplierMatrix[5] +
               operandMatrix[11] * multiplierMatrix[6] + operandMatrix[15] * multiplierMatrix[7];

  result[8] := operandMatrix[0] * multiplierMatrix[8] + operandMatrix[4] * multiplierMatrix[9] +
               operandMatrix[8] * multiplierMatrix[10] + operandMatrix[12] * multiplierMatrix[11];

  result[9] := operandMatrix[1] * multiplierMatrix[8] + operandMatrix[5] * multiplierMatrix[9] +
               operandMatrix[9] * multiplierMatrix[10] + operandMatrix[13] * multiplierMatrix[11];

  result[10] := operandMatrix[2] * multiplierMatrix[8] + operandMatrix[6] * multiplierMatrix[9] +
                operandMatrix[10] * multiplierMatrix[10] + operandMatrix[14] * multiplierMatrix[11];

  result[11] := operandMatrix[3] * multiplierMatrix[8] + operandMatrix[7] * multiplierMatrix[9] +
                operandMatrix[11] * multiplierMatrix[10] + operandMatrix[15] * multiplierMatrix[11];

  result[12] := operandMatrix[0] * multiplierMatrix[12] + operandMatrix[4] * multiplierMatrix[13] +
                operandMatrix[8] * multiplierMatrix[14] + operandMatrix[12] * multiplierMatrix[15];

  result[13] := operandMatrix[1] * multiplierMatrix[12] + operandMatrix[5] * multiplierMatrix[13] +
                operandMatrix[9] * multiplierMatrix[14] + operandMatrix[13] * multiplierMatrix[15];

  result[14] := operandMatrix[2] * multiplierMatrix[12] + operandMatrix[6] * multiplierMatrix[13] +
                operandMatrix[10] * multiplierMatrix[14] + operandMatrix[14] * multiplierMatrix[15];

  result[15] := operandMatrix[3] * multiplierMatrix[12] + operandMatrix[7] * multiplierMatrix[13] +
                operandMatrix[11] * multiplierMatrix[14] + operandMatrix[15] * multiplierMatrix[15];

end;

procedure initIdentityMatrix(var dstMatrix: matrix4d);
var
  i: byte;
begin
  for i := 0 to 15 do
    dstMatrix[i] := 0.0;
  dstMatrix[0] := 1.0;
  dstMatrix[5] := 1.0;
  dstMatrix[10] := 1.0;
  dstMatrix[15] := 1.0;
end;

procedure translateMatrix(var dstMatrix: matrix4d; x, y, z: glFloat);
var
  transformationMatrix: matrix4d;
begin
  initIdentityMatrix(transformationMatrix);
  transformationMatrix[12] := x;
  transformationMatrix[13] := y;
  transformationMatrix[14] := z;
  dstMatrix := multiplyMatrix(dstMatrix, transformationMatrix);
end;

procedure rotateMatrix(var dstMatrix: matrix4d; angle, x, y, z: glFloat);
var
  len, c, s, x2, y2, z2, t: glFloat;
  transformationMatrix: matrix4d;
begin
  angle := degToRad(angle);
  len := sqrt((x * x) + (y * y) + (z * z));
  x /= len;
  y /= len;
  z /= len;

  c := cos(angle);
  s := sin(angle);
  x2 := x * x;
  y2 := y * y;
  z2 := z * z;
  t := 1.0 - c;

  transformationMatrix[0] := (x2 * t) + c;
  transformationMatrix[1] := (y * x * t) + z * s;
  transformationMatrix[2] := (x * z * t) - (y * s);
  transformationMatrix[3] := 0.0;

  transformationMatrix[4] := (x * y * t) - (z * s);
  transformationMatrix[5] := (y2 * t) + c;
  transformationMatrix[6] := (y * z * t) + (x * s);
  transformationMatrix[7] := 0.0;

  transformationMatrix[8] := (x * z * t) + (y * s);
  transformationMatrix[9] := (y * z * t) - (x * s);
  transformationMatrix[10] := (z2 * t) + c;
  transformationMatrix[11] := 0.0;

  transformationMatrix[12] := 0.0;
  transformationMatrix[13] := 0.0;
  transformationMatrix[14] := 0.0;
  transformationMatrix[15] := 1.0;

  dstMatrix := multiplyMatrix(dstMatrix, transformationMatrix);
end;

function getPerspectiveMatrix(left, right, bottom, top, front, back: glFloat): matrix4d;
begin
  result[0] := (2.0 * front) / (right - left);
  result[1] := 0.0;
  result[2] := 0.0;
  result[3] := 0.0;

  result[4] := 0.0;
  result[5] := (2.0 * front) / (top - bottom);
  result[6] := 0.0;
  result[7] := 0.0;

  result[8] := (right + left) / (right - left);
  result[9] := (top + bottom) / (top - bottom);
  result[10] := (-(back + front)) / (back - front);
  result[11] := -1.0;

  result[12] := 0.0;
  result[13] := 0.0;
  result[14] := (-2.0 * back * front) / (back - front);
  result[15] := 0.0;
end;

function getPerspectiveMatrix(angle, aspectRatio, front, back: glFloat): matrix4d;
var
  tangent, height, width: real;
begin
  tangent := tan(degToRad(angle / 2));
  height := front * tangent;
  width := height * aspectRatio;
  result := getPerspectiveMatrix(-width, width, -height, height, front, back);
end;

procedure initShader;
begin
  VertexShader := glCreateShader(GL_VERTEX_SHADER);
  FragmentShader := glCreateShader(GL_FRAGMENT_SHADER);

  glShaderSource(VertexShader, 1, @VertexCode, nil);
  glCompileShader(VertexShader);
  // If vertex shader did not compile, show a message and exit.
  glGetShaderiv(VertexShader, GL_COMPILE_STATUS, @success);
  if success = GL_FALSE then
    begin
      writeln('Vertex shader failed to compile');
      exit;
    end;
  glShaderSource(FragmentShader, 1, @FragmentCode, nil);
  glCompileShader(FragmentShader);
  glGetShaderiv(FragmentShader, GL_COMPILE_STATUS, @success);
  // If fragment shader did not compile, show a message and exit.
  if success = GL_FALSE then
    begin
      writeln('Fragment shader failed to compile');
      exit;
    end;
  ShaderProgram := glCreateProgram();
  glAttachShader(ShaderProgram, VertexShader);
  glAttachShader(ShaderProgram, FragmentShader);
  glLinkProgram(ShaderProgram);
  // Error check
  glGetProgramiv(ShaderProgram, GL_LINK_STATUS, @success);
  if success = GL_FALSE then
    begin
      writeln('Error with linking shader program');
      exit;
    end;
  glUseProgram(ShaderProgram);
  // Detach and delete shaders
  glDetachShader(ShaderProgram, VertexShader);
  glDeleteShader(VertexShader);
  glDetachShader(ShaderProgram, FragmentShader);
  glDeleteShader(FragmentShader);
  // Obtain locations of attributes and uniforms
  VertexPosAttrib := glGetAttribLocation(ShaderProgram, pchar('aVertexPosition'));
  ColourAttrib := glGetAttribLocation(ShaderProgram, pchar('aVertexColor'));
  PM := glGetUniformLocation(ShaderProgram, 'uProjectionMatrix');
  MVM := glGetUniformLocation(ShaderProgram, 'uModelViewMatrix');
  // Enable array processing
  glEnableVertexAttribArray(VertexPosAttrib);
  glEnableVertexAttribArray(ColourAttrib);
  // Populate model view matrix
  ProjectionMatrix := getPerspectiveMatrix(45.0, 1.0, 0.1, 1000.0);
  // Pass attributes and one uniform to shader program
  glVertexAttribPointer(VertexPosAttrib, 3, GL_Float, False, 0, @VertexArray);
  glVertexAttribPointer(ColourAttrib, 3, GL_Float, False, 0, @ColourArray);
  glUniformMatrix4fv(PM, 1, false, @ProjectionMatrix);
end;

procedure RenderTetrahedron(angle, xPos: glFloat);
var
  vertex: integer;
begin
  glEnableClientState(GL_VERTEX_ARRAY);
  initIdentityMatrix(ModelViewMatrix);
  translateMatrix(ModelViewMatrix, xPos, 0.0, -16.0 + FrameCount/25);
  rotateMatrix(ModelViewMatrix, angle + FrameCount * 2, 0.0, 1.0, 0.0);
  glUniformMatrix4fv(MVM, 1, false, @ModelViewMatrix);
  for vertex := 0 to 3 do
    glDrawArrays(gl_TRIANGLES, vertex, 3);
end;

begin
  // Create the main window
  OutputWindow := TSfmlRenderWindow.Create(SfmlVideoMode(500, 500),
    'SFML window with  OpenGL', [sfTitleBar, sfClose]);
  OutputWindow.SetActive(True); // Makes it the active window for OpenGL calls

  InitOpenGL;
  ReadImplementationProperties;
  ReadExtensions;
  glEnable(GL_DEPTH_TEST);
  glClearColor(0.0, 0.0, 0.25, 1.0); // Sets clear colour to blue, fully opaque
  glClearDepth(1.0);

  glViewport(0, 0, 500, 500);
  glClearDepth(1.0);
  glEnable(GL_DEPTH_TEST);           // Enable depth testing
  glDepthFunc(GL_LEQUAL);
  InitShader;

  while OutputWindow.isOpen do
    begin
      FrameCount += 1.0;
      if FrameCount > 300 then
        FrameCount := 0.0;
      while SfmlRenderWindowPollEvent(OutputWindow.Handle, Event) do
        begin
          if Event.EventType = sfEvtClosed then  // Window closed
            SfmlRenderWindowClose(OutputWindow.Handle);
          // Escape key pressed
          if (Event.EventType = sfEvtKeyPressed) and (Event.Key.Code = sfKeyEscape) then
            SfmlRenderWindowClose(OutputWindow.Handle);
        end;
      glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
      RenderTetrahedron(45, -1.5);
      RenderTetrahedron(135, 1.5);
      glDisableClientState(GL_VERTEX_ARRAY);

      OutputWindow.Display;
      sfmlSleep(sfmlMilliseconds(20));
    end;

end.
    

We use much of this code also in a version using a TOpenGLControl.

Programming - a skill for life!

How to use the Simple Fast Media Library in Lazarus