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.
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.
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.
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.