14 Rasterization and Operations on the Framebuffer
OpenGL doesn't provide a standard mechanism to let an application obtain the address of the framebuffer. If an implementation allows this, it's through an extension.
Typically, programmers who write graphics programs for a single standard graphics hardware format, such as the VGA standard under Microsoft Windows, will want the framebuffer's address. The programmers need to understand that OpenGL is designed to run on a wide variety of graphics hardware, many of which don't run on Microsoft Windows and therefore, don't support any kind of standard framebuffer format. Because a programmer will likely be unfamiliar with this proprietary framebuffer layout, writing directly to it would produce unpredictable results. Furthermore, some OpenGL devices might not have a framebuffer that the CPU can address.
You can read the contents of the color, depth, and stencil buffers with the glReadPixels() command. Likewise, glDrawPixels() and glCopyPixels() are available for sending images to and BLTing images around in the OpenGL buffers.
glDrawPixels() and glReadPixels() write and read rectangular areas to and from the framebuffer, respectively. Also, you can access stencil and depth buffer information with the format parameter. Single pixels can be written or read by specifying width and height parameters of 1.
glDrawPixels() draws pixel data with the current raster position at the lower left corner. Problems using glDrawPixels() typically occur because the raster position is set incorrectly. When the raster position is set with the glRasterPos*() function, it is transformed as if it were a 3D vertex. Then the glDrawPixels() data is written to the resulting device coordinate raster position. (This allows you to tie pixel arrays and bitmap data to positions in 3D space).
When the raster position is outside the view volume, it's clipped and the glDrawPixels() call isn't rendered. This occurs even when part of the glDrawPixels() data would be visible. Here's info on how to render when the raster position is clipped.
glReadPixels() doesn't use the raster position. Instead, it obtains its (X,Y) device coordinate address from its first two parameters. Like glDrawPixels(), the area read has x and y for the lower left corner. Problems can occur when reading pixels if:
- The area being read is from a window that is overlapped or partially offscreen. glReadPixels() will return undefined data for the obscured area. (More info.)
- Memory wasn't allocated for the return data (the 7th parameter is a NULL pointer) causing a segmentation fault, core dump, or program termination. If you think you've allocated enough memory, but you still run into this problem, try doubling the amount of memory you've allocated. If this causes your read to succeed, chances are you've miscalculated the amount of memory needed.
For both glDrawPixels() and glReadPixels(), keep in mind:
- The width and height parameters are in pixels.
- If the drawn or read pixel data seems correct, but is slightly off, make sure you've set alignment correctly. Argument values are controlled with the glPixelStore*() functions. The PACK and UNPACK values control sending and receiving pixel data, from and to OpenGL, respectively.
If you create a single-buffered window, you can't change it.
If you create a double-buffered window, you can treat it as a single-buffered window by setting glDrawBuffer() to GL_FRONT and replacing your swap buffers call with a glFlush() call. To switch back to double-buffered, you need to set glDrawBuffer() to GL_BACK, and call swap buffers at the end of the frame.
Use glReadPixels(), passing a value of one for the width and height parameters.
You can obtain a single pixel's depth value by reading it back from the depth buffer with a call to glReadPixels(). This returns the screen space depth value.
It could be useful to have this value in object coordinate space. If so, you'll need to pass the window X and Y values, along with the screen space depth value to gluUnProject(). See more information on gluUnProject() here.
You can set up OpenGL state as follows:
glEnable(GL_STENCIL_TEST); glStencilFunc(GL_ALWAYS, 0x1, 0x1); glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
Subsequent rendering will set a 1 bit in the stencil buffer for every pixel rendered.
You need to call glCopyPixels(). The source and destination of glCopyPixels() are set with calls to glReadBuffer() and glDrawBuffer(), respectively. Thus, to copy from the back buffer to the front buffer, you can code the following:
glReadBuffer (GL_BACK); glDrawBuffer (GL_FRONT); glCopyPixels (GL_COLOR);
This is due to a portion of the OpenGL specification called the Pixel Ownership test. If a window is obscured by another window, it doesn't have to store pixel data for the obscured region. Therefore, a glReadPixels() call can return undefined data for the obscured region.
The Pixel Ownership test varies from one OpenGL implementation to the next. Some OpenGL implementations store obscured regions of a window, or the entire window, in an off-screen buffer. Such an implementation can return valid pixel data for an obscured window. However, many OpenGL implementations map pixels on the screen one-to-one to framebuffer storage locations and don't store (and can't return) pixel data for obscured regions of a window.
One strategy is to instruct the windowing system to bring the window forward to the top of the window stack, render, then perform the glReadPixels() call. However, such an approach still risks user intervention that might obscure the source window.
An approach that might work for some applications is to render into a nonvisible window, such as a Pixmap under X Windows. This type of drawing surface can't be obscured by the user, and its contents should always pass the pixel ownership test. Reading from such a drawing surface should always yield valid pixel data. Unfortunately, rendering to such drawing surfaces is often not accelerated by graphics hardware.
An OpenGL implementation may or may not break up your quad into two triangles for rendering. Whether it breaks it up or not (and if it does, the method used to split the quad) will determine how color is interpolated along the edges and ultimately across each scanline.
Many OpenGL applications avoid quads altogether because of their inherent rasterization problems. A quad can be rendered easily as a two-triangle GL_TRIANGLE_STRIP primitive with the same data transmission cost as the equivalent quad. Wise programmers use this primitive in place of quads.
The OpenGL specification allows for a wide range of line rendering hardware, so exact pixelization may not be possible at all.
You might want to read the OpenGL specification and become familiar yourself with the diamond exit rule. Being familiar with this rule will give you the best chance to obtain exact pixelization. Briefly, the diamond exit rule specifies that a diamond-shaped area exists within each pixel. A pixel is rasterized by a line only if the mathematical definition of that line exits the diamond inscribed within that pixel.
OpenGL draws wide lines by rendering multiple width-1 component lines adjacent to each other. If the wide line is Y major, the component lines are offset in X; if the wide line is X major, the component lines are offset in Y. This can produce ugly gaps at the junction of line segments and differences in apparent width depending on the line segment's slope.
OpenGL doesn't provide a mechanism to cleanly join lines that share common vertices nor to cleanly cap the endpoints.
One possible solution is to render smooth (antialiased) lines instead of normal aliased lines. To produce a clean junction, you need to draw lines with depth test disabled or the depth function set to GL_ALWAYS. See the question on rendering antialiased lines for more info.
Another solution is for the application to handle the capping and mitering. Instead of rendering lines, the application needs to render face-on polygons. The application will need to perform the necessary math to calculate the vertex locations to provide the desired capping and joining styles.
The unspoken objective of this question is, "How can I render something, then erase it without disturbing what has already been rendered?"
Here are two common approaches.
One way is to use overlay planes. You draw the rubber-band lines into the overlay planes, then clear the overlay planes. The contents of the main framebuffer isn't disturbed. The disadvantage of this approach is that OpenGL devices don't widely support overlay planes.
The other approach is to render with logic op enabled and set to XOR mode. Assuming you're rendering into an RGBA window, your code needs to look like:
Set the color to white and render your lines. Where your lines are drawn, the contents of the framebuffer will be inverted. When you render the lines a second time, the contents of the framebuffer will be restored.
The logic op command for RGBA windows is only available with OpenGL 1.1. Under 1.0, you can only enable logic op in color index windows, and GL_LOGIC_OP is passed as the parameter to glEnable().
Filled primitives and line primitives follow different rules for rasterization.
When a filled primitive is rendered, a pixel is only touched if its exact center falls within the primitive's mathematical boundary.
When a line primitive is rasterized, ideally a pixel is only touched if the line exits a diamond inscribed in the pixel's boundary.
From these rules, it should be clear that a line loop specified with the same vertices as those used for a filled primitive, can rasterize pixels that the filled primitive doesn't.
(The OpenGL specification allows for some deviation from the diamond exit line rasterization rule, but it makes no difference in this scenario.)
Draw a full screen quad. See the Transformation section.
To render smooth (antialiased) lines, an application needs to do the following:
glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_LINE_SMOOTH);
If the scene consists entirely of smooth lines, you need to disable the depth test or set it to GL_ALWAYS.
If a scene contains both smooth lines and other primitives, turning depth test off isn't an option. You can achieve nearly correct rendering results if you treat the smooth lines as transparent primitives. The other (non-blended) primitives should be rendered first, then the smooth lines rendered last, in back to front order. See the transparency section for more information.
Even taking these precautions might not prevent some rasterization artifacts at the joints of smooth line segments that share common vertices. The fact that the depth test is enabled could conceivably cause some line endpoints to be rendered incorrectly. This is a rendering artifact that you may have to live with if the depth test must be enabled while smooth lines are rendered.
Not all OpenGL implementations support antialiased polygons. According to the OpenGL spec, an implementation can render an aliased polygon when GL_POLYGON_SMOOTH is enabled.
See the OpenGL Programming Guide, Third Edition, p452, for a description of a multi-pass accumulation buffer technique. This method performs well on devices that support the accumulation buffer in hardware.
On OpenGL 1.2 implementations that support the optional imaging extension, a smoothing filter may be applied to the final framebuffer image.
Many devices support the multisampling extension.