Rendering Grids with OpenGL Evaluators

I often hear requests from OpenGL programmers asking how to quickly render a graph paper-like grid. Grids are often used as backdrops for 3D models to get extra distance cues to the viewer. Often grids are Cartesian, but parametric grids are also useful.

The simplest way to draw a Cartesian grid is as a set of horizontal and vertical unconnected lines:

  /* Render grid over 0..rows, 0..columns. */
  glBegin(GL_LINES);
    /* Horizontal lines. */
    for (i=0; i<=rows; i++) {
      glVertex2f(0, i);
      glVertex2f(columns, i);
    }
    /* Vertical lines. */
    for (i=0; i<=columns; i++) {
      glVertex2f(i, 0);
      glVertex2f(i, rows);
    }
  glEnd();

Fancier grids may be rotated, sheared, or stretched. Even fancier grids are actually surface meshes defined by control points that describe a Bezier surface patch. OpenGL has a standard feature called evaluators that sets up a polynomial or rational polynomial mapping for producing vertex, normal, texture coordinates, and colors. With evaluators, it is actually quite straightforward to generate very fancy grids.

An Example: Rendering a Grid with Evaluators

Say you have the four corners of a grid. The corners of the grid can actually be thought of as control points for a linear (2nd order, ie. very simple) Bezier patch. It is not all that hard to setup OpenGL using evaluators to draw the grid in a few short commands.

Say our grid corners are located at (-2,-2), (4,-2), (-2,3), and (4,3). First set up an array of these coordinates (as 3D coordinates):

  GLfloat grid2x2[2][2][3] = {
    {{-2.0, -2.0, 0.0}, {4.0, -2.0, 0.0}},
    {{-2.0, 3.0, 0.0}, {3.0, 4.0, 0.0}}
  };

Before rendering the grid, we enabled the GL_MAP2_VERTEX_3 evaluator map and pass the control points to OpenGL with the glMap2f command:

  glEnable(GL_MAP2_VERTEX_3);
  glMap2f(GL_MAP2_VERTEX_3,
    0.0, 1.0,  /* U ranges 0..1 */
    3,         /* U stride, 3 floats per coord */
    2,         /* U is 2nd order, ie. linear */
    0.0, 1.0,  /* V ranges 0..1 */
    2 * 3,     /* V stride, row is 2 coords, 3 floats per coord */
    2,         /* V is 2nd order, ie linear */
    grid2x2);  /* control points */

When we actually go to evaluate and render the evaluator map as a 2D mesh, we need to indicate the number of partitions of U and V (number of grid rows and columns) and over what region of the parametric U and V domain we will iterate across. So we tell OpenGL to iterate across the full 0.0 to 1.0 range setup above with 5 rows and 6 columns. This is done with glMapGrid2f:

  glMapGrid2f(
    5, 0.0, 1.0,
    6, 0.0, 1.0);

After this setup is performed, a single OpenGL command evaluates and renders the specified grid as an evaluator mesh:

  glEvalMesh2(GL_LINE,
    0, 5,   /* Starting at 0 mesh 5 steps (rows). */
    0, 6);  /* Starting at 0 mesh 6 steps (columns). */

That may seem like a ton of setup work just to render a grid. With all the parameters required, it should not be at all surprising that OpenGL's evaluator mechanism can do a lot more than render simple grids. Along with grid outlines (GL_LINE), evaluators can generate solid patches (GL_FILL). OpenGL can also automatically generate normals. Colors and texture coordinates can also be evaluated.

Even with all the generality, why would an OpenGL programmer go through all the bother of specifying evaluator parameters? Couldn't an application just calculate the vertices for fancy Bezier patches and then just render the result with normal OpenGL primitives? Yes, but parametric curves and surfaces are both common (particularly in CAD and 3D modeling applications) and quite computationally expensive. It makes sense for OpenGL to off-load the floating-point intensive task of evaluating these curves and surfaces to fast 3D transformation engines when available. Indeed, SGI's RealityEngine, InfiniteReality, IMPACT, and Octane workstations all off-load OpenGL evaluator calculations to the dedicated Geometry Engine graphics hardware.

I describe the really really simple case of rendering a grid with evaluators not because it is a particularly wonderful or straightforward way to render grids with OpenGL, but because it helps introduce the concept of OpenGL evaluators without getting too complicated too fast. A grid is a nice, simple case of a flat Bezier surface.

A Complete Example Program

To further help you appreciate how a grid can be defined by control points, I've written a simple GLUT-based OpenGL program called editgrid. Check out the source file editgrid.c The program allows you to interactively move the control points for a simple 2D grid. For example:

The first picture above shows a basic 2D grid. The yellow dots indicate the location of evaluator control points. editgrid allows the user to interactively move the control points and see the corresponding change to the grid. The second picture above shows that the control points really are the corners of the grid. The grid above is specified as a 2nd order evaluator mesh for both U and V; this is why the grid is so linear.

Below is a similar grid but controlled by many more control points. The grid below is specified as a 4th order evaluator mesh for both U and V. Notice that in the picture below, there are still four corner points, but the other points help affect the curvature of the grid. All the grid lines are not straight as in the 2nd order evaluator mesh above.

Surfaces, Not Just Grids

Below is a Bezier surface rendered with OpenGL evaluators. Notice that lighting is enabled and uses surface normals generated during the evaluation. The source code for the program is bezmesh.c.

While curves and surfaces generated by evaluating Bezier patches and curves are interesting, surfaces defined by NURBS (non-uniform rational B-splines) are very common in 3D modeling and CAD. OpenGL's GLU library contains utility routines that will render NURBS surfaces, automatically making use of evaluators where possible to speed the rendering. Below is an example of rendering NURBS. The source code for the program is molehill.c.

More Information

For more information about about OpenGL evaluators, see the evaluators section of the OpenGL specification. The NURBS GLU routines are described in the GLU specification. The molehill example is described in my book Programming OpenGL for the X Window System. The bezmesh example is described in the OpenGL Programming Guide.

3D programmers interested in the raging OpenGL versus Direct3D debate should take special note that OpenGL has excellent hardware-amenable support for rendering curves and surfaces using evaluators as well as the higher-level GLU routines. Direct3D has no support for hardware-accelerated curve or surface rendering.

Other Cool OpenGL-rendering Techniques

This tutorial and source code is just one in a series of tutorials on fast, cool OpenGL rendering techniques. Check out the full set of tutorials. I hope this help!

- Mark Kilgard (mjk@nvidia.com)