Hello Triangle: An OpenGL ES 2.0 Example [con't]
Compiling and Loading the Shaders
Now that we have the shader source code defined, we can go about loading the shaders to OpenGL ES. The LoadShader
function in the Hello Triangle example is responsible for loading the shader source code, compiling it, and checking to make sure that there were no errors. It returns a shader object, which is an OpenGL ES 2.0 object that can later be used for attachment to a program object (these two objects are detailed in Chapter 4, "Shaders and Programs").
Let's take a look at how the LoadShader
function works. The shader object is first created using glCreateShader
, which creates a new shader object of the type specified.
The shader source code itself is loaded to the shader object using glShaderSource
. The shader is then compiled using the glCompileShader
function.
If the shader compiles successfully, a new shader object is returned that will be attached to the program later. The details of these shader object functions are covered in the first sections of Chapter 4.
Creating a Program Object and Linking the Shaders
Once the application has created a shader object for the vertex and fragment shader, it needs to create a program object. Conceptually, the program object can be thought of as the final linked program. Once each shader is compiled into a shader object, they must be attached to a program object and linked together before drawing.
The process of creating program objects and linking is fully described in Chapter 4. For now, we provide a brief overview of the process. The first step is to create the program object and attach the vertex shader and fragment shader to it.
Once the two shaders have been attached, the next step the sample application does is to set the location for the vertex shader attribute vPosition:
In Chapter 6, "Vertex Attributes, Vertex Arrays, and Buffer Objects," we go into more detail on binding attributes. For now, note that the call to glBindAttribLocation
binds the vPosition
attribute declared in the vertex shader to location 0. Later, when we specify the vertex data, this location is used to specify the position.
Finally, we are ready to link the program and check for errors:
After all of these steps, we have finally compiled the shaders, checked for compile errors, created the program object, attached the shaders, linked the program, and checked for link errors. After successful linking of the program object, we can now finally use the program object for rendering! To use the program object for rendering, we bind it using glUseProgram
.
After calling glUseProgram
with the program object handle, all subsequent rendering will occur using the vertex and fragment shaders attached to the program object.
Setting the Viewport and Clearing the Color Buffer
Now that we have created a rendering surface with EGL and initialized and loaded shaders, we are ready to actually draw something. The Draw callback function draws the frame. The first command that we execute in Draw is glViewport, which informs OpenGL ES of the origin, width, and height of the 2D rendering surface that will be drawn to. In OpenGL ES, the viewport defines the 2D rectangle in which all OpenGL ES rendering operations will ultimately be displayed.
The viewport is defined by an origin (x, y) and a width and height. We cover glViewport
in more detail in Chapter 7, "Primitive Assembly and Rasterization," when we discuss coordinate systems and clipping.
After setting the viewport, the next step is to clear the screen. In OpenGL ES, there are multiple types of buffers that are involved in drawing: color, depth, and stencil. We cover these buffers in more detail in Chapter 11, "Fragment Operations." In the Hello Triangle example, only the color buffer is drawn to. At the beginning of each frame, we clear the color buffer using the glClear
function.
The buffer will be cleared to the color specified with glClearColor
. In the example program at the end of Init
, the clear color was set to (0.0, 0.0, 0.0, 1.0) so the screen is cleared to black. The clear color should be set by the application prior to calling glClear
on the color buffer.
Loading the Geometry and Drawing a Primitive
Now that we have the color buffer cleared, viewport set, and program object loaded, we need to specify the geometry for the triangle. The vertices for the triangle are specified with three (x, y, z) coordinates in the vVertices array.
The vertex positions need to be loaded to the GL and connected to the vPosition
attribute declared in the vertex shader. As you will remember, earlier we bound the vPosition
variable to attribute location 0. Each attribute in the vertex shader has a location that is uniquely identified by an unsigned integer value. To load the data into vertex attribute 0, we call the glVertexAttribPointer
function. In Chapter 6, we cover how to load vertex attributes and use vertex arrays in full.
The final step to drawing the triangle is to actually tell OpenGL ES to draw the primitive. That is done in this example using the function glDrawArrays
. This function draws a primitive such as a triangle, line, or strip. We get into primitives in much more detail in Chapter 7.
Displaying the Back Buffer
We have finally gotten to the point where our triangle has been drawn into the framebuffer. There is one final detail we must address: how to actually display the framebuffer on the screen. Before we get into that, let's back up a little bit and discuss the concept of double buffering.
The framebuffer that is visible on the screen is represented by a two-dimensional array of pixel data. One possible way one could think about displaying images on the screen is to simply update the pixel data in the visible framebuffer as we draw. However, there is a significant issue with updating pixels directly on the displayable buffer. That is, in a typical display system, the physical screen is updated from framebuffer memory at a fixed rate. If one were to draw directly into the framebuffer, the user could see artifacts as partial updates to the framebuffer where displayed.
To address this problem, a system known as double buffering is used. In this scheme, there are two buffers: a front buffer and back buffer. All rendering occurs to the back buffer, which is located in an area of memory that is not visible to the screen. When all rendering is complete, this buffer is "swapped" with the front buffer (or visible buffer). The front buffer then becomes the back buffer for the next frame.
Using this technique, we do not display a visible surface until all rendering is complete for a frame. The way this is all controlled in an OpenGL ES application is through EGL. This is done using an EGL function called eglSwapBuffers:
This function informs EGL to swap the front buffer and back buffer. The parameters sent to eglSwapBuffers
are the EGL display and surface. These two parameters represent the physical display and the rendering surface, respectively. In the next chapter, we explain eglSwapBuffers
in more detail and further clarify the concepts of surface, context, and buffer management. For now, suffice to say that after swapping buffers we now finally have our triangle on screen!
This chapter is an excerpt from the book, OpenGL® ES 2.0 Programming Guide by Aaftab Munshi, Dan Ginsburg, Dave Shreiner, published by Addison-Wesley Professional, May 2008, ISBN 0321502795, Copyright 2008 Pearson Education, Inc.
Original: July 24, 2008