Part Fourteen: Creating the Cube

In the last chapter, we managed to get a texture onto our square. Now it’s time to add a little depth to our object and turn it into a cube.

We’re going to start by conceptualizing the cube that we want to draw. First, think back to the flat square that was defined in the vanilla template.

 

 

Using the letters as vertices, the square was defined as ABCD, and OpenGL used that in GL_TRIANGLE_STRIP mode to make two triangles, ABC and BCD.

Well, conceptually, it made ABC and CBD. What’s the difference, you ask? In OpenGL, it’s a big difference, and it’s called “winding”.

When we create a shape in OpenGL space, for example a triangle, one face of that triangle is the front, and the other face is the back. When we apply textures to these triangles, OpenGL will keep track of the front face and the back face. The way that OpenGL determines which is the front and which is the back is by looking at the direction in which that triangle was drawn.

Triangle ABC is wound counter-clockwise, but triangle ACB is wound clockwise. Take a minute to fully understand this concept by looking at the square diagram above and mentally drawing the differently-wound triangles in that square.

To illustrate the point, let’s image that you’re walking along one day and find an amazing clock lying on the ground. The clock face is completely transparent, and the hands are inside of the transparent face, visible from both sides. There are no numbers, and it’s impossible to tell just by looking at the clock which side is the front. From one side it looks like it’s reading 3:00, but from the other it looks like 9:00. Which side is the front?

For the sake of argument, let’s also say that you can’t tell the time from just looking around. You must use the clock. What do you do?

You wait until the minute hand moves. Did it go clockwise or counter-clockwise? If it went clockwise, you’re looking at the front. If it went counter-clockwise, you’re looking at the back.

OpenGL does the same thing. It can’t tell what direction an arbitrary triangle floating in space is facing, so it looks at the way it was drawn. However, OpenGL considers the counter-clockwise side to the front and the clockwise side to be the back.

When I drew my square ABCD as the two triangles ABC and CBD, I got two counter-clockwise triangles, and that became the front of my object.

 

 

If we want to draw a cube using GL_TRIANGLE_STRIP mode, there are a couple of different ways to go. The first way is to look at each cube face individually, make sure we’re drawing the triangles counter-clockwise so the texture maps correctly, and draw each face one by one.

 

 

A cube has eight corners, so we’re going to have eight vertices to deal with. We’ll be using each one more than once as we construct the six faces, but there will only be eight unique vertices in total.

We’re going to assign OpenGL coordinates to each vertex, and make our cube take up the entire OpenGL space. We’ll use matrices to scale it down to something reasonable. Given that, our vertices would be the following.

A = (-1, -1, 1)		B = (1, -1, 1)		C = (-1, 1, 1)		D = (1,1,1)
E = (-1, -1, -1)	F = (1, -1, -1)		G = (-1, 1, -1) 	H = (1, 1, -1)

Now, let’s pull each of the six faces off and look at them as if we were standing in front of each one from the outside of the cube.

 

 

We already have the first face, ABCD. If we use that same drawing pattern, the right face would be BFDH, the back would be FEHG, and so on. The triangle strip would be ABCDBFDHFEHGEAGCEFABCDGH.

In OpenGL coordinates, our cube vertices array would look like this.

    static const GLfloat cubeVerticesStrip[] = {
        // Front face
        -1,-1,1, 1,-1,1, -1,1,1, 1,1,1,
        // Right face
        1,-1,1, 1,-1,-1, 1,1,1, 1,1,-1,
        // Back face
        1,-1,-1, -1,-1,-1, 1,1,-1, -1,1,-1,
        // Left face
        -1,-1,-1, -1,-1,1, -1,1,-1, -1,1,1,
        // Bottom face
        -1,-1,-1, 1,-1,-1, -1,-1,1, 1,-1,1,
        // Top face
        -1,1,1, 1,1,1, -1,1,-1, 1,1,-1
    };

And since we’re following the same draw pattern on each face, out texture coordinates would look like this.

    static const GLfloat cubeTexCoordsStrip[] = {
        // Front face
        0,0, 1,0, 0,1, 1,1,
        // Right face
        0,0, 1,0, 0,1, 1,1,
        // Back face
        0,0, 1,0, 0,1, 1,1,
        // Left face
        0,0, 1,0, 0,1, 1,1,
        // Bottom face
        0,0, 1,0, 0,1, 1,1,
        // Top face
        0,0, 1,0, 0,1, 1,1
    };

The code to implement this would look like this.

    glVertexAttribPointer(ATTRIB_VERTEX, 3, GL_FLOAT, 0, 0, cubeVerticesStrip);
    glEnableVertexAttribArray(ATTRIB_VERTEX);
    glVertexAttribPointer(ATTRIB_TEXTURE_COORD, 2, GL_FLOAT, 0, 0, cubeTexCoordsStrip);
    glEnableVertexAttribArray(ATTRIB_TEXTURE_COORD);
           
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    glDrawArrays(GL_TRIANGLE_STRIP, 4, 4);
    glDrawArrays(GL_TRIANGLE_STRIP, 8, 4);
    glDrawArrays(GL_TRIANGLE_STRIP, 12, 4);
    glDrawArrays(GL_TRIANGLE_STRIP, 16, 4);
    glDrawArrays(GL_TRIANGLE_STRIP, 20, 4);

    [(EAGLView *)self.view presentFramebuffer];

Since we’ve defined each face independently, we need to call glDrawArrays() once for each face, specifying the appropriate offset into the vertex array for each face.

A program using this logic would create a textured cube that looked pretty good (after scaling).

 

 

I know what you’re thinking. “But what if I had one hundred cubes? I don’t want to call glDrawArrays() six hundred times!

I wonder what would happen if we just tried calling it once with this vertex array?

    glVertexAttribPointer(ATTRIB_VERTEX, 3, GL_FLOAT, 0, 0, cubeVerticesStrip);
    glEnableVertexAttribArray(ATTRIB_VERTEX);
    glVertexAttribPointer(ATTRIB_TEXTURE_COORD, 2, GL_FLOAT, 0, 0, cubeTexCoordsStrip);
    glEnableVertexAttribArray(ATTRIB_TEXTURE_COORD);
           
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 24);

    [(EAGLView *)self.view presentFramebuffer];

This code would produce this.

 

 

Well, at least it’s still a cube, of sorts.

The key to making this work is to construct our triangle strip properly, not by only looking at one face at a time. Let’s look at those individual cube faces again.

 

 

Let’s keep the first face (the front face) the same, ABCD. After drawing the front face, we stopped at vertex D. Now look at the next face we’re going to draw, the right face. There’s a vertex D on it, so we should keep drawing from there. The front and right faces together as a triangle strip would be ABCDBHF. OpenGL would interpret this as four triangles, ABC, CBD, DBH, and HBF.

That looks good, we have four triangles making up two faces, all wound counter-clockwise. There is, however, one small problem.

In order to properly apply textures to these faces, we need four vertices to apply the four corners of our texture. We have four on the front face, but only three on the right face. In addition, I was planning to draw the front, right, back, left, bottom, and top faces in that order, but what will I do when I finish the bottom face? The bottom face doesn’t share any vertices with the top face.

I could be clever and draw the faces in a different order, guaranteeing that I’ll always have a shared vertex, but I don’t have to. There’s an easy way to solve both of my problems at once: degenerate triangles.

Degenerate triangles are triangles that have no area. When OpenGL runs into a degenerate triangle in a triangle strip, it simply ignores it and goes to the next vertex. OpenGL wont start drawing again until it finds a triangle with area.

For our first problem, guaranteeing four texture coordinates per face, we can just throw in a degenerate triangle. The easiest way to do that is to use the same vertex twice between faces, like this.

Instead of ABCDBHF, let’s use ABCDDBHF. OpenGL would create the following triangles: ABC, CBD, CDD, DDB, DBH, and HBF. If you remove the degenerate triangles, CDD and DDB, you’re left with ABC, CBD, DBH, and BHF, just like we had with only seven vertices.

The cube will render the same way, and we’ll have enough vertices for all of our texture coordinates.

If we fix our vertex array to take advantage of degenerate triangles, we get this.

    static const GLfloat cubeVerticesStrip[] = {
        // Front face
        -1,-1,1, 1,-1,1, -1,1,1, 1,1,1,
        // Right face
        1,1,1, 1,-1,1, 1,1,-1, 1,-1,-1,
        // Back face
        1,-1,-1, -1,-1,-1, 1,1,-1, -1,1,-1,
        // Left face
        -1,1,-1, -1,-1,-1, -1,1,1, -1,-1,1,
        // Bottom face
        -1,-1,1, -1,-1,-1, 1,-1,1, 1,-1,-1,
       
        // move to top
        1,-1,-1, -1,1,1,

        // Top Face
        -1,1,1, 1,1,1, -1,1,-1, 1,1,-1
    };

If you look at the first vertex of each face after the first one, you can see that it’s a duplicate of the last vertex of the previous face. This not only gives us that fourth vertex we need for a texture coordinate, it creates a degenerate triangle that will be ignored by OpenGL.

Also, after the bottom face, we duplicate the last vertex of the bottom face and then duplicate the first vertex of the top face. This effectively moves us to the top of the cube from the bottom without drawing triangles between the two.

In order for our textures to be drawn correctly, we had to move the texture coordinates around to match whatever vertex was specified in the vertex array.

    static const GLfloat cubeTexCoordsStrip[] = {
        // Front face
        0,0, 1,0, 0,1, 1,1,
        // Right face
        0,1, 0,0, 1,1, 1,0,
        // Back face
        0,0, 1,0, 0,1, 1,1,
        // Left face
        0,1, 0,0, 1,1, 1,0,
        // Bottom face
        0,1, 0,0, 1,1, 1,0,
       
        1,0, 0,0,
       
        // Top face
        0,0, 1,0, 0,1, 1,1
    };

The code that uses these new arrays is only a little different.

    glVertexAttribPointer(ATTRIB_VERTEX, 3, GL_FLOAT, 0, 0, cubeVerticesStrip);
    glEnableVertexAttribArray(ATTRIB_VERTEX);
    glVertexAttribPointer(ATTRIB_TEXTURE_COORD, 2, GL_FLOAT, 0, 0, cubeTexCoordsStrip);
    glEnableVertexAttribArray(ATTRIB_TEXTURE_COORD);
       
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 26);

    [(EAGLView *)self.view presentFramebuffer];

Since we added a few degenerate triangles to get from the bottom face to the top face, we have to make sure that the vertex size in the glDrawArrays() function matches.

Running this code we get a nice looking cube with only one call to the glDrawArrays() function.

 

 

With some clever placement of degenerate triangles, we could even add more cubes in different locations around the screen with the same glDrawArrays() call.

So how can we take advantage of this in our project? In the EDCubeDemoViewController.m file, make the following changes to the drawFrame method.

- (void)drawFrame
{
    [(EAGLView *)self.view setFramebuffer];
   
    /* ED: Removed
    static const GLfloat squareVertices[] = {
        -0.5f, -0.5f,
        0.5f, -0.5f,
        -0.5f, 0.5f,
        0.5f, 0.5f,
    };
     */


    // ED: Added
    static const GLfloat cubeVertices[] = {
        // Front face
        -1,-1,1, 1,-1,1, -1,1,1, 1,1,1,
        // Right face
        1,1,1, 1,-1,1, 1,1,-1, 1,-1,-1,
        // Back face
        1,-1,-1, -1,-1,-1, 1,1,-1, -1,1,-1,
        // Left face
        -1,1,-1, -1,-1,-1, -1,1,1, -1,-1,1,
        // Bottom face
        -1,-1,1, -1,-1,-1, 1,-1,1, 1,-1,-1,
       
        // move to top
        1,-1,-1, -1,1,1,
       
        // Top Face
        -1,1,1, 1,1,1, -1,1,-1, 1,1,-1
    };

    /* ED: Removed
    static const GLfloat squareTexCoords[] = {
        0, 0,
        1, 0,
        0, 1,
        1, 1
    };
     */

   
    // ED: Added
    static const GLfloat cubeTexCoords[] = {
        // Front face
        0,0, 1,0, 0,1, 1,1,
        // Right face
        0,1, 0,0, 1,1, 1,0,
        // Back face
        0,0, 1,0, 0,1, 1,1,
        // Left face
        0,1, 0,0, 1,1, 1,0,
        // Bottom face
        0,1, 0,0, 1,1, 1,0,
       
        1,0, 0,0,
       
        // Top face
        0,0, 1,0, 0,1, 1,1
    };
   
    static float transY = 0.0f;
    static float transX = 0.0f;
   
    GLfloat mvpMatrix[16];
   
    glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
   
    if ([context API] == kEAGLRenderingAPIOpenGLES2) {
        glEnable(GL_TEXTURE_2D);
       
        glBindTexture(GL_TEXTURE_2D, textureName);
       
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        // Use shader program.
        glUseProgram(program);
       
        [EDMatrixTools applyIdentity:mvpMatrix];
       
        //[EDMatrixTools applyScale:mvpMatrix x:0.5f y:0.5f z:1.0f]; // ED: Removed
        [EDMatrixTools applyScale:mvpMatrix x:0.25f y:0.25f z:0.25f]; // ED: Added
        //[EDMatrixTools applyRotation:mvpMatrix x:0 y:0 z:transY]; // ED: Removed
        [EDMatrixTools applyRotation:mvpMatrix x:(transX / 4) y:(transX / 4) z:transY];
        [EDMatrixTools applyTranslation:mvpMatrix x:(cosf(transX) / 2.0f) y:(sinf(transY) / 2.0f) z:0.0f];
       
        [EDMatrixTools applyProjection:mvpMatrix aspect:1.5];
               
        // Update uniform value.
        glUniformMatrix4fv(uniforms[UNIFORM_MVP_MATRIX], 1, 0, mvpMatrix);
        glUniform1i(uniforms[UNIFORM_TEXTURE], 0);
        transY += 0.075f;  
        transX += 0.075f;
       
        // Update attribute values.
        //glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, squareVertices); // ED: Removed
        glVertexAttribPointer(ATTRIB_VERTEX, 3, GL_FLOAT, 0, 0, cubeVertices); // ED: Added
        glEnableVertexAttribArray(ATTRIB_VERTEX);
        //glVertexAttribPointer(ATTRIB_TEXTURE_COORD, 2, GL_FLOAT, 0, 0, squareTexCoords); // ED: Removed
        glVertexAttribPointer(ATTRIB_TEXTURE_COORD, 2, GL_FLOAT, 0, 0, cubeTexCoords); // ED: Added
        glEnableVertexAttribArray(ATTRIB_TEXTURE_COORD);
       
        // Validate program before drawing. This is a good check, but only really necessary in a debug build.
        // DEBUG macro must be defined in your debug configurations if that's not already the case.
#if defined(DEBUG)
        if (![self validateProgram:program]) {
            NSLog(@"Failed to validate program: %d", program);
            return;
        }
#endif
    }
   
    //glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // ED: Removed
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 26); // ED: Added
   
    [(EAGLView *)self.view presentFramebuffer];
}

Let’s look at the changes one at a time.

    /* ED: Removed
    static const GLfloat squareVertices[] = {
        -0.5f, -0.5f,
        0.5f, -0.5f,
        -0.5f, 0.5f,
        0.5f, 0.5f,
    };
     */


    // ED: Added
    static const GLfloat cubeVertices[] = {
        // Front face
        -1,-1,1, 1,-1,1, -1,1,1, 1,1,1,
        // Right face
        1,1,1, 1,-1,1, 1,1,-1, 1,-1,-1,
        // Back face
        1,-1,-1, -1,-1,-1, 1,1,-1, -1,1,-1,
        // Left face
        -1,1,-1, -1,-1,-1, -1,1,1, -1,-1,1,
        // Bottom face
        -1,-1,1, -1,-1,-1, 1,-1,1, 1,-1,-1,
       
        // move to top
        1,-1,-1, -1,1,1,
       
        // Top Face
        -1,1,1, 1,1,1, -1,1,-1, 1,1,-1
    };

We got rid of the old vertex array for our square and added a new vertex array for our cube.

    /* ED: Removed
    static const GLfloat squareTexCoords[] = {
        0, 0,
        1, 0,
        0, 1,
        1, 1
    };
     */

   
    // ED: Added
    static const GLfloat cubeTexCoords[] = {
        // Front face
        0,0, 1,0, 0,1, 1,1,
        // Right face
        0,1, 0,0, 1,1, 1,0,
        // Back face
        0,0, 1,0, 0,1, 1,1,
        // Left face
        0,1, 0,0, 1,1, 1,0,
        // Bottom face
        0,1, 0,0, 1,1, 1,0,
       
        1,0, 0,0,
       
        // Top face
        0,0, 1,0, 0,1, 1,1
    };

Likewise, we got rid of our old texture coordinates and replaced them with our new ones. The number of texture coordinates in your texture coordinate array will usually match the number of vertices in your vertex array.

        //[EDMatrixTools applyScale:mvpMatrix x:0.5f y:0.5f z:1.0f]; // ED: Removed
        [EDMatrixTools applyScale:mvpMatrix x:0.25f y:0.25f z:0.25f]; // ED: Added

We’ve defined our cube as being larger than the square was, so we’re going to scale it down a bit more. We’re also going to scale along the Z axis now, since our new object has depth.

        //[EDMatrixTools applyRotation:mvpMatrix x:0 y:0 z:transY]; // ED: Removed
        [EDMatrixTools applyRotation:mvpMatrix x:(transX / 4) y:(transX / 4) z:transY];

Here we’ll add rotation along the X and Y axes as well as Z, so it will rotate as it spins. Warning: this may make you as nauseous as it made me – we’ll fix it in the next chapter.

        //glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, squareVertices); // ED: Removed
        glVertexAttribPointer(ATTRIB_VERTEX, 3, GL_FLOAT, 0, 0, cubeVertices); // ED: Added

Not only do we start using the new cubeVertices array, but we change the number of values per vertex to 3 from 2, since we’re now specifying a Z coordinate in addition to X and Y.

        //glVertexAttribPointer(ATTRIB_TEXTURE_COORD, 2, GL_FLOAT, 0, 0, squareTexCoords); // ED: Removed
        glVertexAttribPointer(ATTRIB_TEXTURE_COORD, 2, GL_FLOAT, 0, 0, cubeTexCoords); // ED: Added

We need to use the new cubeTexCoords array for the texture coordinates.

    //glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // ED: Removed
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 26); // ED: Added

And finally, we update the glDrawArrays() function call to use all 26 vertices instead of just four.

After these changes, we should have a rotating cube where our square used to be.

 

 

That’s odd, what happened?

We’ll find out what’s wrong and how to fix it in the next chapter, Drawing and Culling.

Part Thirteen | Index | Part Fifteen