Testing and Operating on Fragments (The Framebuffer) (OpenGL Programming) Part 2

Depth Test

For each pixel on the screen, the depth buffer keeps track of the distance between the viewpoint and the object occupying that pixel. Then, if the specified depth test passes, the incoming depth value replaces the value already in the depth buffer.

The depth buffer is generally used for hidden-surface elimination. If a new candidate color for that pixel appears, it’s drawn only if the corresponding object is closer than the previous object. In this way, after the entire scene has been rendered, only objects that aren’t obscured by other items remain. Initially, the clearing value for the depth buffer is a value that’s as far from the viewpoint as possible, so the depth of any object is nearer than that value. If this is how you want to use the depth buffer, you simply have to enable it by passing GL_DEPTH_TEST to glEnable() and remember to clear the depth buffer before you redraw each frame.You can also choose a different comparison function for the depth test with glDepthFunc().

void glDepthFunc(GLenum ftinc);

Sets the comparison function for the depth test. The value for func must be GL_NEVER, GL_ALWAYS, GL_LESS, GL_LEQUAL, GL_EQUAL, GL_GEQUAL, GL_GREATER, or GL_NOTEQUAL. An incoming fragment passes the depth test if its z-value has the specified relation to the value already stored in the depth buffer. The default is GL_LESS, which means that an incoming fragment passes the test if its z-value is less than that already stored in the depth buffer. In this case, the z-value represents the distance from the object to the viewpoint, and smaller values mean that the corresponding objects are closer to the viewpoint.


Occlusion Query

Advanced

The depth buffer determines visibility on a per-pixel basis. For performance reasons, it would be nice to be able to determine if a geometric object is visible before sending all of its (perhaps complex) geometry for rendering. Occlusion queries enable you to determine if a representative set of geometry will be visible after depth testing.

This is particularly useful for complex geometric objects with many polygons. Instead of rendering all of the geometry for a complex object, you might render its bounding box or another simplified representation that require less rendering resources. If OpenGL returns that no fragments or samples would have been modified by rendering that piece of geometry, you know that none of your complex object will be visible for that frame, and you can skip rendering that object for the frame.

The following steps are required to utilize occlusion queries:

1.    (Optional) Generate a query id for each occlusion query that you need.

2.    Specify the start of an occlusion query by calling glBeginQuery().

3.    Render the geometry for the occlusion test.

4.    Specify that you’ve completed the occlusion query by calling glEndQuery().

5.    Retrieve the number of samples that passed the depth test.

In order to make the occlusion query process as efficient as possible, you’ll want to disable all rendering modes that will increase the rendering time but won’t change the visibility of a pixel.

Generating Query Objects

An occlusion query object identifier is just an unsigned integer. While not strictly necessary, it’s a good practice to have OpenGL generate a set of ids for your use. glGenQueries() will generate the requested number of unused query ids for your subsequent use.

void glGenQueries(GLsizei nr GLuint *ids);

Returns n currently unused names for occlusion query objects in the array ids The names returned in ids do not have to be a contiguous set of integers.

The names returned are marked as used for the purposes of allocating additional query objects, but only acquire valid state once they have been specified in a call to glBeginQuery().

Zero is a reserved occlusion query object name and is never returned as a valid value by gIGenQueries().

You can also determine if an identifier is currently being used as an occlusion query by calling glIsQuery().

GLboolean glIsQuery(GLuint id);

Returns GL_TRUE if id is the name of an occlusion query object. Returns GL_FLASE if id is zero or id is a nonzero value that is not the name of a buffer object

Initiating an Occlusion Query Test

To specify geometry that’s to be used in an occlusion query, merely bracket the rendering operations between calls to glBeginQuery() and glEndQuery(), as demonstrated in Example 10-2.

Example 10-2 Rendering Geometry with Occlusion Query

Rendering Geometry with Occlusion Query

All OpenGL operations are available while an occlusion query is active, with the exception of glGenQueries() and glDeleteQueries(), which will raise a GL_INVALID_OPERATION error.

void glBeginQueryiGLenum target, GLuint id);

Specifies the start of an occlusion query operation, target must be GL_SAMPLES_PASSED. id is an unsigned integer identifier for this occlusion query operation.

void glEndQuery(GLenum target); 

Ends an occlusion query, target must be GL_SAMPLES_PASSED.

Determining the Results of an Occlusion Query

Once you’ve completed rendering the geometry for the occlusion query, you need to retrieve the results. This is done with a call to glGetQueryObject[u]iv(), as shown in Example 10-3, which will return the number of fragments, or samples, if you’re using multisampling.

void glGetQueryObjectiv(GLenum id, GLenum pname;GLint *params); void glGetQueryObjectuiv(GLenum id, GLenum pname,GLuint *params); 

Queries the state of an occlusion query object, id is the name of a query object. If pname is GL_QUERY_RESULT, then params will contain the number of fragments or samples (if multisampling is enabled) that passed the depth test, with a value of zero representing the object being entirely occluded.

There may be a delay in completing the occlusion query operation. If pname is GL_QUERY_RESULT_ AVAILABLE, params will contain GL_rRUE if the results for query id are available, or GL_FALSE otherwise.

Example 10-3 Retrieving the Results of an Occlusion Query

Retrieving the Results of an Occlusion Query

Cleaning Up Occlusion Query Objects

After you’ve completed your occlusion query tests, you can release the resources related to those queries by calling gIDeIeteQueries().

void glDe!eteQueries(GLsizei n, const GLuint *ids);

Deletes n occlusion query objects, named by elements in the array ids. The freed query objects ma)T now be reused (for example, by glGenQueriesQ).

Blending, Dithering, ana Logical Operations

Once an incoming fragment has passed all the tests described in "Testing and Operating on Fragments," it can be combined with the current contents of the color buffer in one of several ways. The simplest way, which is also the default, is to overwrite the existing values. Alternatively, if you ‘re using RGBA mode and you want the fragment to be translucent or antialiased, you might average its value with the value already in the buffer (blending). On systems with a small number of available colors, you might want to dither color values to increase the number of colors available at the cost of a loss in resolution. In the final stage, you can use arbitrary bitwise logical operations to combine the incoming fragment and the pixel that’s already written.

Blending

Blending combines the incoming fragment’s R, G, B, and alpha values with those of the pixel already stored at the location. Different blending operations can be applied, and the blending that occurs depends on the values of the incoming alpha value and the alpha value (if any) stored at the pixel.

Dithering

On systems with a small number of color bitplanes, you can improve the color resolution at the expense of spatial resolution by dithering the color in the image. Dithering is like halftoning in newspapers. Although The New York Times has only two colors—black and white—it can show photographs by representing the shades of gray with combinations of black and white dots. Comparing a newspaper image of a photo (having no shades of gray) with the original photo (with grayscale) makes the loss of spatial resolution obvious. Similarly, systems with a small number of color bitplanes may dither values of red, green, and blue on neighboring pixels for the appearance of a wider range of colors.

The dithering operation that takes place is hardware-dependent; all OpenGL allows you to do is to turn it on and off. In fact, on some machines, enabling dithering might do nothing at all, which makes sense if the machine already has high color resolution. To enable and disable dithering, pass GL_DITHER to gIEnabIe() and glDisable(). Dithering is enabled by default.

Dithering applies in both RGBA and color-index mode. The colors or color indices alternate in some hardware-dependent way between the two nearest possibilities. For example, in color-index mode, if dithering is enabled and the color index to be painted is 4.4, then 60 percent of the pixels may be painted with index 4, and 40 percent of the pixels with index 5. (Many dithering algorithms are possible, but a dithered value produced by any algorithm must depend on only the incoming value and the fragment’s x- and y-coordinates.) In RGBA mode, dithering is performed separately for each component (including alpha). To use dithering in color-index mode, you generally need to arrange the colors in the color map appropriately in ramps; otherwise, bizarre images might result.

Logical Operations

The final operation on a fragment is the logical operation, such as an OR, XOR, or INVERT, which is applied to the incoming fragment values (source) and/or those currently in the color buffer (destination). Such fragment operations are especially useful on bit-blt-type machines, on which the primary graphics operation is copying a rectangle of data from one place in the window to another, from the window to processor memory, or from memory to the window. Typically, the copy doesn’t write the data directly into memory but instead allows you to perform an arbitrary logical operation on the incoming data and the data already present; then it replaces the existing data with the results of the operation.

Since this process can be implemented fairly cheaply in hardware, many such machines are available. As an example of using a logical operation, XOR can be used to draw on an image in an undoable way; simply XOR the same drawing again, and the original image is restored. As another example, when using color-index mode, the color indices can be interpreted as bit patterns. Then you can compose an image as combinations of drawings on different layers, use writemasks to limit drawing to different sets of bitplanes, and perform logical operations to modify different layers.

You enable and disable logical operations by passing GL_INDEX_LOGIC_ OP or GL_COLOR_LOGIC_OP to glEnable() and glDisable() for color-index mode or RGBA mode, respectively. You also must choose among the 16 logical operations with glLogicOp(), or you’ll just get the effect of the default value, GL_COPY. (For backward compatibility with OpenGL Version 1.0, glEnable(GL_LOGIC_OP) also enables logical operation in color-index mode.)

void glLogicOp(GLenum opcode);

Selects the logical operation to be performed, given an incoming (source) fragment and the pixel currently stored in the color buffer (destination). Table 10-4 shows the possible values for opcode and their meaning (s represents source and d destination). The default value is GL_COPY.

Parameter

Operation

Parameter

Operation

GL.CLEAR

tmp1cfe-172

GL_AND

tmp1cfe-173

GL_COPY

tmp1cfe-174

GL_OR

tmp1cfe-175

GL_NOOP

tmp1cfe-176

GL_NAND

tmp1cfe-177

GL_SET

tmp1cfe-178

GL_NOR

tmp1cfe-179

GL_COPY_INVERTED

tmp1cfe-180

GL_XOR

tmp1cfe-181

GLJNVERT

tmp1cfe-182

GL_EQUIV

tmp1cfe-183

GL_AND_RE VERSE

tmp1cfe-184

GL_AND_INVERTED

tmp1cfe-185

GL_OR_RE VERSE

tmp1cfe-186

GL_OR_INVERTED

tmp1cfe-187

Table 10-4    Sixteen Logical Operations

Next post:

Previous post: