«*DPH 'HYHORSHUV &RQIHUHQFH $GYDQFHG 2SHQ*/ *DPH 'HYHORSPHQW Avoiding 19 Common OpenGL Pitfalls Mark J. Kilgard * NVIDIA Corporation Last updated: ...»
Still another symmetric, but generally unexpected result of OpenGL’s identical treatment of vertices and the raster position is that, just like a vertex, the raster position can be clipped. This means if you specify a raster position outside (even slightly outside) the view frustum, the raster position is clipped and marked “invalid”. When the raster position is invalid, OpenGL simply discards the pixel data specified by the glBitmap, glDrawPixels, and glCopyPixls commands.
Figure 3: The enclosing box represents the view frustum and viewport. Each line of text is preceded by a dot indicating where the raster position is set before rendering the line of text. The dotted underlining shows the pixels that will actually be rasterized from each line of text. Notice that none of the pixels in the lowest line of text are rendered because the line’s raster position is invalid.
Consider how this can surprise you. Say you wanted to draw a string of text with each character rendered with glBitmap. Figure 3 shows a few situations. The point to notice is that the text renders as expected in the first two cases, but in the last case, the raster position's placement is outside the view frustum so no pixels from the last text string are drawn.
It would appear that there is no way to begin rendering of a string of text outside the bounds of the viewport and view frustum and render at least the ending portion of the string. There is a way to accomplish what you want; it is just not very obvious. The glBitmap command both draws a bitmap and then offsets the raster position in relative window coordinates. You can render the final line of text if you first position the raster position within the view frustum (so that the raster position is set valid), and then you offset the raster position by calling glBitmap with relative raster position offsets. In this case, be sure to specify a zero-width and zero-height bitmap so no pixels are actually rendered.
Here is an example of this:
This code fragment assumes that the glRasterPos2i call will validate the raster position at the origin. The code to setup the projection and modelview matrix to do that is not show (setting both matrices to the identity matrix would be sufficient).
Figure 4: Various raster position scenarios. A, raster position is within the view frustum and the image is totally with the viewport. B, raster position is within the view frustum but the image is only partially within the viewport; still fragments are generated outside the viewport. C, raster position is invalid (due to being placed outside the view frustum); no pixels are rasterized. D, like case B except glPixelZoom(1,-1) has inverted the Y pixel rasterization direction so the image renders top to bottom.
10. The Viewport Does Not Clip or Scissor It is a very common misconception that pixels cannot be rendered outside the OpenGL viewport.
The viewport is often mistaken for a type of scissor. In fact, the viewport simply defines a transformation from normalized device coordinates (that is, post-projection matrix coordinates with the perspective divide applied) to window coordinates. The OpenGL specification makes no mention of clipping or culling when describing the operation of OpenGL’s viewport.
Part of the confusion comes from the fact that, most of the time, the viewport is set to be the window’s rectangular extent and pixels are clipped to the window’s rectangular extent. But do not confuse window ownership clipping with anything the viewport is doing because the viewport does not clip pixels.
Another reason that it seems like primitives are clipped by the viewport is that vertices are indeed clipped against the view frustum. OpenGL’s view frustum clipping does guarantee that no vertex (whether belonging to a geometric primitive or the raster position) can fall outside the viewport.
So if vertices cannot fall outside the view frustum and hence cannot be outside the viewport, how do pixels get rendered outside the viewport? Might it be an idle statement to say that the viewport does not act as a scissor if indeed you cannot generate pixels outside the viewport?
Well, you can generate fragments that fall outside the viewport rectangle so it is not an idle statement.
The last section has already hinted at one way. While the raster position vertex must be specified to be within the view frustum to validate the raster position, once valid, the raster position (the state of which is maintained in window coordinates) can be moved outside the viewport with the glBitmap call’s raster position offset capability. But you do not even have to move the raster position outside the viewport to update pixels outside of the viewport rectangle. You can just render a large enough bitmap or image so that the pixel rectangle exceeds the extent of the viewport rectangle. Figure 4 demonstrates image rendering outside the viewport.
The other case where fragments can be generated outside the viewport is when rasterizing wide lines and points or smooth points, lines, and polygons. While the actual vertices for wide and smooth primitives will be clipped to fall within the viewport during transformation, at rasterization time, the widened rasterization footprint of wide or smooth primitives may end up generating fragments outside the boundaries of the viewport rectangle.
Indeed, this can turn into a programming pitfall. Say your application renders a set of wide points
that slowly wander around on the screen. Your program configures OpenGL like this:
glViewport(0, 0, windowWidth, windowHeight);
What happens when a point slowly slides off the edge of the window? If the viewport matches the window’s extents as indicated by the glViewport call above, you will notice that a point will disappear suddenly at the moment its center is outside the window extent. If you expected the wide point to gradually slide of the screen, that is not what happens!
Keep in mind that the extra pixels around a wide or antialiased point are generated at rasterization time, but if the point’s vertex (at its center) is culled during vertex transformation time due to view frustum clipping, the widened rasterization never happens. You can fix the problem by widening the viewport to reflect the fact that a point’s edge can be up to four pixels (half of 8.0) from the point’s center and still generate fragments within the window’s extent. Change the
glViewport call to:
glViewport(-4, -4, windowWidth+4, windowHeight+4);
With this new viewport, wide points can still be rasterized even if the hang off the window edge.
Note that this will also slightly narrow your rectangular region of view, so if you want the identical view as before, you need to compensate by also expanding the view frustum specified by the projection matrix.
Note that if you really do require a rectangular 2D scissor in your application, OpenGL does provide a true window space scissor. See glEnable(GL_SCISSOR_TEST) and glScissor.
11. Setting the Raster Color Before you specify a vertex, you first specify the normal, texture coordinate, material, and color and then only when glVertex3f (or its ilk) is called will a vertex actually be generated based on the current per-vertex state. Calling glColor3f just sets the current color state. glColor3f does not actually create a vertex or any perform any rendering. The glVertex3f call is what binds up all the current per-vertex state and issues a complete vertex for transformation.
The raster position is updated similarly. Only when glRasterPos3f (or its ilk) is called does all the current per-vertex state get transformed and assigned to the raster position.
A common pitfall is attempting to draw a string of text with a series of glBitmap calls where
different characters in the string are different colors. For example:
Unfortunately, glBitmap’s relative offset of the raster position just updates the raster position location. The raster color (and the other remaining raster state values) remain unchanged.
The designers of OpenGL intentionally specified that glBitmap should not latch into place the current per-vertex state when the raster position is repositioned by glBitmap. Repeated glBitmap calls are designed for efficient text rendering with mono-chromatic text being the most common case. Extra processing to update per-vertex state would slow down the intended most common usage for glBitmap.
If you do want to switch the color of bitmaps rendered with glBitmap, you will need to explicitly call glRasterPos3f (or its ilk) to lock in a changed current color.
12. OpenGL’s Lower Left Origin Given a sheet of paper, people write from the top of the page to the bottom. The origin for writing text is at the upper left-hand margin of the page (at least in European languages). However, if you were to ask any decent math student to plot a few points on an X-Y graph, the origin would certainly be at the lower left-hand corner of the graph. Most 2D rendering APIs mimic writers and use a 2D coordinate system where the origin is in the upper left-hand corner of the screen or window (at least by default). On the other hand, 3D rendering APIs adopt the mathematically minded convention and assume a lower left-hand origin for their 3D coordinate systems.
If you are used to 2D graphics APIs, this difference of origin location can trip you up. When you specify 2D coordinates in OpenGL, they are generally based on a lower left-hand coordinate system. Keep this in mind when using glViewport, glScissor, glRasterPos2i, glBitmap, glTexCoord2f, glReadPixels, glCopyPixels, glCopyTexImage2D, glCopyTexSubImage2D, gluOrtho2D, and related routines.
Another common pitfall related to 2D rendering APIs having an upper left-hand coordinate system is that 2D image file formats start the image at the top scan line, not the bottom scan line.
OpenGL assumes images start at the bottom scan line by default. If you do need to flip an image when rendering, you can use glPixelZoom(1,-1) to flip the image in the Y direction. Note that you can also flip the image in the X direction. Figure 4 demonstrates using glPixelZoom to flip an image.
Note that glPixelZoom only works when rasterizing image rectangles with glDrawPixels or glCopyPixels. It does not work with glBitmap or glReadPixels. Unfortunately, OpenGL does not provide an efficient way to read an image from the frame buffer into memory starting with the top scan line.
13. Setting Your Raster Position to a Pixel Location
A common task in OpenGL programming is to render in window coordinates. This is often needed when overlaying text or blitting images onto precise screen locations. Often having a 2D window coordinate system with an upper left-hand origin matching the window system’s default 2D coordinate system is useful.
Here is code to configure OpenGL for a 2D window coordinate system with an upper left-hand
origin where w and h are the window’s width and height in pixels:
glViewport(0, 0, w, h);
glOrtho(0.0, w, h, 0.0, -1, 1);
One pitfall associated with setting up window coordinates is that switching to window coordinates involves loading both the modelview and projection matrices. If you need to “get back” to what was there before, use glPushMatrix and glPopMatrix (but remember the pitfall about assuming the projection matrix stack has more than two entries).
All this matrix manipulation can be a lot of work just to do something like place the raster position at some window coordinate. Brian Paul has implemented a freeware version of the OpenGL API called Mesa. Mesa implements an OpenGL extension called MESA_window_pos that permits direct efficient setting of the raster position without disturbing any other OpenGL state. The calls
Here is the equivalent implementation of these routines in unextended OpenGL:
Note all the extra work the emulation routines go through to ensure that no OpenGL state is disturbed in the process of setting the raster position. Perhaps commercial OpenGL vendors will consider implementing this extension.
14. Careful Enabling Color Material OpenGL’s color material feature provides a less expensive way to change material parameters.
With color material enabled, material colors track the current color. This means that instead of using the relatively expensive glMaterialfv routine, you can use the glColor3f routine.
Here is an example using the color material feature to change the diffuse color for each vertex of
glVertex3f(1.0, 0.0, 0.0);
glVertex3f(0.0, 0.0, 0.0);
glVertex3f(1.0, 1.0, 0.0);
If you are rendering objects that require frequent simple material changes, try to use the color material mode. However, there is a common pitfall encountered when enabling the color material mode. When color material is enabled, OpenGL immediately changes the material colors controlled by the color material
state. Consider the following piece of code to initialize a newly create OpenGL rendering context:
What state will the front ambient and diffuse material colors be after executing the above code fragment? While the programmer may have intended the ambient material state to be (0.1, 0.1, 0.1, 1.0) and the diffuse material state to be (0.3, 0.5, 0.6, 1.0), that is not quite what happens.