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.
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.
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.
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);
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.
|
|
| specifies the buffers to be cleared, and is composed of the union of the following bitmask representing the various OpenGL ES buffers: |
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.
|
|
| specifies the color value (in the range [0,1]) that all pixels in the color buffers should be initialized to when |
|
|
| specifies the depth value (in the range [0,1]) that all pixels in the depth buffer should be initialized to when |
|
|
| 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 |
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.
|
|
| 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.
|
|
| 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.
| |
| 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.
|
|
| specifies the stencil mask to be applied based on the face vertex order of the rendered primitive. Valid values are |
| 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 |
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
Description | |
---|---|
| Control depth testing of fragments |
| Control stencil testing of fragments |
| Control blending of fragments with colors stored in the color buffer |
| Control the dithering of fragment colors before being written in the color buffer |
| Control the computation of sample coverage values |
| Control the use of a sample’s alpha in the computation of a sample coverage value |
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.
|
|
| specify the lower left corner of the scissor rectangle in viewport coordinates |
| specifies the width of the scissor box (in pixels) |
| 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.
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.
|
|
|
|
| specifies the face associated with the provided stencil function. Valid values are |
| specifies the comparison function for the stencil test. Valid values are |
| specifies the comparison value for the stencil test |
| 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:
The fragment fails the stencil tests. If this occurs, no further testing (i.e., the depth test) is applied to that fragment.
The fragment passes the stencil test, but fails the depth test.
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 |
---|---|
| Set the stencil value to zero |
| Replace the current stencil value with the reference value specified in |
| 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 |
| 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) |
| Keep the current stencil value, effectively not modifying the value for that pixel |
| Bitwise-invert the value in the stencil buffer |
| |
|
|
| specifies the face associated with the provided stencil function. Valid values are |
| specifies the operation applied to the stencil bits if the fragment fails the stencil test. Valid values are |
| specifies the operation applied when the fragment passes the stencil test, but fails the depth test |
| 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]); }
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
.
|
|
| specifies the depth value comparison function, which can be one of |
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
.
|
|
| specifies the blending coefficient for the incoming fragment |
| specifies the blending coefficient for the destination pixel |
|
|
| specifies the blending coefficient for the incoming fragment’s red, green, and blue components |
specifies the blending coefficient for the destination pixel’s red, green, and blue components | |
| specifies the blending coefficient for the incoming fragment’s alpha value |
| 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 |
---|---|---|
| (0, 0, 0) | 0 |
| (1, 1, 1) | 1 |
| (Rs, Gs, Bs) | As |
| (1 – Rs, 1 – Gs, 1 – Bs) | 1 – As |
| (As, As, As) | As |
| (1 – As, 1 – As, 1 – As) | 1 – As |
| (Rd, Gd, Bd) | Ad |
| (1 – Rd, 1 – Gd, 1 – Bd) | 1 – Ad |
| (Ad, Ad, Ad) | Ad |
| (1 – Ad, 1 – Ad, 1 – Ad) | 1 – Ad |
| (Rc, Gc, Bc) | Ac |
| (1 – Rc, 1 – Gc, 1 – Bc) | 1 – Ac |
| (Ac, Ac, Ac) | Ac |
| (1 – Ac, 1 – Ac, 1 – Ac) | 1 – Ac |
| 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.
|
|
| 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.
|
|
| specify the blending operator. Valid values are |
|
|
| specify the blending operator for the red, green, and blue components |
| specify the alpha component blending operator |
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.
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.
|
|
| 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 |
| 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.
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.
| |
| specify the viewport coordinates of the lower left corner of the pixel rectangle read from the color buffer |
| specify the dimensions of the pixel rectangle read from the |
| color buffer |
| specifies the pixel format that you would like returned. Two formats are available: |
| specifies the data type of the pixels returned. Two types are available: |
| is a contiguous array of bytes that contain the values read from the color buffer after |
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.