In our first demo we rendered 3 quads in Open GL ES 2.0 using the function glDrawArrays. In this demo we will use a different function, glDrawElements, to draw a single quad to the screen. We will also be changing some of the settings from the first demo but don't worry as we will review all the necessary code.
In the first demo we reviewed the code in the GraphicsUtils.m file, AppDelegate.m file, basic.fsh file and basic.vsh file. These files remain unchanged for this demo so we will only be reviewing the DemoViewController.m file. Additionally, the DemoViewController init function remains the same as the previous demo so we will not be reviewing that function. If you need to review any of the previous files or function code you can re-read the previous demo. Lets start our code review with the initGL function.
-(void) initGL {
NSString* vSource;
NSString* fSource;
vSource = [GraphicsUtils readShaderFile:@"basic.vsh"];
fSource = [GraphicsUtils readShaderFile:@"basic.fsh"];
const uint progId = [GraphicsUtils createProgramVertexSource:vSource fragmentSource:fSource];
[GraphicsUtils activateProgram:progId];
//set clear color
glClearColor(.25f, .25f, .25f, 1);
//read attribute locations and enable
maColor = glGetAttribLocation(progId, "aColor");
glEnableVertexAttribArray(maColor);
maVertices = glGetAttribLocation(progId, "aVertices");
glEnableVertexAttribArray(maVertices);
const float halfWidth = mWidth / 2.0f;
const float halfHeight = mHeight / 2.0f;
glViewport(0, 0, mWidth, mHeight);
//set matrices and pass to shader
GLKMatrix4 projMatrix = GLKMatrix4MakeOrtho(-halfWidth, halfWidth, -halfHeight, halfHeight, -1, 1);
int uProjectionMatrix = glGetUniformLocation(progId, "uProjectionMatrix");
glUniformMatrix4fv(uProjectionMatrix, 1, false, &projMatrix.m[0]);
}
All the code up til the function call glEnableVertexAttribArray(maVertices); should be the same as the first demo. So, we will start our review after that code. After that code we are defining the half width and half height of the surface. These values are used in the call to GLKMatrix4MakeOrtho. Next we have our call to glViewport which is the same as the previous demo and will probably be the same in most of the demos. Next we define our projection matrix, projMatrix, and set it using the GLKMatrix4MakeOrtho function. This function call is passed different values than the previous demo. Here we are creating a projection matrix that has a center of 0, 0 instead of having the 0, 0 at the bottom left like the previous demo. There is no right or wrong when defining the projection matrix but the data passed for vertices may be different so it's something to be kept in mind. Finally, the projection matrix is passed to the GPU as a uniform.
Next up we will review the glkView:drawInRect: function. This will be the last function we will review. As you can see we will be reviewing a lot less code when compared to the previous demo. The main reason for this is that most of the code is the same and I want to keep the demos to a minimum as to not overwhelm you with a ton of new information.
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
//clear screen
glClear(GL_COLOR_BUFFER_BIT);
//set color buffer and pass to shader
float colors[] = {
1.0f, 0.0f, 1.0f, 0.0f
, 1.0f, 0.0f, 1.0f, 0.0f
, 1.0f, 0.0f, 1.0f, 0.0f
, 1.0f, 0.0f, 1.0f, 0.0f
};
glVertexAttribPointer(maColor, 4, GL_FLOAT, false, 0, &colors[0]);
//measure out vertices and set for quad
const float SIZE = 50;
const float leftX = -SIZE;
const float rightX = SIZE;
const float topY = SIZE;
const float bottomY = -SIZE;
//triangles
const float vertices[] = {
leftX, bottomY
, rightX, bottomY
, leftX, topY
, rightX, topY
};
glVertexAttribPointer(maVertices, 2, GL_FLOAT, false, 0, &vertices[0]);
//indices
const ushort indices[] = {
0, 1, 2, 1, 2, 3
};
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, &indices[0]);
}
The first function we call is glClearColor which will clear the screen to a value that was previously set with glClearColor. Then we define the vertex colors for the primative that we are going to render and pass it to the GPU. There are multiple steps in this process and in our first post we review what each step does. The most important piece is passing the data to the GPU in the function call glVertexAttribPointer. Next we define out values that we will use for our primative vertices, store those values and pass them to the GPU with, again, glVertexAttribPointer. These are essentially the same steps that we used in the first demo so nothing should be a surprise here. The next step is where things start to become different. We define the indices that we will use when drawing with glDrawElements.
I will take some time here to explain the function glDrawElements and how it differs from glDrawArrays. With glDrawArrays we draw our primatives with the values passed to the GPU in the order that they are passed. This means that if we specify 0 as the second parameter to glDrawArrays and 6 as the third parameter, we will draw 6 vertices, starting with vertex 0. With glDrawElements, we still pass the number of vertices to draw (parameter 2) but we also pass a list of indices to draw. To give a comparison, when we drew a quad in the first demo with the mode GL_TRIANGLES we needed to pass 6 vertices worth of data to glVertexAttribPointer. In that set of data, the values for vertices 1 and 2 was duplicated for vertices 3 and 4. Using glDrawElements prevents this duplication of vertex data because we specify the vertex index, which is repeated when we are defining the indices (0, 1, 2, 1, 2, 3). Hopefully this brief explanation will help in understanding the difference between glDrawArrays and glDrawElements. If you have any questions please feel free to post and I will get back to you.
Another difference in this example is the use of the ushort (unsigned short) values for the indices. Previously we used only float values but the indices should be in whole numbers and short's use the least amount of byte space (2 bytes vs 4 for ints) and provide a high enough range (up to 65,535) for this example. We pass the unsigned short data to the GPU in a similar fashion as float data, but one thing to note is the parameters for glDrawElements and the values we pass for them. The first parameter specifies the drawing mode which is the same as for glDrawArrays. The second parameter specifies the number of vertices to draw which, again, is the same as glDrawArrays. The third parameter is the type of data being passed for the fourth parameter. This value needs to be GL_UNSIGNED_SHORT since we are using unsigned short values. The final parameter is a pointer to the indices that are used.
Hopefully this post has been helpful in learning OpenGL ES 2.0 and it can be used as a reference as we delve further into some other features of OpenGL ES 2.0 in subsequent demos. You can download the demo from github.