Rendering a triangle
The most prominent example for OpenGL is probably that of rendering a colored triangle. This tutorial shows how this is done using Pascal3D. Though nothing keeps you from using OpenGL directly Pascal3D provides a powerful API that greatly simplifies rendering. Many basic OpenGL tutorials use the so called immediate mode because it is very simple to use. This has however been deprecated in OpenGL 3.x in favor of buffer objects because of it's poor performance and for compatibility reasons. While being more efficient it is a harder to understand for a beginner. The P3DPlot library (in p3d.core) aims for simplicity while at the same time using buffer objects.
A short introduction to graphic functions of Pascal3D
Rendering a red triangle is described through the following command.
geom_polygon([ vec3( -1,-1, 0 ), vec3( 1, -1, 0 ), vec3( 0, 1, 0 )], settings([ attrib( P3DAttribColor, vec4( 1, 0, 0, 1 )) // Red ])
The name of the function determines what type of geometry we want to draw (geom_points, geom_lines, geom_polygon). The first parameter is an array of coordinates and determines where we want to draw. The lower left corner is at -1,-1,0 and the upper right is at 1, 1. The coordinates have three dimensions but for now we can ignore the last coordinate. We use the vec3 constructor for a three dimensional vector defined in p3d.math which syntax is similar to that of GLSL.
P3DPlot distinguishes between two types of commands, layers and settings. The former represents a drawing operation while the latter determines how it is performed.
With the second parameter is of type setting. We can pass our attributes and uniforms there. It is possible to pass more than one setting which is why we need to enclose them in a settings block. This will turn an array of settings into a list class that can be used for rendering. With attrib we say we want to pass an attribute. The location parameter is set to the color attribute and the color is passed as a four dimensional vector (the components correspond to the channels Red, Green, Blue, Alpha). So vec4( 1, 0, 0, 1 )
is red with an alpha value of 1. This results in the whole triangle becoming red.
It is also possible to define one color for each vertex.
attrib( P3DAttribColor, [ vec4( 1, 0, 0, 1 ), vec4( 0, 1, 0, 1 ), vec4( 0, 0, 1, 1 )])
Hint
If we pass nil the color attribute will remain undefined. This sometimes makes sense but when using geom_points/geom_lines/geom_polygon you should always define a color because otherwise the color of the triangle will depend on what the attribute was previously set to or even on the OpenGL implementation. E.g. the triangle will by default be black on Intel and white on NVidia hardware.
For efficiency the polygon is not rendered directly. A call to the geometry functions will create a list of function calls that can be executed later. We therefore store the this list in a variable so this list has to be generated only once. Similar to the settings it is possible to store more than one function call in the list. We might want to clear the color buffer before rendering our triangle with command_clear first. We therefore have to pass a list of the function calls to the layers function to make a plot list that can be rendered later. The complete function call will then be:
TriangleList:= layers([ command_clear([ cfColor, cfDepth ], vec4( 0.5, 0.5, 0.5, 1 )), // Clear the background and depth buffer with grey geom_polygon([ vec3( -1,-1, 0 ), vec3( 1, -1, 0 ), vec3( 0, 1, 0 )], // draws a triangle from the passed coordinates settings([ attrib( P3DAttribColor, [ vec4( 1, 0, 0, 1 ), vec4( 0, 1, 0, 1 ), vec4( 0, 0, 1, 1 )]) // sets the color attribute for each vertex ]) ) ]);
And for rendering we call the Execute method.
TriangleList.Execute;
Preparing the app for the rendering
We start with the project from the last tutorial. To store our render calls we add a field FTriangleList of type TP3DPlotListLayer. Since we it is not freed automatically we override the Finalize method. The render method is called from the application's main loop. This is where we execute our triangle list. To generate the list we make a SetupTriangle method that is called from initialize.
TMyApplication = class ( TP3DApplication ) private FTriangleList: TP3DPlotListLayer; public procedure Initialize; override; // Override the initialize procedure to // allow custom initializations procedure Finalize; override; procedure Render; override; procedure SetupTriangle; end;
Hint
Since P3DPlot relies on shader nodes we need to define where our shaders are located. This is in the pascal3d/shaders/ directory. For your own projects you may want to copy these files to your project directory.
procedure TMyApplication.Initialize; begin inherited Initialize; TP3DWindow.Create( Windows ); // Create the main window. Note that this is freed // automatically when the application terminates MainWindow.Title:= 'Rendering a triangle'; // Set the title P3DShaderNodeLib.LoadLibraryPath( '../../../shaders/' ); SetupTriangle; end; procedure TMyApplication.Finalize; begin FreeAndNil( FTriangleList ); // Free the triangle list inherited Finalize; end;
The Render method that is executed on each frame is very simple. We just execute our triangle list.
procedure TMyApplication.Render; begin inherited Render; FTriangleList.Execute; end;