Please send suggestions or corrections for this page to the author at sbuss@ucsd.edu.

This page describes some of the features of Modern OpenGL used in the SimpleAnimModern program. This is the second of a series of programs illustrating features of Modern OpenGL. The first program, SimpleDrawModern, already introduced many of the basic features of any OpenGL program, and these were described on the web page for SimpleDrawModern. On this page, we only discuss aspects of the software with are new to SimpleAnimModern.

The code fragments listed below are taken from the programs SimpleAnimModern.cpp and ShaderMgrSAM.cpp. It is recommended to refer to the source code as you read this web page to see how the code fits together. In some cases, the code examples given below are slightly modified to make this web page more readable. We cannot cover all features of commands here, and it strongly recommended to search on-line for documentation of the OpenGL commands and keywords to learn more about their functionality.

I. Using triangle fans and triangle strips

Triangle fans and triangle strips can be used to draw contiguous portions of surfaces with less repetition of vertices than would be needed to specify vertices of triangles individually. The code shown next defines an array of vertex information for a triangle fan, and sets up a a Vertex Array Object (VAO) for these vertices and loads the vertex data into Vertex Buffer Object (VBO). This is done by exactly the same kind of code as was used for points, lines, line strips, line loops, and triangles in SimpleDrawModern, so please see the documentation for SimpleDrawModern for more information on what this code does. (A lot of comments from the program have been removed here.)

    // Specify vertices for the triangles rendered with GL_TRIANGLE_FAN
    float triangleFanVerts[] = {
        // Positions            // Colors
        0.0f, 0.0f, 0.0f,       0.8f, 0.8f, 0.8f,   // Light grey
        1.0f, 0.0f, 0.0f,       1.0f, 0.0f, 0.0f,   // Red
        0.5f, 0.866f, 0.0f,     1.0f, 1.0f, 0.0f,   // Yellow
        -0.5f, 0.866f, 0.0f,    0.0f, 1.0f, 0.0f,   // Green
        -1.0f, 0.0f, 0.0f,      0.0f, 1.0f, 1.0f,   // Cyan
        -0.5f, -0.866f, 0.0f,   0.0f, 0.0f, 1.0f,   // Blue
        .5f, -0.866f, 0.0f,     1.0f, 0.0f, 1.0f,   // Magenta
        1.0f, 0.0f, 0.0f,       1.0f, 0.0f, 0.0f    // Duplicate vert and color
    };

    glBindVertexArray(myVAO[iTriangleFan]);
    glBindBuffer(GL_ARRAY_BUFFER, myVBO[iTriangleFan]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(triangleFanVerts), triangleFanVerts, GL_STATIC_DRAW);
    glVertexAttribPointer(vertPos_loc, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);    // Vertex data is in the VBO
    glEnableVertexAttribArray(vertPos_loc);                                    // Enable the vertex data
    glVertexAttribPointer(vertColor_loc, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(vertColor_loc);

Note that only one of vertices had to be specified twice; namely, the second vertex and the last vertex are repeated in order to have the outer edges of the triangle fan form a closed loop. By comparison, if the triangle fan were displayed as separate triangles, then the central vertex would have to be specified six times, and the other vertices would need to be specified two times each.

The triangle fan is rendered by the following code:

    // Draw Triangle Fan
    float matEntries[16];
    theModelViewMatrixTriFan.DumpByColumns(matEntries);
    glUniformMatrix4fv(modelviewMatLocation, 1, false, matEntries);
    glBindVertexArray(myVAO[iTriangleFan]);
    glDrawArrays(GL_TRIANGLE_FAN, 0, 8);

theModelViewMatrixTriFan is a 4x4 matrix, of type LinearMapR4: The C++ class of LinearMapR4 is defined in the files LinearMapR4[.cpp,h] and LinearMapR3[.cpp,h] The call to DumpByColumns() extracts the 16 entries of the 4x4 matrix into an array of floating point numbers (float's). The routine glUniformMatrix4fv loads them into the GPU as the "uniform" value of the modelview matrix for the shader. As we'll see later on in Section VI, one of the inputs to the vertex shader is declared by:

    uniform mat4 modelviewMatrix;       // The model-view matrix

This declaration means modelviewMatrix is a value that is set uniformly for all vertices (and in fact, for all shaders in the current shader program) rather than being specified in a VBO for a single vertex or in the VAO as a generic vertex attribute. The call to glUniformMatrix4fv sets this uniform value for all subsequent glDrawArrays and other kinds of glDraw... calls. Note that the uniform value does not belong to the VAO; in fact, the VAO is not even bound until after the uniform value is set.

The value of modelviewMatLocation specifies the location of the modelview matrix as a uniform input to the shader program. The value is set automatically when the shader program is compiled. Unfortunately, the location of the modelview matrix cannot be set by the user; instead it is set by the shader compiler. The C++ code determines the locations of the modelview matrices in the two shader programs using the following lines in the routine my_setup_SceneData():

    modelviewMatLocation_smooth = glGetUniformLocation(shaderProgramSmooth, modelviewMatName);
    modelviewMatLocation_flat = glGetUniformLocation(shaderProgramFlat, modelviewMatName);

modelviewMatLocation is set to the appropriate one of these in the myRenderScene when selecting which shader program to use. This is done with:

    int modelviewMatLocation;
    if (FlatSmoothMode == 0) {
        glUseProgram(shaderProgramSmooth);
        modelviewMatLocation = modelviewMatLocation_smooth;
    } 
    else {
        glUseProgram(shaderProgramFlat);
        modelviewMatLocation = modelviewMatLocation_flat;
    }

The call in the code above to glBindVertexArray makes the VAO myVAO[iTriangleFan] the active VAO. The subsequent call to glDrawArrays draws a triangle fan using eight vertices from the data stored in the VBO, starting with the first one, which has index zero (0).

The code for rendering a triangle strip and for rendering a sequence of three triangles is almost identical: you can see this by examining the source code. The matrix transformations for the three overlapping triangles are more complicated, since those triangles are animated with a slow rotation. The animation is discussed below in Section IV.

II. Using a projection matrix to control an orthographic view.

The projection matrix is another matrix which is a uniform input to the vertex shader. It is set by the C++ code by the same kinds of commands as are used for the modelview matrix, using glGetUniformLocation to find the shader program location of the projection matrix, and using glUniformMatrix4fv to set its value. The function window_size_callback is called every time the window is resized, just as in the SimpleDrawModern program. Resizing the window usually causes its aspect ratio to change, and this means the projection matrix needs to be changed to avoid distorting the image. window_size_callback uses the following code to define a orthographic projection matrix:

    // Setup the projection matrix as an orthographic view.
    // Determine the min and max values for x and y that should appear in the window.
    // The complication is that the aspect ratio of the window may not match the
    //      aspect ratio of the scene we want to view.
    double w = (width == 0) ? 1.0 : (double)width;
    double h = (height == 0) ? 1.0 : (double)height;
    
    // Scale (increase) either width or height up to match window aspect ratio
    double scale = (halfHeight * w ) / (halfWidth * h);
    if (scale > 1.0) {
        halfWidth *= scale;
    }
    else {
        halfHeight /= scale;
    }
    double windowXmin = centerX - halfWidth;
    double windowXmax = centerX + halfWidth;
    double windowYmin = centerX - halfHeight;
    double windowYmax = centerY + halfHeight;
 
    // Using the max & min values for x & y & z that should be visible in the window,
    //      we set up the orthographic projection.
    theProjectionMatrix.Set_glOrtho(windowXmin, windowXmax, windowYmin, windowYmax, Zmin, Zmax);

The routine window_size_callback is receives two parameters width and height giving the dimensions of the graphics window in pixels. It is desired to view objects which have x,y,z coordinates in the ranges [Xmin,Xmin], [Ymin,Ymax] and [Zmin,Zmax]. The aspect ratio of the window is compared to the aspect ratio of the desired view, and from this the maximum and minimum values for x and y that should be visible in the graphics window are computed. The final line of code below calls Set_glOrtho: this sets theProjectionMatrix to the desired orthographic projection matrix.

theProjectionMatrix is a LinearMapR4 object containing a 4x4 matrix. The function Set_glOrtho mostly replicates the old OpenGL function glOrtho but instead of multiplying by the orthographic matrix, Set_glOrtho just sets the matrix to the orthographic matrix. See the GlLinearMath web pages for more on the LinearMapR4 C++ class.

III. Using a modelview matrix to place geometric objects.

Traditionally, there are three matrices for controlling the placement of objects on the screen. The projection matrix controls the amount of perspective (if any) and the visible depth of field --- usually under the assumption that the viewer is placed at the origin, looking down the negative z-axis. The view matrix controls the placement of the overall scene, generally in front of the viewer, centered on the z-axis. The model matrix is used on a per-item basis to control the placement of individual objects within the scene. In practice, it is common to combine the view and model matrices in a single matrix, called the modelview matrix.

In SimpleAnimModern, the modelview matrices are quite simple to compute, but still allows separate objects to placed and even animated. The commands to set the modelview matrix for the triangle fan are:

    // The model view matrix for triangle fan resizes and repositions it
    theModelViewMatrixTriFan.Set_glTranslate(1.2, 1.2, 0.0);    // Initialize to translation by (1.2, 1.2, 0.0)
    theModelViewMatrixTriFan.Mult_glScale(0.5);                 // Shrink using scale factor 1/2 (multiplies on the right)

The first line sets the modelview matrix to the matrix which performs a translation by (1.2, 1.0, 0.0). The second line multiplies it on the right by the matrix which uniformly scales the object by a factor of 0.5. Later, the triangle fan is rendered by the commands:

    theModelViewMatrixTriFan.DumpByColumns(matEntries);
    glUniformMatrix4fv(modelviewMatLocation, 1, false, matEntries);
    glBindVertexArray(myVAO[iTriangleFan]);
    glDrawArrays(GL_TRIANGLE_FAN, 0, 8);

The code that sets the modelview matrix for the triangle strip works similarly using the following commands:

    // The model view matrix for triangle strip resizes and repositions it
    theModelViewMatrixTriStrip.Set_glTranslate(-1.5, 0.5, 0.0);         // Initialize to translation by (-1.5, 0.5, 0.0)
    theModelViewMatrixTriStrip.Mult_glScale(0.4);                       // Shrink by scaling factor 0.4
    theModelViewMatrixTriStrip.Mult_glRotate(-0.785, 0.0, 0.0, 1.0);    // Rotate 45 degrees (0.785 radians) clockwise

The final line multiplies the matrix on the right by the matrix which performs a rotation of 0.785 radians (approximately 45 degrees) clockwise around the z-axis. The angle is measured, using the right-hand rule, in the counterclockwise direction around the z-axis. Hence the first parameter to Mult_glRotate is negative, i.e., -0.785.

The functions Set_glTranslate, Mult_glScale, and Mult_glRotate duplicate the functionality of the legacy OpenGL commands glTranslate[f,d], glScale[f,d], and glRotate[f,d]. However, the first one uses the "Set" version of the command instead of the "Mult" version. The "Set" version sets the matrix equal to the translation matrix. The "Mult" versions multiply on the right by the matrix. The command Mult_glScale(0.4) is a convenience function that is equivalent to Mult_glScale(0.4,0.4,0.4). See the separate documentation page for more on the LinearMapR4 C++ package.

IV. Animating three rotating triangles with a matrix.

Animation of the three triangles in a simple rotation is done with exactly the same kinds of commands. mySetupGeometries sets a matrix to the identity:

    theViewMatrixThreeTriangles.SetIdentity();

Each time the scene is rendered, the modelview matrix for the triangles is computed by:

    currentAngle += 0.005;      // Increment current angle by 0.005 radians.
    if (currentAngle > 2.0*3.1415926535897932) {
        currentAngle -= 2.0*3.1415926535897932;
    }
    LinearMapR4 mat = theViewMatrixThreeTriangles;
    mat.Mult_glRotate(currentAngle, 0.0, 0.0, -1.0);   // Rotate around negative z-axis (clockwise for viewer)

The call to Mult_glRotate multiplies the matrix mat on the right by the indicated rotation matrix.

In this case, it is overkill to clamp the currentAngle in the range 0 to 2π; nonetheless, it good practice to avoid any possibility of large floating point numbers causing loss of precision.

V. Antialiasing with multisampling.

The main program contains the following two lines to turn on multisampling antialiasing (MSAA):

    glfwWindowHint(GLFW_SAMPLES, 4);        // Invoke Multisample Antialiasing (MSAA)
    glEnable(GL_MULTISAMPLE);               // Usually is enabled by default, but call just in case.

This instructs the OpenGL window to use "multisample aliasing" with each screen pixel holding four color and depth values (in a 2x2 arrangement). Each screen pixel has its color calculated once per triangle that overlaps the screen pixel: the color and depth values are saved into those subpixels which are actually covered by the triangle. This can greatly improve many common aliasing problems with edges of triangles.

Multisample antialiasing (MSAA) is fairly expensive in terms of memory, since it requires four color and depth values per pixel: this usually causes a noticeable slowdown in graphics performance. However, it is substantially less expensive than full-blown supersampling. A cheaper form of antialiasing, fast approximate antialiasing (FXAA) can often give good results, but is not a standard feature of OpenGL.

VI. Using the projection and modelview matrices in a vertex shader.

The main vertex shader and the fragment shader are shown below. The vertex shader now takes two kinds of inputs: First, there are two variables declared with in, named vertPos and vertColor; these are called vertex attributes. A vertex attribute value can be set from per-vertex data values from the VBO (as SimpleAnimModern does for both vertPos and vertColor), or using a VertexAttrib... command to supply a generic attribute (SimpleAnimModern does not use any generic attributes, but SimpleDrawModern used a generic attribute value for its vertColor when rendering three points). Second, the two values projectionMatrix and modelviewMatrix are declared as uniform values. Uniform values are set using a glUniform... command; in this case, a glUniformMatrix4fv command.

Uniform values are somewhat like generic vertex attributes in that they do not change per vertex. In fact, any kind of shader, not just a vertex shader, can take a uniform parameter as input. For instance, a fragment shader is able to directly access a uniform value. Vertex attributes on the other hand, are accessible only to the vertex shader: if the vertex shader sends them as out values to a fragment shader, then the fragment shader will by default get an averaged value, as is discussed in Section VII next. (The averaged value is calculated by OpenGL using linear interpolation; aka Gourand interpolation.)

The vertex shader is:

    #version 330 core
    layout (location = 0) in vec3 vertPos;    // Position in attribute location 0
    layout (location = 1) in vec3 vertColor;  // Color in attribute location 1
    out vec3 theColor;                        // Output a color to the fragment shader
    uniform mat4 projectionMatrix;            // The projection matrix
    uniform mat4 modelviewMatrix;             // The modelview matrix
    void main()
    {
       gl_Position = projectionMatrix * modelviewMatrix * vec4(vertPos.x, vertPos.y, vertPos.z, 1.0);
       theColor = vertColor;
    }

Note how the vertex shader is able to calculate the gl_Position using matrix operations.

The fragment shader is:

    const char *fragmentShader_ColorOnly =
    #version 330 core
    in vec3 theColor;       // Color value came from the vertex shader (smoothed) 
    out vec4 FragColor;     // Color that will be used for the fragment
    void main()
    {
       FragColor = vec4(theColor, 1.0f);   // Add alpha value of 1.0.
    }

VII. Using flat and smooth shading.

In the two shaders shown above, the parameter theColor was declared as an out variable for the vertex shader, and as an in variable for the fragment shader. When the shader program is compiled, it links these declarations, so that the output of the vertex shader becomes an input to the fragment shader. BUT, this is not quite correct: the value of the variable theColor of an input to a fragment shader is the average of the three values of vertices of the triangle containing the fragment (or the two vertices of the line segment containing the fragment). This averaging process is known as smooth shading or Gouraud shading. Hitting the space bar in the graphics window of SimpleAnimModern will toggle between smooth shading and flat shading.

In flat shading,the averaging is not done. Instead, every fragment of a triangle or a line segment gets the same color. The usual rule for flat shading is that the value from the last specified vertex is the value used for every fragment of a triangle or line segment. To use flat shading, the declarations of the variable vertColor are changed in the second pair of shaders in ShaderMgrSAM by adding the keyword flat. Thus, the flat version of the vertex shader contains the declaration

    flat out vec3 theColor; 

The corresponding declaration in the flat version of the fragment shader is

    flat in vec3 theColor;  

It is possible to change the convention that the flat shading is from the last vertex of a triangle: this is done with the glProvokingVertex function.

VIII. Clearing the color buffer and depth buffer.

SimpleAnimModern uses the following method to clear the color and depth buffers at the beginning of each rendering.

	// Clear the rendering window
	static const float black[] = { 0.0f, 0.0f, 0.0f, 0.0f };
	const float clearDepth = 1.0f;
	glClearBufferfv(GL_COLOR, 0, black);
	glClearBufferfv(GL_DEPTH, 0, &clearDepth);	// Must pass in a pointer to the depth value!

These are similar in functionality to glClearBuffer but give additional flexibility. A depth value of 1.0 is the maximum possible value, indicating maximum distance from the viewer.