Chapter 11. Fragment Operations

This chapter discusses the operations that can be applied either to the entire framebuffer or to individual fragments after the execution of the fragment shader in the OpenGL ES 2.0 fragment pipeline. As you’ll recall, the output of the fragment shader is the fragment’s color and depth value. The operations that occur after fragment shader execution and can affect the visibility and final color of a pixel are:

  • Scissor box testing.

  • Stencil buffer testing.

  • Depth buffer testing.

  • Multisampling.

  • Blending.

  • Dithering.

The tests and operations that a fragment goes through on its way to the framebuffer are shown in Figure 11-1.

The Post-Shader Fragment Pipeline

Figure 11-1. The Post-Shader Fragment Pipeline

As you might have noticed, there isn’t a stage named “multisampling.” Multisampling is an antialiasing technique that duplicates operations at a subfragment level. We describe more about how multisampling affects fragment processing later in the chapter.

The chapter concludes with a discussion of methods for reading pixels from and writing pixels to the framebuffer.

Buffers

OpenGL ES supports three types of buffers, each of which stores different data for every pixel in the framebuffer:

  • Color buffer (composed of front and back color buffers).

  • Depth buffer.

  • Stencil buffer.

The size of a buffer—commonly referred to as the “depth of the buffer” (but not to be confused with the depth buffer)—is measured by the number of bits that are available for storing information for a single pixel. The color buffer, for example, will have three components for storing the red, green, and blue color components, and optional storage for the alpha component. The depth of the color buffer is the sum of the number of bits for all of its components. For the depth and stencil buffers, on the other hand, a single value represents the bit depth of a pixel in those buffers. For example, a depth buffer might have 16 bits per pixel. The overall size of the buffer is the sum of the bit depths of all of the components. Common framebuffer depths include 16-bit RGB buffers, with five bits for red and blue, and six bits for green (the human visual system is more sensitive to green than red or blue), and 32 bits divided equally for an RGBA buffer.

Additionally, the color buffer may be double buffered, where it will contain two buffers: one that is displayed on the output device (usually a monitor or LCD display) named the “front” buffer; and another buffer that is hidden from the viewer, but used for constructing the next image to be displayed, and called the “back” buffer. In double-buffered applications, animation is accomplished by drawing into the back buffer, and then swapping the front and back buffers to display the new image. This swapping of buffers is usually synchronized with the refresh cycle of the display device, which will give the illusion of a continuously smooth animation. Recall that double buffering was discussed in Chapter 3, “An Introduction to EGL.”

Although every EGL configuration will have a color buffer, the depth and stencil buffers are optional. However, every EGL implementation must provide at least one configuration that contains all three of the buffers, with the depth buffer being at least 16 bits deep, and at least eight bits for the stencil buffer.

Requesting Additional Buffers

To include a depth or stencil buffer along with your color buffer, you need to request them when you specify the attributes for your EGL configuration. As you might recall from Chapter 3, you pass a set of attribute–value pairs into the EGL that specify the type of rendering surface your application needs. To include a depth buffer in addition to the color buffer, you would specify EGL_DEPTH_SIZE in the list of attributes with the desired bit depth you need. Likewise, you would add EGL_STENCIL_SIZE along with the number of required bits to obtain a stencil buffer.

Our convenience library, esUtil, simplifies those operations by merely allowing you to say that you’d like those buffers along with a color buffer, and it takes care of the rest of the work (requesting a maximally sized buffer). When using our library, you would add (by means of a bitwise-or operation) ES_WINDOW_DEPTH and ES_WINDOW_STENCIL in your call to esCreateWindow. For example,

esCreateWindow(&esContext, "Application Name",
               window_width, window_height,
               ES_WINDOW_RGB | ES_WINDOW_DEPTH | ES_WINDOW_STENCIL);

Clearing Buffers

OpenGL ES is an interactive rendering system, and assumes that at the start of each frame, you’ll want to initialize all of the buffers to their default value. Buffers are cleared by calling the glClear function, which takes a bitmask representing the various buffers that should be cleared to their specified clear values.

void

glClear(GLbitfield mask);

mask

specifies the buffers to be cleared, and is composed of the union of the following bitmask representing the various OpenGL ES buffers: GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT, GL_STENCIL_BUFFER_BIT

You’re neither required to clear every buffer, nor clear them all at the same time, but you might obtain the best performance by only calling glClear once per frame with all the buffers you want simultaneously cleared.

Each buffer has a default value that’s used when you request that buffer be cleared. For each buffer, you can specify your desired clear value using the functions shown here.

void

glClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha);

red, green, blue, alpha

specifies the color value (in the range [0,1]) that all pixels in the color buffers should be initialized to when GL_COLOR_BUFFER_BIT is present in the bitmask passed to glClear

void

glClearDepthf(GLclampf depth);

depth

specifies the depth value (in the range [0,1]) that all pixels in the depth buffer should be initialized to when GL_DEPTH_BUFFER_BIT is present in the bitmask passed to glClear

void

glClearStencil(GLint s);

s

specifies the stencil value (in the range [0,2n – 1], where n is the number of bits available in the stencil buffer) that all pixels in the stencil buffer should be initialized to when GL_STENCIL_BUFFER_BIT is present in the bitmask passed to glClear

Using Masks to Control Writing to Framebuffers

You can also control which buffers, or components, in the case of the color buffer, are writable by specifying a buffer write mask. Before a pixel’s value is written into a buffer, the buffer’s mask is used to verify that the buffer is writable.

For the color buffer, the glColorMask routine specifies which components in the color buffer will be updated if a pixel is written. If the mask for a particular component is set to GL_FALSE, that component will not be updated if written to. By default, all color components are writable.

void

glColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha);

red, green, blue, alpha

specify whether the particular color component in the color buffer is modifiable while rendering

Likewise, the writing to the depth buffer is controlled by calling glDepthMask with GL_TRUE or GL_FALSE to specify if the depth buffer is writable.

Quite often, disabling writing to the depth buffer is used when rendering translucent objects. Initially, you would render all of the opaque objects in the scene with writing to the depth buffer enabled (i.e., set to GL_TRUE). This would make sure that all of the opaque objects are correctly depth sorted, and the depth buffer contains the appropriate depth information for the scene. Then, before rendering the translucent objects, you would disable writing to the depth buffer by calling glDepthMask(GL_FALSE). While writing to the depth buffer is disabled, values can still be read from it and used for depth comparisons. This allows translucent objects that are obscured by opaque objects to be correctly depth buffered, but not modify the depth buffer such that opaque objects would be obscured by translucent ones.

void

glDepthMask(GLboolean depth);

depth

specifies whether the depth buffer is modifiable

Finally, you can also disable writing to the stencil buffer by calling glStencilMask, but as compared to glColorMask or glDepthMask, you specify which bits of the stencil buffer are writable by providing a mask.

void

glStencilMask(GLuint mask);

mask

specifies a bitmask (in the range [0,2n – 1], where n is the number of bits in the stencil buffer) of which bits in a pixel in the stencil buffer are modifiable

The glStencilMaskSeparate routine allows you to set the stencil mask based on the face vertex order (sometimes called “facedness”) of the primitive. This allows different stencil masks for front- and back-facing primitives. glStencilMaskSeparate(GL_FRONT_AND_BACK, mask) is identical to calling glStencilMask, which sets the same mask for the front and back polygon faces.

void

glStencilMaskSeparate(GLenum face, GLuint mask);

face

specifies the stencil mask to be applied based on the face vertex order of the rendered primitive. Valid values are GL_FRONT, GL_BACK, and GL_FRONT_AND_BACK

mask

specifies a bitmask (in the range [0,2n], where n is the number of bits in the stencil buffer) of which bits in a pixel in the stencil buffer are specified by face

Fragment Tests and Operations

The following sections describe the various tests that can be applied to a fragment in OpenGL ES. By default, all fragment tests and operations are disabled, and fragments become pixels as they are written to the framebuffer in the order in which they’re received. By enabling the various fragments, operational tests can be applied to choose which fragments become pixels and affect the final image.

Each fragment test is individually enabled by calling glEnable with the appropriate token listed in Table 11-1.

Table 11-1. Fragment Test Enable Tokens

glEnable Token

Description

GL_DEPTH_TEST

Control depth testing of fragments

GL_STENCIL_TEST

Control stencil testing of fragments

GL_BLEND

Control blending of fragments with colors stored in the color buffer

GL_DITHER

Control the dithering of fragment colors before being written in the color buffer

GL_SAMPLE_COVERAGE

Control the computation of sample coverage values

GL_SAMPLE_ALPHA_TO_COVERAGE

Control the use of a sample’s alpha in the computation of a sample coverage value

Using the Scissor Test

The scissor test provides an additional level of clipping by specifying a rectangular region that further limits which pixels in the framebuffer are writable. Using the scissor box is a two-step process. First, you need to specify the rectangular region using the glScissor function.

void

glScissor(GLint x, GLint y, GLsizei width, GLsizei height);

x, y

specify the lower left corner of the scissor rectangle in viewport coordinates

width

specifies the width of the scissor box (in pixels)

height

specifies the height of the scissor box (in pixels)

After specifying the scissor box, you’ll need to enable it by calling glEnable(GL_SCISSOR_TEST) to employ the additional clipping. All rendering, including clearing the viewport, is restricted to the scissor box.

Generally, the scissor box is a subregion in the viewport, but there’s no requirement that the two regions actually intersect.

Stencil Buffer Testing

The next operation that might be applied to a fragment is the stencil test. The stencil buffer is a per-pixel mask that holds values that can be used to determine whether a pixel should be updated or not. The stencil test is enabled or disabled by the application.

Using the stencil buffer can be considered a two-step operation. The first step is to initialize the stencil buffer with the per-pixel masks, which is done by rendering geometry and specifying how the stencil buffer should be updated. The second step is generally to use those values to control subsequent rendering into the color buffer. In both cases, you specify how the parameters are to be used in the stencil test.

The stencil test is essentially a bit test, as you might do in a C program where you use a mask to determine if a bit is set, for example. The stencil function, which controls the operator and values of the stencil test, is controlled by the glStencilFunc or glStencilFuncSeparate functions.

void

glStencilFunc(GLenum func, GLint ref, GLuint mask);

void

glStencilFuncSeparate(GLenum face, GLenum func, GLint ref, GLuint mask);

face

specifies the face associated with the provided stencil function. Valid values are GL_FRONT, GL_BACK, and GL_FRONT_AND_BACK

func

specifies the comparison function for the stencil test. Valid values are GL_EQUAL, GL_NOTEQUAL, GL_LESS, GL_GREATER, GL_LEQUAL, GL_GEQUAL, GL_ALWAYS, and GL_NEVER

ref

specifies the comparison value for the stencil test

mask

specifies the mask that is bitwise-anded with the bits in the stencil buffer before being compared against the reference value

To allow finer control of the stencil test, a masking parameter is used to select which bits of the stencil values should be considered for the test. After selecting those bits, their value is compared with a reference value using the operator provided. For example, to specify that the stencil test passes where the lowest three bits of the stencil buffer are equal to 2, you would call

glStencilFunc(GL_EQUAL, 2, 0x7);

and enable the stencil test.

With the stencil test configured, you generally also need to let OpenGL ES 2.0 know what to do with the values in the stencil buffer when the stencil test passes. In fact, modifying the values in the stencil buffer relies on more than just the stencil tests, but also incorporates the results of the depth test (discussed in the next section). There are three possible outcomes that can occur for a fragment with the combined stencil and depth tests:

  1. The fragment fails the stencil tests. If this occurs, no further testing (i.e., the depth test) is applied to that fragment.

  2. The fragment passes the stencil test, but fails the depth test.

  3. The fragment passes both the stencil and depth tests.

Each of those possible outcomes can be used to affect the value in the stencil buffer for that pixel location. The glStencilOp and glStencilOpSeparate functions control the actions done on the stencil buffer’s value for each of those test outcomes, and the possible operations on the stencil values are shown in Table 11-2.

Table 11-2. Stencil Operations

Stencil Function

Description

GL_ZERO

Set the stencil value to zero

GL_REPLACE

Replace the current stencil value with the reference value specified in glStencilFunc or glStencilFuncSeparate

GL_INCR, GL_DECR

Increment or decrement the stencil value; the stencil value is clamped to zero or 2n, where n is the number of bits in the stencil buffer

GL_INCR_WRAP, GL_DECR_WRAP

Increment or decrement the stencil value, but “wrap” the value if the stencil value overflows (incrementing the maximum value will result in a new stencil value of zero) or underflows (decrementing zero will result in the maximum stencil value)

GL_KEEP

Keep the current stencil value, effectively not modifying the value for that pixel

GL_INVERT

Bitwise-invert the value in the stencil buffer

void

glStencilOp(GLenum sfail, GLenum zfail, GLenum zpass);

void

glStencilOpSeparate(GLenum face, GLenum sfail, GLenum zfail, GLenum zpass);

face

specifies the face associated with the provided stencil function. Valid values are GL_FRONT, GL_BACK, and GL_FRONT_AND_BACK

sfail

specifies the operation applied to the stencil bits if the fragment fails the stencil test. Valid values are GL_KEEP, GL_ZERO, GL_REPLACE, GL_INCR, GL_DECR, GL_INCR_WRAP, GL_DECR_WRAP, and GL_INVERT

zfail

specifies the operation applied when the fragment passes the stencil test, but fails the depth test

zpass

specifies the operation applied when the fragment passes both the stencil and depth tests

The following example illustrates using glStencilFunc and glStencilOp to control rendering in various parts of the viewport.

GLfloat vVertices[] =

 {
     -0.75f,  0.25f,  0.50f, // Quad #0
     -0.25f,  0.25f,  0.50f,
     -0.25f,  0.75f,  0.50f,
     -0.75f,  0.75f,  0.50f,
      0.25f,  0.25f,  0.90f, // Quad #1
      0.75f,  0.25f,  0.90f,
      0.75f,  0.75f,  0.90f,
      0.25f,  0.75f,  0.90f,
     -0.75f, -0.75f,  0.50f, // Quad #2
     -0.25f, -0.75f,  0.50f,
     -0.25f, -0.25f,  0.50f,
     -0.75f, -0.25f,  0.50f,
      0.25f, -0.75f,  0.50f, // Quad #3
      0.75f, -0.75f,  0.50f,
      0.75f, -0.25f,  0.50f,
      0.25f, -0.25f,  0.50f,
     -1.00f, -1.00f,  0.00f, // Big Quad
      1.00f, -1.00f,  0.00f,
      1.00f,  1.00f,  0.00f,
     -1.00f,  1.00f,  0.00f
 };
   GLubyte indices[][6] =
   {
       {  0,  1,  2,  0,  2,  3 }, // Quad #0
       {  4,  5,  6,  4,  6,  7 }, // Quad #1
       {  8,  9, 10,  8, 10, 11 }, // Quad #2
       { 12, 13, 14, 12, 14, 15 }, // Quad #3
       { 16, 17, 18, 16, 18, 19 }  // Big Quad
   };

#define NumTests  4
   GLfloat  colors[NumTests][4] =
   {
       { 1.0f, 0.0f, 0.0f, 1.0f },
       { 0.0f, 1.0f, 0.0f, 1.0f },
       { 0.0f, 0.0f, 1.0f, 1.0f },
       { 1.0f, 1.0f, 0.0f, 0.0f }
   };

   GLint    numStencilBits;
   GLuint  stencilValues[NumTests] =
   {
      0x7, // Result of test 0
      0x0, // Result of test 1
      0x2, // Result of test 2
      0xff // Result of test 3. We need to fill this
           // value in a run-time
   };

   // Set the viewport
   glViewport(0, 0, esContext->width, esContext->height);

   // Clear the color, depth, and stencil buffers. At this
   // point, the stencil buffer will be 0x1 for all pixels
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT |
           GL_STENCIL_BUFFER_BIT);

   // Use the program object
   glUseProgram(userData->programObject);

   // Load the vertex position
   glVertexAttribPointer(userData->positionLoc, 3, GL_FLOAT,
                         GL_FALSE, 0, vVertices);

   glEnableVertexAttribArray(userData->positionLoc);

   // Test 0:
   //
   // Initialize upper-left region. In this case, the stencil-
   // buffer values will be replaced because the stencil test
   // for the rendered pixels will fail the stencil test, which is
   //
   //        ref   mask    stencil  mask
   //      ( 0x7 & 0x3 ) < ( 0x1 & 0x7 )
   //
   // The value in the stencil buffer for these pixels will
   // be 0x7.
   //
   glStencilFunc(GL_LESS, 0x7, 0x3);
   glStencilOp(GL_REPLACE, GL_DECR, GL_DECR);
   glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, indices[0]);

   // Test 1:
   //
   // Initialize the upper right region. Here, we'll decrement
   // the stencil-buffer values where the stencil test passes
   // but the depth test fails. The stencil test is
   //
   //        ref  mask     stencil  mask
   //       ( 0x3 & 0x3 ) > ( 0x1 & 0x3 )
   //
   //    but where the geometry fails the depth test.  The
   //    stencil values for these pixels will be 0x0.
   //
   glStencilFunc(GL_GREATER, 0x3, 0x3);
   glStencilOp(GL_KEEP, GL_DECR, GL_KEEP);
   glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, indices[1]);

   // Test 2:
   //
   // Initialize the lower left region. Here we'll increment
   // (with saturation) the stencil value where both the
   // stencil and depth tests pass. The stencil test for
   // these pixels will be
   //
   //        ref  mask      stencil  mask
   //      ( 0x1 & 0x3 ) == ( 0x1 & 0x3 )
   //
   // The stencil values for these pixels will be 0x2.
   //
   glStencilFunc(GL_EQUAL, 0x1, 0x3);
   glStencilOp(GL_KEEP, GL_INCR, GL_INCR);
   glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, indices[2]);

   // Test 3:
   //
   // Finally, initialize the lower right region. We'll invert
   // the stencil value where the stencil tests fails. The
   // stencil test for these pixels will be
   //
   //        ref   mask    stencil  mask
   //      ( 0x2 & 0x1 ) == ( 0x1 & 0x1 )
   //
   // The stencil value here will be set to ~((2^s-1) & 0x1),
   // (with the 0x1 being from the stencil clear value),
   // where 's' is the number of bits in the stencil buffer
   //
   glStencilFunc(GL_EQUAL, 0x2, 0x1);
   glStencilOp(GL_INVERT, GL_KEEP, GL_KEEP);
   glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, indices[3]);

   // As we don't know at compile time how many stencil bits are
   // present, we'll query, and update the correct value in the
   // stencilValues arrays for the fourth tests. We'll use this
   // value later in rendering.
   glGetIntegerv(GL_STENCIL_BITS, &numStencilBits);

   stencilValues[3] = ~(((1 << numStencilBits) - 1) & 0x1) & 0xff;

   // Use the stencil buffer for controlling where rendering will
   // occur. We disable writing to the stencil buffer so we can
   // test against them without modifying the values we generated.
   glStencilMask(0x0);

   for(i = 0; i < NumTests; ++i)
   {
      glStencilFunc(GL_EQUAL, stencilValues[i], 0xff);
      glUniform4fv(userData->colorLoc, 1, colors[i]);
      glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, indices[4]);
   }

Depth Buffer Testing

The depth buffer is usually used for hidden-surface removal. It traditionally keeps the distance value of the closest object to the viewpoint for each pixel in the rendering surface, and for every new incoming fragment, compares its distance from the viewpoint with the stored value. By default, if the incoming fragment’s depth value is less than the value stored in the depth buffer (meaning it’s closer to the viewer) the incoming fragment’s depth value replaced the values stored in the depth buffer, and then its color value replaces the color value in the color buffer. This is the standard method for depth buffering, and if that’s what you would like to do, all you need to do is request a depth buffer when you create a window, and then enable the depth test by calling glEnable with GL_DEPTH_TEST. If no depth buffer is associated with the color buffer, the depth test always passes. Of course, that’s only one way to use the depth buffer. You can modify the depth comparison operator by calling glDepthFunc.

void

glDepthFunc(GLenum func)

func

specifies the depth value comparison function, which can be one of GL_LESS, GL_GREATER, GL_LEQUAL, GL_GEQUAL, GL_EQUAL, GL_NOTEQUAL, GL_ALWAYS, or GL_NEVER

Blending

In this section, we discuss blending pixel colors. Once a fragment passes all of the enabled fragment tests, its color can be combined with the color that’s already present in the fragment’s pixel location. Before the two colors are combined, they’re multiplied by a scaling factor and combined using the specified blending operator. The blending equation is

Cfinal = fsource Csource op fdestingation Cdestination

where fsource and Csource are the incoming fragment’s scaling factor and color, respectively. Likewise, fdestingation and Cdestination are the pixel’s scaling factor and color. op is the mathematical operator for combining the scaled values.

The scaling factors are specified by calling either glBlendFunc or glBlendFuncSeparate.

void

glBlendFunc(GLenum sfactor, GLenum dfactor);

sfactor

specifies the blending coefficient for the incoming fragment

dfactor

specifies the blending coefficient for the destination pixel

void

glBlendFuncSeparate(GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha);

srcRGB

specifies the blending coefficient for the incoming fragment’s red, green, and blue components

dstRGB

specifies the blending coefficient for the destination pixel’s red, green, and blue components

srcAlpha

specifies the blending coefficient for the incoming fragment’s alpha value

dstAlpha

specifes the blending coefficient for the destination pixel’s alpha value

The possible values for the blending coefficients are shown in Table 11-3.

Table 11-3. Blending Functions

Blending Coefficient Enum

RGB Blending Factors

Alpha Blending Factor

GL_ZERO

(0, 0, 0)

0

GL_ONE

(1, 1, 1)

1

GL_SRC_COLOR

(Rs, Gs, Bs)

As

GL_ONE_MINUS_SRC_COLOR

(1 – Rs, 1 – Gs, 1 – Bs)

1 – As

GL_SRC_ALPHA

(As, As, As)

As

GL_ONE_MINUS_SRC_ALPHA

(1 – As, 1 – As, 1 – As)

1 – As

GL_DST_COLOR

(Rd, Gd, Bd)

Ad

GL_ONE_MINUS_DST_COLOR

(1 – Rd, 1 – Gd, 1 – Bd)

1 – Ad

GL_DST_ALPHA

(Ad, Ad, Ad)

Ad

GL_ONE_MINUS_DST_ALPHA

(1 – Ad, 1 – Ad, 1 – Ad)

1 – Ad

GL_CONSTANT_COLOR

(Rc, Gc, Bc)

Ac

GL_ONE_MINUS_CONSTANT_COLOR

(1 – Rc, 1 – Gc, 1 – Bc)

1 – Ac

GL_CONSTANT_ALPHA

(Ac, Ac, Ac)

Ac

GL_ONE_MINUS_CONSTANT_ALPHA

(1 – Ac, 1 – Ac, 1 – Ac)

1 – Ac

GL_SRC_ALPHA_SATURATE

min( As, 1 – Ad)

1

In Table 11-3 (Rs, Gs, Bs, As) are the color components associated with the incoming fragment color, (Rd, Gd, Bd, Ad) are the components associated with the pixel color already in the color buffer, and (Rc, Gc, Bc, Ac) represent a constant color that you set by calling glBlendColor. In the case of GL_SRC_ALHPA_SATURATE, the minimum value that’s computed is applied to the source color only.

void

glBlendColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha);

red, green, blue, alpha

specify the component values for the constant blending color

Once the incoming fragment and pixel color have been multiplied by their respective scaling factors, they’re combined using the operator specified by glBlendEquation or glBlendEquationSeparate. By default, blended colors are accumulated using the GL_FUNC_ADD operator. The GL_FUNC_SUBTRACT operator subtracts the scaled color from the framebuffer from the incoming fragment’s value. Likewise, the GL_FUNC_REVERSE_SUBTRACT reverses the blending equation such that the incoming fragment colors are subtracted from the current pixel value.

void

glBlendEquation(GLenum mode);

mode

specify the blending operator. Valid values are GL_FUNC_ADD, GL_FUNC_SUBTRACT, or GL_FUNC_REVERSE_SUBTRACT

void

glBlendEquationSeparate(GLenum modeRGB, GLenum modeAlpha);

modeRGB

specify the blending operator for the red, green, and blue components

modeAlpha

specify the alpha component blending operator

Dithering

On a system where the number of colors available in the framebuffer is limited due to the number of bits per component in the framebuffer, we can simulate greater color depth using dithering. Dithering algorithms arrange colors in such ways that the image appears to have more available colors than are really present. OpenGL ES 2.0 doesn’t specify which dithering algorithm is to be used in supporting its dithering stage; the technique is very implementation dependent.

The only control your application has over dithering is whether it is applied to the final pixels or not, which is entirely controlled by calling glEnable or glDisable to specify dithering’s use in the pipeline.

Multisampled Antialiasing

Antialiasing is an important technique for improving the quality of generated images by trying to reduce the visual artifacts of rendering into discrete pixels. The geometric primitives that OpenGL ES 2.0 renders get rasterized onto a grid, and their edges may become deformed in that process. We’ve all seen that staircase effect that happens to lines drawn diagonally across a monitor.

There are various techniques for trying to reduce those aliasing effects, and OpenGL ES 2.0 supports a variant called multisampling. Multisampling divides every pixel into a set of samples, each of which is treated like a “mini-pixel” during rasterization. That is, when a geometric primitive is rendered, it’s like rendering into a framebuffer that has many more pixels than the real display surface. Each sample has its own color, depth, and stencil value, and those values are preserved until the image is ready for display. When it’s time to compose the final image, the samples are resolved into the final pixel color. What makes this process special is that in addition to using every sample’s color information, OpenGL ES 2.0 also has additional information about how many samples for a particular pixel were occupied during rasterization. Each sample for a pixel is assigned a bit in the sample coverage mask. Using that coverage mask, we can control how the final pixels are resolved. Every rendering surface created for an OpenGL ES 2.0 application will be configured for multisampling, even if there’s only a single sample per pixel.

Multisampling has multiple options that can be turned on and off (using glEnable and glDisable) to control the usage of sample coverage value. First, you can specify that the sample’s alpha value should be used to determine the coverage value by enabling GL_SAMPLE_ALPHA_TO_COVERAGE. In this mode, if the geometric primitive covers a sample, the alpha value of incoming fragment is used to determine an additional sample coverage mask computed that is bitwise AND’ed into the coverage mask that is computed using the samples of the fragment. This newly computed coverage value replaces the original one generated directly from the sample coverage calculation. These sample computations are implementation dependent.

Additionally you can specify GL_SAMPLE_COVERAGE, which uses the fragment’s (potentially modified by previous operations listed earlier) coverage value, and computes the bitwise-and of that value with one specified using the glSampleCoverage function. The value specified with glSampleCoverage is used to generate an implementation-specific coverage mask, and includes an inversion flag, invert, that inverts the bits in the generated mask. Using this inversion flag, it is possible to create two transparency masks that don’t use entirely distinct sets of samples.

void

glSampleCoverage(GLfloat value, GLboolean invert);

value

specifies a value in the range [0, 1] that is converted into a sample mask. The resulting mask should have a proportional number of bits set corresponding to the value

invert

specifies that after determining the mask’s value, all of the bits in the mask should be inverted

Although multisampling helps reduce aliasing in scenes, it is also prone to artifacts that may be visually displeasing. This usually occurs due to the sample locations with a pixel. This problem can be rectified by using centroid sampling, which is unfortunately not a feature that OpenGL ES 2.0 supports at the time of this writing.

Reading and Writing Pixels to the Framebuffer

If you would like to preserve your rendered image for posterity’s sake, you can read the pixel values back from the color buffer, but not from the depth or stencil buffers. By calling glReadPixels, the pixels in the color buffer are returned to your application in an array that has been previously allocated.

void

glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *pixels);

x, y

specify the viewport coordinates of the lower left corner of the pixel rectangle read from the color buffer

width,

specify the dimensions of the pixel rectangle read from the

height

color buffer

format

specifies the pixel format that you would like returned. Two formats are available: GL_RGB, and the value returned by querying GL_IMPLEMENTATION_COLOR_READ_FORMAT, which is an implementation-specific pixel format

type

specifies the data type of the pixels returned. Two types are available: GL_UNSIGNED_BYTE, and the value returned from querying GL_IMPLEMENTATION_COLOR_READ_TYPE, which is an implementation-specific pixel type

pixels

is a contiguous array of bytes that contain the values read from the color buffer after glReadPixels returns

Aside from the fixed format (GL_RGB), and type (GL_UNSIGNED_BYTE), you’ll notice there are implementation-dependent values that should return the best format and type combination for the implementation you’re using. The implementation-specific values can be queried as follows:

GLenum    readType, readFormat;
GLubyte  *pixels;

glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &readType);
glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &readFormat);

unsigned int  bytesPerPixel = 0;

switch(readType)
{
     case GL_UNSIGNED_BYTE:
         switch(readFormat)
         {
             case GL_RGBA:
                 bytesPerPixel = 4;
                 break;
             case GL_RGB:
                 bytesPerPixel = 3;
                 break;

             case GL_LUMINANCE_ALPHA:
                 bytesPerPixel = 2;
                 break;

             case GL_ALPHA:
             case GL_LUMINANCE:
                 bytesPerPixel = 1;
                 break;
         }
         break;

     case GL_UNSIGNED_SHORT_4444:   // GL_RGBA format
     case GL_UNSIGNED_SHORT_555_1:  // GL_RGBA format
     case GL_UNSIGNED_SHORT_565:    // GL_RGB  format
         bytesPerPixel = 2;
         break;
}

pixels = (GLubyte*) malloc(width * height * bytesPerPixel);

glReadPixels(0, 0, windowWidth, windowHeight, readFormat,
             readType, pixels );

You can read pixels from any currently bound framebuffer, whether it’s one allocated by the windowing system, or from a framebuffer object. Because each buffer can have a different layout, you’ll probably need to query the type and format for each buffer you want to read.

OpenGL ES 2.0 doesn’t have a function to directly copy a block of pixels into the framebuffer. Instead, the available method is to create a texture map from the block of pixels, and use the texture-mapping technique described in Chapter 9, “Texturing,” to initiate writing the pixels.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset