This tutorial shows a simple OGLplus application that is using the GLUT (GL Utility Toolkit) library to initialize OpenGL, create a window, the default rendering context and the default framebuffer and the GLEW (GL Extension Wrangler) library which allows to use functions from the OpenGL 3 (or higher) API and the OpenGL extensions. The application creates a single window and renders into it a red triangle on a black background. It involves only the very basic things necessary to draw something. For a full working code see the standalone/001_triangle_glut_glew.cpp
file in the example directory.
First we include two headers from the C++ standard library; we are going to use C assertions and C++ input/output streams.
Include the main header of the GLEW library, which declares the OpenGL 3 function prototypes, typedefs, enumerations, etc.
Include the main header of the GLUT library. GLUT defines functions for GL initialization, window creation, user input and event handling, etc.
Include the OGLplus's main header file. Before this file is included the OpenGL 3 function prototypes and other symbols must already be declared. As said above, we use the GLEW library to achieve it in this tutorial, there are however also other ways of doing it.
The OGLplus -related code that does the actual rendering is encapsulated in the Example
class. It contains objects that store the shading program and its individual shaders, objects storing and managing the vertex data and a wrapper around the current OpenGL context.
First let's have a look at the data members of the Example
class:
The Context
class encapsulates the current context functionality. It mainly wraps functions that are not related to other OpenGL objects like shaders, programs, buffers, textures, queries and so on.
There is a vertex shader object that usually handles vertex attribute transformations and may pass or emit other per-vertex data to the following stages of the OpenGL's pipeline. The shader will be invoked once per every vertex in the rendered primitives.
Since this is a very simple example that does not involve hardware tesselation nor any on-the-fly geometry modification or construction the next stage in the shading program is the fragment shader. In a simplistic view (which is sufficient for the purposes of this tutorial) a fragment shader is responsible for determining the color of a single pixel on the currently rendered polygon, line or point. A fragment shader is basically invoked once per every pixel on the rendered primitive.
The vertex and fragment shader are combined into a shading program. Such program is executed by the GPU and its individual stages handle the whole rendering process from the point where vertex attributes are consumed, transformed, possibly tessellated, assembled into the final primitives clipped, etc. until the point where they are drawn into a framebuffer. Many of these tasks are driven by the program's shaders.
Declare a vertex array object (VAO) for the triangle that will be rendered. A VAO encapsulates the state related to the definition of data that will be used by the vertex processor to draw a sequence of primitives that usually form a more complex geometric shape.
The actual vertex data is stored in (vertex) buffer objects (VBOs). This example uses only the positions of the vertices, thus there is only a single buffer. In cases where a single vertex has multiple attributes (position, colors, texture coordinates, normal, tangential, and binormal vectors, etc.) there would be multiple buffers and these buffers would be collected in a VAO. The buffer objects represent data stored in the server's memory.
The public interface of the Example
consists of a constructor and one member function.
The constructor does not take any arguments and it is responsible for initializing the private members described above.
The constructor uses several functions and types from the oglplus
namespace. One might do this on the global scope in simple applications but in complex ones it is usually a better idea to do this only in limited scopes or use fully qualified names to limit the possibility of name clashes with symbols from other libraries.
Set the source code for out vertex shader.
Specify which version of the OpenGL Shading language (GLSL) we are using;
Specify the Position
input variable of the vertex shader. Since the vertex shader is the first stage of the rendering pipeline, the values of the input variables of vertex shaders are consumed from the vertex attribute values which can be stored in vertex buffer objects (VBOs).
The main
function of the vertex shader has the following signature:
This example does not do any explicit transformations of the rendered primitives and thus the vertex shader only passes the value of the input variable Position
to the pre-defined output variable gl_Position
,
and this concludes the vertex shader source. Of course the source text does not have to be specified inline in the C++ source code, it can be for example loaded from a separate file.
The next step is to compile the shader. If something goes wrong the Compile()
member function throws an exception with full diagnostic output from the shader compiler.
Now we set the fragment shader source,
where we specify the version of GLSL that we wish to use,
and we define an output variable of type vec4 named fragColor:
The main
function has again the same signature,
This concludes the shader source,
and we can compile it too, just like the vertex shader above:
If the functions above did not throw any exceptions, we can now attach the compiled shaders to the shading program:
Try to link and use the program. If the linking process fails it throws an exception with output from the shading program linker with information about the cause of the error.
Input the vertex data for the rendered triangle. We start by binding the VAO. This will cause that the following operations related to vertex data specification will affect the triangle
object.
Define an array of float values for vertex positions. There are 3 values per each vertex (the x, y and z coodrinate) and since we are rendering a triangle there are three vertices, lying on the z-plane with the following x and y coordinates [0, 0], [1, 0] and [0, 1]:
Bind the VBO that will hold the vertex data in server memory to the ARRAY_BUFFER
target (or binding point). Since we've currently bound the triangle
VAO, this will attach the verts
buffer object to the triangle
vertex array object.
The data from the triangle_verts
array in client memory can be uploaded to the server memory managed by the verts
VBO by calling the static function Buffer::Data on the same binding point that the verts
buffer is currently bound to.
To tell OpenGL that the value of the Position
input variable of the vertex shader should be taken from the verts
VBO and to supply it with some additional information about the organization of the data in the VBO we use a temporary VertexAttribArray
object. Such objects reference input variables of shading programs, thus during construction of vert_attr
we say that we want to reference a variable named Position
in the shading program prog
. Since verts
is the currently bound VBO to the ARRAY_BUFFER
target, the operations on the vert_attr
vertex attribute array object will also operate on this buffer.
Specify that there are three values per-vertex (3 coordinates) and that the data is of GLfloat
type.
Enable this vertex attribute array,
The constructor ends with specifying the clear color and the clear depth value, i.e. the values that will be written to all pixels in the color and depth buffers when preparing them to render a new frame.
The Display
member function is called periodically and is responsible for redrawing the window contents.
We'll be again using some symbols from the oglplus
namespace:
Before rendering the triangle we clear the color and depth buffers. Note that clearing the depth buffer is technically not necessary for this example since we are drawing a single primitive and thus we do not require a z-buffer, its here just to bring us up to speed and show how to clear multiple buffers at once.
Use the DrawArrays
command to draw some Triangles
from the vertex data managed by the currently bound VAO (which is still triangle
since we did not bind any other VAO). More precisely we want to draw 3 vertices starting at index 0 from the data stored in the buffers attached to this VAO.
With this is we finish the Display
function and the Example
class.
A class that manages a single instance of the Example
class, defined above. It is necessary because the GLUT library is a little weird and does not allow to specify contextual data for its individual callbacks (functions handling various events like user input, window redraw, etc.) directly.
The SingleInstance
function manages and returns a reference to a pointer to the Example
type and when this pointer is initialized it points to the single instance of Example
that does the rendering.
Disable the copy-construction for this class, which would only mess things up.
Define a constructor that instantiates a single Example
and stores the address of the new instace in the pointer managed by SingleInstance
. Note that only one instance of SingleExample
can exist at a time.
The destructor deallocates the single instance of Example
and resets the static pointer, when SingleExample
is destroyed.
The static Display
function is used as a callback for GLUT; it requires that the single instance is initialized and it call its Display member function and then tells GLUT to swap the front and back buffers once the rendering is finished.
This finishes the declaration of the SingleExample
helper class
The main function of this example application starts by initializing the GLUT library:
Let it initialize the default framebuffer. We want double buffering, RGBA color buffer and a depth buffer.
Set the dimensions and the position and we let GLUT create the main window for this application.
Try to initialize the GLEW library. Note that a real-life application using GLEW needs to check if the desired functions are available. Otherwise it may crash with a segmentation fault or result in undefined behavior. Here the checks are omitted for the sake of simplicity.
We use SingleExample
to instantiate the single instance of the Example
class (this will cause the Example
's constructor to be called, initializing the shaders, program, VAO and VBO, etc. as described above).
Tell GLUT to use SingleExample
's Display
function as the display callback function and we start the main loop, which will start event processing and redrawing of the window, which in turn will result in calling of the Example
's Display
function. If everything goes OK we exit the program with code 0.
If an exception was thrown by oglplus
during the initialization or rendering, it will be caught here, some diagnostic info will be printed to error output and the application will exit with code 1. We end up here also if the initialization of the GLEW library failed for some reason.
This concludes the main
function and our example's souce code.