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.