# Basic Rendering in OpenGL

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.

#### 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.

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.

#### 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.

### 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.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.

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.

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.

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.

#### 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.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.

### 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).

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.

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.

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.

#### 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.

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.

### 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.

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.

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.

### 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();
}```

### InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

## Overview

Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

## Collection and Use of Information

To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

### Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

### Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

### Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

### Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

### Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

### Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

### Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

## Other Collection and Use of Information

### Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

### Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

### Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

### Do Not Track

This site currently does not respond to Do Not Track signals.

## Security

Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

## Children

This site is not directed to children under the age of 13.

## Marketing

Pearson may send or direct marketing communications to users, provided that

• Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
• Such marketing is consistent with applicable law and Pearson's legal obligations.
• Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
• Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

## Correcting/Updating Personal Information

If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

## Choice/Opt-out

Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

## Sale of Personal Information

Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

## Supplemental Privacy Statement for California Residents

California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

## Sharing and Disclosure

Pearson may disclose personal information, as follows:

• As required by law.
• With the consent of the individual (or their parent, if the individual is a minor)
• In response to a subpoena, court order or legal process, to the extent permitted or required by law
• To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
• In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
• To investigate or address actual or suspected fraud or other illegal activities
• To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
• To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
• To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

## Links

This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

## Requests and Contact

Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

## Changes to this Privacy Notice

We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020