Home > Articles > Programming

  • Print
  • + Share This
This chapter is from the book

Connecting The Dots

When (and if) you first learned to draw any kind of 2D graphics on any computer system, you probably started with pixels. A pixel is the smallest element on your computer monitor, and on color systems that pixel can be any one of many available colors. This is computer graphics at its simplest: Draw a point somewhere on the screen and make it a specific color. Then build on this simple concept, using your favorite computer language to produce lines, polygons, circles, and other shapes and graphics—perhaps even a GUI.

With OpenGL, however, drawing on the computer screen is fundamentally different. You're not concerned with physical screen coordinates and pixels, but rather positional coordinates in your viewing volume. It is the job of your shader program and rasterization hardware to get your points, lines, and triangles projected from your established 3D space to the 2D image seen on your computer screen.

Points and Lines

To start drawing solid geometry, we use seven geometric primitives defined by OpenGL. Primitives are rendered in a single batch that contain all the vertices and associated attributes for a given primitive. Essentially, all the vertices in a given batch are assembled into one of these primitives. Table 3.2 lists these seven primitives and briefly describes their purpose.

Table 3.2. OpenGL Geometric Primitives

Primitive

Description

GL_POINTS

Each vertex is a single point on the screen.

GL_LINES

Each pair of vertices defines a line segment.

GL_LINE_STRIP

A line segment is drawn from the first vertex to each successive vertex.

GL_LINE_LOOP

Same as GL_LINE_STRIP, but the last and first vertex are connected.

GL_TRIANGLES

Every three vertices define a new triangle.

GL_TRIANGLE_STRIP

Triangles share vertices along a strip.

GL_TRIANGLE_FAN

Triangles fan out from an origin, sharing adjacent vertices.

The example program Primitives demonstrates rendering each one of these. Start the program and press the space bar to progress from GL_POINTS to GL_TRIANGLE_STRIP. You can also use the arrow keys to rotate the rendering on the x and y axes.

Points

Points are the simplest primitive. Each vertex specified is simply a single point on the screen. By default, points are one pixel in size. You can change the default point size by calling glPointSize.

void glPointSize(GLfloat size);

The glPointSize function takes a single parameter that specifies the approximate diameter in pixels of the point drawn. Not all point sizes are supported, however, and you should make sure the point size you specify is available. Use the following code to get the range of point sizes and the smallest interval between them:

GLfloat sizes[2];     // Store supported point size range
GLfloat step;         // Store supported point size increments

// Get supported point size range and step size
glGetFloatv(GL_POINT_SIZE_RANGE,sizes);
glGetFloatv(GL_POINT_SIZE_GRANULARITY,&step);

Here, the sizes array contains two elements that contain the smallest and largest valid value for glPointsize. In addition, the variable step holds the smallest step size allowable between the point sizes. The OpenGL specification requires only that one point size, 1.0, be supported, but most implementations support a wide range of point sizes. For example, you may find a range for point sizes from 0.5 to 256.0, with 0.125 the smallest step size. Specifying a size out of range is not interpreted as an error. Instead, the largest or smallest supported size is used, whichever is closest to the value specified.

By default, point size, unlike other geometry, is not affected by the perspective division. That is, they do not become smaller when they are further from the viewpoint, and they do not become larger as they move closer. Points are also always square pixels, even if you use glPointSize to increase the size of the points. You just get bigger squares! To get round points, you must draw them antialiased (coming up later in this chapter).

Another way to set the point size is to enable program point size mode.

glEnable(GL_PROGRAM_POINT_SIZE);

This mode allows you to set the point size programmatically in the vertex shader or the geometry shader, both of which are beyond the scope of this chapter. For completeness, the shader built-in variable is gl_PointSize, and in your shader source code, you'd simply set it like this:

gl_PointSize = 5.0;

Figure 3.5 shows the initial output from the Primitives example program. The point size was set to 4.0, and an array of vertices is used that is shaped roughly like the state of Florida.

Figure 3.5

Figure 3.5 A set of points along the coastline of Florida.

Lines

Next up the ladder from points are individual line segments. A line segment is drawn between two vertices, so a batch of lines should consist of an even number of vertices, one for each end of the line segment. If we use the same set of points used in the previous figure to draw a series of lines, every two points along the Florida coast will form a new line segment. This of course would leave gaps between each separated line segment as shown in Figure 3.6.

Figure 3.6

Figure 3.6 Separated lines, each formed by two vertices.

Lines are by default one pixel in width. The only way to change a line's width is with the function glLineWidth.

void glLineWidth(GLfloat width);

Line Strips

For a true connect-the-dots parallel, line strips draw line segments from one vertex to the next continually. To make a continuous line around the state of Florida with separated lines, each of the connecting vertices would have to be specified twice. Once as the end of one line segment, and then sent down again as the beginning of the next line segment. Moving all this extra data and transforming the same point twice is waste of bandwidth and clock cycles on the GPU. Figure 3.7 shows the same set of points again, this time drawn as a GL_LINE_STRIP.

Figure 3.7

Figure 3.7 A line strip simply connects the dots from start to finish.

Line Loops

A simple extension to line strips, a line loop draws an extra line segment from the end of the batch back to the beginning. This provides a net savings of only one vertex but is convenient when you are trying to close a loop or line-based figure. Figure 3.8 shows the inevitable conclusion of our rendering of the outline of the state of Florida in the Primitives example program.

Figure 3.8

Figure 3.8 Closing the final gap with a line loop.

Drawing Triangles in 3D

You've seen how to draw points and lines and even how to draw some enclosed polygons with GL_LINE_LOOP. With just these primitives, you could easily draw any shape possible in three dimensions. You could, for example, draw six squares and arrange them so they form the sides of a cube.

You might have noticed, however, that any shapes you create with these primitives are not filled with any color; after all, you are drawing only lines. In fact, arranging six squares produces just a wireframe cube, not a solid cube. To draw a solid surface, you need more than just points and lines; you need polygons. A polygon is a closed shape that may or may not be filled with colors or texture data, and it is the basis for all solid-object composition in OpenGL.

Individual Triangles

The simplest solid polygon possible is the triangle, with only three sides. Rasterization hardware just loves triangles, and so this is now the only type of polygon that OpenGL supports. Every three vertices simply form a new triangle. Figure 3.9 shows two triangles drawn with six vertices numbered V0 through V5.

Figure 3.9

Figure 3.9 Two triangles drawn using .

Figure 3.10 shows the next step of the Primitives example program. Here, we don't draw one triangle, but four triangles in the shape of a pyramid. The arrow keys allow you to rotate the pyramid and look at it from different angles. There is no bottom on the pyramid, so you can also look up inside it.

Figure 3.10

Figure 3.10 Triangles forming a four-sided pyramid.

Note that in our example here (and continuing forward), we have outlined the green triangles with black lines. Because there is no shading or texture across the triangles, this helps the individual triangles stand out. This black outline is not a natural behavior of primitives. This was achieved by drawing the geometry solid and then drawing a black wireframe version of the geometry right over the top of the solid. We show you how this is done in more detail in the upcoming section on glPolygonOffset.

Winding

An important characteristic of any triangle is illustrated in Figure 3.9. Notice the arrows on the lines that connect the vertices. When the first triangle is drawn, the lines are drawn from V0 to V1, and then to V2, and finally back to V0 to close the triangle. This path is in the order in which the vertices are specified, and for this example, that order is clockwise from your point of view. The same directional characteristic is present for the second triangle as well.

The combination of order and direction in which the vertices are specified is called winding. The triangles in Figure 3.9 are said to have clockwise winding because they are literally wound in the clockwise direction. If we reverse the positions of V4 and V5 on the triangle on the left, we get counterclockwise winding. Figure 3.11 shows two triangles, each with opposite windings.

Figure 3.11

Figure 3.11 Two triangles with different windings.

OpenGL, by default, considers polygons that have counterclockwise winding to be front facing. This means that the triangle on the left in Figure 3.11 shows the front of the triangle, and the one on the right shows the back of the triangle.

Why is this issue important? As you will soon see, you will often want to give the front and back of a polygon different physical characteristics. You can hide the back of a triangle altogether or give it a different color and reflective property. Texture images are also reversed on the back sides of triangles. It's important to keep the winding of all polygons in a scene consistent, using front-facing polygons to draw the outside surface of any solid objects. If you need to reverse the default behavior of OpenGL, you can do so by calling the following function:

glFrontFace(GL_CW);

The GL_CW parameter tells OpenGL that clockwise-wound polygons are to be considered front-facing. To change back to counterclockwise winding for the front face (this is the default anyway), use GL_CCW.

Triangle Strips

For many surfaces and shapes, you need to draw several connected triangles. You can save a lot of time by drawing a strip of connected triangles with the GL_TRIANGLE_STRIP primitive. Figure 3.12 shows the progression of a strip of three triangles specified by a set of five vertices numbered V0 through V4. Here, you see that the vertices are not necessarily traversed in the same order in which they were specified. The reason for this is to preserve the winding (counterclockwise) of each triangle. The pattern is V0, V1, V2; then V2, V1, V3; then V2, V3, V4; and so on.

Figure 3.12

Figure 3.12 The progression of a GL_TRIANGLE_STRIP.

There are two advantages to using a strip of triangles instead of specifying each triangle separately. First, after specifying the first three vertices for the initial triangle, you need to specify only a single point for each additional triangle. This saves a lot of program or data storage space when you have many triangles to draw. The second advantage is mathematical performance and bandwidth savings. Fewer vertices means a faster transfer from your computer's memory to your graphics card and fewer times your vertex shader must be executed. Figure 3.13 shows the next step in the Primitives example program. Here, a triangle strip is drawn to create a circular band.

Figure 3.13

Figure 3.13 A circular band made of a triangle strip.

Triangle Fans

In addition to triangle strips, you can use GL_TRIANGLE_FAN to produce a group of connected triangles that fan around a central point. Figure 3.14 shows a fan of three triangles produced by specifying four vertices. The first vertex, V0, forms the origin of the fan. After the first three vertices are used to draw the initial triangle, all subsequent vertices are used with the origin (V0) and the vertex immediately preceding it (Vn–1) to form the next triangle.

Figure 3.14

Figure 3.14 The progression of GL_TRIANGLE_FAN.

Figure 3.15 shows the final step of the Primitives example program. We've drawn six triangles around the origin of the triangle fan, which we raised ever so slightly to give it a little depth. Don't forget you can use the arrow keys to rotate the figures around.

Figure 3.15

Figure 3.15 Final output of the Primitives example, GL_TRIANGLE_FAN.

A Simple Batch Container

The GLTools library contains a simple container class called GBatch. This class can contain a single batch of any of the seven primitives listed in Table 3.2, and it knows how to render the primitives when using any of the stock shaders supported by the GLShaderManager. Using the GLBatch class is simple. First initialize the batch, telling the class the type of primitive it represents, the number of vertices it will contain, and optionally, one or two sets of texture coordinates:

void GLBatch::Begin(GLenum primitive, GLuint nVerts, GLuint nTextureUnits = 0);

Then, at a minimum, copy in an array of three component (x, y, z) vertices.

void GLBatch::CopyVertexData3f(GLfloat *vVerts);

Optionally, you can also copy in surface normals, colors, and texture coordinates as well:

void GLBatch::CopyNormalDataf(GLfloat *vNorms);
void GLBatch::CopyColorData4f(GLfloat *vColors);
void GLBatch::CopyTexCoordData2f(GLfloat *vTexCoords, GLuint uiTextureLayer);

When you are finished, you can call End to signify you are done copying in data, and the internal flags will be set so the class knows which attributes it contains.

void GLBatch::End(void);

You can actually copy new data in anytime you like, as long as you do not change the size of the class, and once you call the End function, you cannot add new attributes (that is you can't decide now I also want surface normals too).

The actual underlying OpenGL mechanism for submitting attributes is actually far more flexible than this. The GLBatch class, however, is simply a convenience class, much like using GLUT is convenient so you don't have to worry about OS specifics until you are ready.

Let's take a quick look at using the class to render a single triangle. In the Triangle example from Chapter 2 (the simplest example we have), we declared an instance of the GLBatch class near the top of the source file:

GLBatch triangleBatch;

Then in the SetupRC function, we set up the triangle with three vertices.

// Load up a triangle
GLfloat vVerts[] = { -0.5f, 0.0f, 0.0f,
                  0.5f, 0.0f, 0.0f,
                   0.0f, 0.5f, 0.0f };

triangleBatch.Begin(GL_TRIANGLES, 3);
triangleBatch.CopyVertexData3f(vVerts);
triangleBatch.End();

Finally, in the RenderScene function, we select the appropriate stock shader and call the Draw function.

GLfloat vRed[] = { 1.0f, 0.0f, 0.0f, 1.0f };
shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vRed);
triangleBatch.Draw();

While the GLBatch class provides a convenient mechanism for containing and submitting geometry, it is not fully representative of the full breadth and power of OpenGL for geometry management. Chapter 12, "Advanced Geometry Management," goes into far more detail on this topic. The reason we don't cover this material earlier in the book is simply because we want you to be able to start rendering as soon as possible. One of the best ways to learn is by doing, and too much explanation before seeing anything on-screen can be very frustrating, much less error prone.

Unwanted Geometry

By default, every point, line, or triangle you render is rasterized on-screen and in the order in which you specify when you assemble the primitive batch. This can sometimes be problematic. One problem that can occur is if you draw a solid object made up of many triangles, the triangles drawn first can be drawn over by triangles drawn afterward. For example, let's say you have an object such as a torus (donut shaped object) made up of many triangles. Some of those triangles are on the back side of the torus, and some on the front sides. You can't see the back sides—at least you aren't supposed to see the backsides (omitting for the moment the special case of transparent geometry). Depending on your orientation, the order in which the triangles are drawn may simply make a mess of things. Figure 3.16 shows the output of the sample program GeoTest (short for Geometry Test Program) with the torus rotated slightly (use the arrow keys to see this yourself).

Figure 3.16

Figure 3.16 An object with some of the far-side triangles drawn on top of near-side triangles.

One potential solution to this problem would be to sort the triangles and render the ones farther away first and then render the nearer triangles on top of them. This is called the painters algorithm and is very inefficient in computer graphics for two reasons. One is that you must write to every pixel twice wherever any geometry overlaps, and writing to memory slows things down. The second is that sorting individual triangles would be prohibitively expensive. There is a better way.

Front and Back Face Culling

One of the reasons OpenGL makes a distinction between the front and back sides of triangles is for culling. Back face culling can significantly improve performance and corrects problems like those shown in Figure 3.16. Right-click the GeoTest example program and select the Toggle Cull Backface menu option. Figure 3.17 shows the output.

Figure 3.17

Figure 3.17 Correctly rendered object with the back sides of triangles eliminated.

This is very efficient, as a whole triangle is thrown away in the primitive assembly stage of rendering, and no wasteful or inappropriate rasterization is performed. General face culling is turned on like this:

glEnable(GL_CULL_FACE);

and turned back off with the counterpart:

glDisable(GL_CULL_FACE);

Note, we did not say whether to cull the front or back of anything. That is controlled by another function, glCullFace.

void glCullFace(GLenum mode);

Valid values for the mode parameter are GL_FRONT, GL_BACK, or GL_FRONT_AND_BACK. To throw away the insides of opaque (nontransparent) geometry takes two lines of code then.

glCullFace(GL_BACK);
glEnable(GL_CULL_FACE);

Culling away the front of solid geometry is also useful in some circumstances, for example, showing a rendering of the insides of some figure. When rendering transparent objects (blending is coming up soon), we often render an object twice, once with transparency on, culling the front sides, and then again with the back sides turned off. This layers the object with the back side rendered before the front side, a requirement for rendering things transparently.

Depth Testing

Depth testing is another effective technique for hidden surface removal. The concept is simple: When a pixel is drawn, it is assigned a value (called the z value) that denotes its distance from the viewer's perspective. Later, when another pixel needs to be drawn to that screen location, the new pixel's z value is compared to that of the pixel that is already stored there. If the new pixel's z value is higher, it is closer to the viewer and thus in front of the previous pixel, so the previous pixel is obscured by the new pixel. If the new pixel's z value is lower, it must be behind the existing pixel and thus is not obscured. This maneuver is accomplished internally by a depth buffer with storage for a depth value for every pixel on the screen. Almost all the samples in this book use depth testing.

You should request a depth buffer when you set up your OpenGL window with GLUT. For example, you can request a color and a depth buffer like this:

glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);

To enable depth testing, simply call

glEnable(GL_DEPTH_TEST);

If you do not have a depth buffer, then enabling depth testing will just be ignored.

Depth testing further solves a performance issue when drawing multiple objects. Even though back face culling can eliminate triangles on the back sides of an object, what about having to separate overlapping objects? The painters algorithm, which we mentioned previously, is so named after a technique used by painters. Simply paint the background objects first and then paint the near objects over the top of them. This may use a trivial amount of paint on the canvas (much less be useful for manual painting), but for graphics hardware, this results in repeated writes to the same fragment location, each of which has a performance overhead. Too much overwrite slows down the rasterization process, and we call such renderings fill limited. The reverse of the painters algorithm actually speeds up fill performance. Draw the objects nearest you first, and then the objects farther away. The depth test eliminates pixel writes that would fall under existing pixels, saving a considerable amount of memory bandwidth.

Sorting objects relative to the viewer's position is not difficult, but what about an object that overlaps itself? Back to our DepthTest sample program; we can orient the torus (again, use the arrow keys for this) such that part of the torus overlaps a nearer portion that happened to be rendered first. Figure 3.18 shows what this looks like.

Figure 3.18

Figure 3.18 Self-overlapping objects without depth testing can be problematic.

Right-click the window and select Toggle Depth Test from the pop-up menu. This simply calls glEnable to turn on depth testing, and Figure 3.19 shows the correctly rendered object.

Figure 3.19

Figure 3.19 Correct depth testing on the torus.

Polygon Modes

Polygons (triangles) don't have to be solid. By default, polygons are drawn solid, but you can change this behavior by specifying that polygons are to be drawn as outlines or just points (only the vertices are plotted). The function glPolygonMode allows polygons to be rendered as filled solids, as outlines, or as points only. In addition, you can apply this rendering mode to both sides of the polygons or only to the front or back.

void glPolygonMode(GLenum face, GLenum mode);

Like in face culling, the face parameter can be GL_FRONT, GL_BACK, or GL_FRONT_AND_BACK. The mode parameter can be GL_FILL (the default), GL_LINE, or GL_POINT. Figure 3.20 shows the output from GeoTest when you select Set Line Mode.

Figure 3.20

Figure 3.20 The torus in wireframe mode.

This wireframe rendering is accomplished simply by calling glPolygonMode to set the fronts and backs of polygons to outline mode.

glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

Drawing the torus as a point cloud is also easily accomplished. Selecting Set Point Mode from the context menu in GeoTest causes the polygon mode to be set as follows:

glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);

Figure 3.21 shows the torus with only the vertices drawn as points. In this case, we did make the points a bit larger by calling glPointSize with an argument of 5.0.

Figure 3.21

Figure 3.21 The torus drawn as a point cloud.

Polygon Offset

While the depth buffer can have positive visual and performance effects, sometimes it just gets a little in the way, and you might need to fib to it just a little bit. This happens whenever you intentionally want to draw two pieces of geometry in the same place. This might sound odd, but consider two cases. At times, you may want to draw a large plane and then draw a smaller shape over the plane but in the same physical location. This is called decaling, and you might for example draw a star shape over a flat surface to make a design. In this case, the depth values of the star will be the same or nearly the same as the values in the depth buffer from drawing the original plane. This causes fragments to pass or fail the depth test unpredictably and can leave nasty visual artifacts from what is commonly called z-fighting.

Another case (and easier to demonstrate with our current examples), is when you want to draw solid geometry but want to highlight the edges. In the example program Primitives, presented earlier, triangles, triangle fans, and triangle strips were all drawn in green but with black lines showing the individual triangles. This is not the default behavior, and we had to take special care to make this happen. By default, simply drawing a triangle strip would leave the ring looking like that shown in Figure 3.22.

Figure 3.22

Figure 3.22 A triangle strip with no edges highlighted.

To see the triangle edges, we would need to draw the strip using glPolygonMode as shown in the previous section. The results of drawing thick black lines in wireframe mode just creates the wireframe only as shown in Figure 3.23.

Figure 3.23

Figure 3.23 A triangle strip but just the edges.

The problem is that if you draw the wireframe in the same location as the solid strips, you get the aforementioned z-fighting problem. The solution you might think is to offset the second draw in the z direction ever so slightly. This would do the trick, but you'd have to be careful to make sure you moved in the z toward the camera only and enough to offset the depth test but no so much that you'd see a gap between the geometry layers. There is a better way.

The glPolygonOffset function shown here allows you to tweak the depth values of fragments, thus offsetting the depth values but not the actual physical location in 3D space.

void glPolygonOffset(GLfloat factor, GLfloat units);

The total offset applied to fragments is given by this equation:

  • Depth Offset = (DZ X factor) + (r X units)

DZ is the change in depth values (the z) relative to the screen area of the polygon, and r is the smallest value that produces a change in depth buffer values. There are no hard and fast rules for foolproof values, and some experimentation may be required on your part. Negative values bring the z closer to you, and positive values move them farther away. For the Primitives example program, we used the value of -1.0 for both the factor and units parameters.

In addition to using glPolygonOffset to set up your offset values, you must enable polygon offset separately for filled geometry (GL_POLYGON_OFFSET_FILL), lines (GL_POLYGON_OFFSET_LINE), and points (GL_POLYGON_OFFSET_POINT). Listing 3.1 shows a function from the Primitives example program that renders a green batch of primitives and then draws a black wireframe version over it. Note that we used thicker, antialiased lines for the outlines for a better appearance. We talk more about antialiasing in the upcoming section on blending.

Listing 3.1. Function to Draw a Primitive Batch in Green, Followed by Black Wireframe Version

void DrawWireFramedBatch(GLBatch* pBatch)
    {
    // Draw the batch solid green
    shaderManager.UseStockShader(GLT_SHADER_FLAT,
                  transformPipeline.GetModelViewProjectionMatrix(), vGreen);
    pBatch->Draw();

    // Draw black outline
    glPolygonOffset(-1.0f, -1.0f);      // Shift depth values
    glEnable(GL_POLYGON_OFFSET_LINE);

    // Draw lines antialiased
    glEnable(GL_LINE_SMOOTH);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    // Draw black wireframe version of geometry
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    glLineWidth(2.5f);
    shaderManager.UseStockShader(GLT_SHADER_FLAT,
                  transformPipeline.GetModelViewProjectionMatrix(), vBlack);
    pBatch->Draw();

    // Put everything back the way we found it
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    glDisable(GL_POLYGON_OFFSET_LINE);
    glLineWidth(1.0f);
    glDisable(GL_BLEND);
    glDisable(GL_LINE_SMOOTH);
    }

Figure 3.24 shows how the two passes are finally superimposed.

Figure 3.24

Figure 3.24 Our "assembled" rendering with both solid and wireframe drawings.

Cutting It Out with Scissors

Another way to improve rendering performance is to update only the portion of the screen that has changed. You may also need to restrict OpenGL rendering to a smaller rectangular region inside the window. OpenGL allows you to specify a scissor rectangle within your window where rendering can take place. By default, the scissor rectangle is the size of the window, and no scissor test takes place. You turn on the scissor test with the ubiquitous glEnable function:

glEnable(GL_SCISSOR_TEST);

You can, of course, turn off the scissor test again with the corresponding glDisable function call. The rectangle within the window where rendering is performed, called the scissor box, is specified in window coordinates (pixels) with the following function:

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

The x and y parameters specify the lower-left corner of the scissor box, with width and height being the corresponding dimensions of the scissor box. Listing 3.2 shows the rendering code for the sample program Scissor. This program clears the color buffer three times, each time with a smaller scissor box specified before the clear. The result is a set of overlapping colored rectangles, as shown in Figure 3.25.

Listing 3.2. Using the Scissor Box to Render a Series of Rectangles

void RenderScene(void)
        {
        // Clear blue window
        glClearColor(0.0f, 0.0f, 1.0f, 0.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        // Now set scissor to smaller red sub region
        glClearColor(1.0f, 0.0f, 0.0f, 0.0f);
        glScissor(100, 100, 600, 400);
        glEnable(GL_SCISSOR_TEST);
        glClear(GL_COLOR_BUFFER_BIT);

        // Finally, an even smaller green rectangle
        glClearColor(0.0f, 1.0f, 0.0f, 0.0f);
        glScissor(200, 200, 400, 200);
        glClear(GL_COLOR_BUFFER_BIT);

        // Turn scissor back off for next render
        glDisable(GL_SCISSOR_TEST);

        glutSwapBuffers();
        }
Figure 3.25

Figure 3.25 Shrinking scissor boxes.

  • + Share This
  • 🔖 Save To Your Account