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

This page describes how NoPerspective program renders a checkerboard pattern texture map, first without hyperbolic interpolation, and secondly with hyperbolic interpolation. This is part of a series of programs illustrating features of Modern OpenGL. This page only discusses aspects of the software that are new to NoPerspective; other aspects of the code were discussed earlier with other programs.

The code fragments below are taken from the C++ program NoPerspective.cpp and the GLSL Shader code in NoPerspective.glsl. It is recommended to refer to the source code as you read this web page to see how the code fits together. We cannot cover all features of commands here, and it strongly recommended to search to find on-line documentation for the OpenGL commands and keywords to learn more about their functionality.

NoPerspective renders a single square. The square is formed from two triangles (with the GL_TRIANGLES drawing mode). The square is given texture coordinates that range uniformly over [0,1]×[0,1].

However, when rendered in noperspective the texture pattern on the two triangles making up the rectangle is distorted. Hitting the space bar (or "p") will toggle between noperspective and the usual, default smooth mode. The default smooth uses hyperbolic interpolation, and shows the correct application of the texture map.

I. The "noperspective" shaders

Vertex values output from the the vertex shader are averaged, or "interpolated" or "shaded", before reaching the fragment shader. This shading means that each pixel (fragment) receives a value which is formed as an average of the values from the three vertices on the triangle containing the pixel (or an average of the two pixels on the line containing the pixel when rendering a line).

By default, this shading, or "interpolation", is done using hyperbolic interpolation, and this is almost always the correct shading mode for your program. But the shading can be replaced with non-hyperbolic linear interpolation by declaring an in-out value with the qualifier noperspective. The vertex and shader programs that do this are shown next. First the vertex shader is:

    #version 330 core
    layout (location = 0) in vec3 vPos;	       // Position in attribute location 0
    layout (location = 1) in vec2 vTexCoords;  // Texture coordinates in attribute location 1
    noperspective out vec2 theTexCoords;       // Output Texture Coordinates to the fragment shader    
    uniform mat4 projectionMatrix;         // The projection matrix    
    uniform mat4 modelviewMatrix;          // The model-view matrix    
    void main()    
    {    
       gl_Position = projectionMatrix * modelviewMatrix * vec4(vPos.x, vPos.y, vPos.z, 1.0);    
       theTexCoords = vTexCoords;    
    }

And, the fragment shader is:

    #version 330 core    
    noperspective in vec2 theTexCoords;   // Color value came from the vertex shader (shaded)     
    out vec4 FragColor;                   // Color that will be used for the fragment    
    void main()    
    {    
       int resolution = 10;
       int i =  int(floor(theTexCoords.x*resolution));
       int j =  int(floor(theTexCoords.y*resolution));
       if ( (i+j)%2 == 0 ) {
         FragColor = vec4(0.0, 0.0, 0.0, 1.0);      // Black
       }
       else {
          FragColor = vec4(1.0, 1.0, 1.0, 1.0);     // White
       }
    }

The key thing to note here is the inclusion of the qualifier noperspective both in the vertex shader in declaring the out variable theTexCoords, and in the fragment shader declaring the same variable as an in variable. (Some GLSL compilers will let you get away with using the qualifier noperspective in only the fragment shader, but this is system dependent, and so is not a good practice.)

The "perspective" versions of the vertex and fragment shaders are identical the above code, except that they omit the qualifier noperspective. This defaults back to usual smooth or "hyperbolic" interpolation.

II. A procedural checkboard texture map

The fragment shader shown above calculated a "procedurally generated" texture map that forms a checkerboard pattern of alternating black and white squares. The variable resolution set the number of squares in each direction. The variables i and j give the coordinates of the square containing the current pixel. The GLSL function floor(…) rounds down to the nearest integer. This is needed in general to handle negative texture coordinates, as the function int rounds towards zero instead.

The parity of i+j is checked by testing the value (i+j)%2. This tests whether the square is black or white.

III. Polygon offset for outlining triangles

NoPerspective draws the triangles twice. The outlines of the triangles are rendering them with glPolygonMode set to GL_LINE and the fragment shader that draws solid black. The triangles are then drawn filled in by rendering with glPolygonMode set to GL_FILL and using a fragment shader that draws the checkerboard texture. This done with the code

    // Render the outlines of the triangles. Polygon offset is enabled for lines 
    glUseProgram(shaderProgramBlack);
    glUniformMatrix4fv(modelviewMatLocation3, 1, false, matEntries);
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

    // Render the triangles with the checked texture
    //      Use either perspective or no-perspective shaders.
    glUseProgram(perspectiveMode ? shaderProgramPerspective : shaderProgramNoPerspective);
    glUniformMatrix4fv(perspectiveMode ? modelviewMatLocation: modelviewMatLocation2, 1, false, matEntries);
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

This outlining of the triangles has the problem that it can cause aliasing between the edges of the triangles and the filled in triangles. The effect of the aliasing is that the edges of the triangles may show up as dots or short dashes, as the depth buffer testing is unable to reliably determine whether the outlines or the triangles are closer to the viewer. This is prevented by earlier calling:

    glPolygonOffset(-1, 1);             // Using -1, 1 makes depth value less (closer to the viewer)
    glEnable(GL_POLYGON_OFFSET_LINE);   // Polygon offset is enabled only for lines 

Using glPolygonOffset(…) with the parameters -1, 1 has the effect of decreasing the depth value slightly (that is, pulling the lines closer the viewer). It is enabled only for GL_POLYGON_OFFSET_LINE and thus the polygon offset affects only the outlines of the triangles, not the filled in triangles.