Part Fifteen: Drawing and Culling

In the last chapter, we converted our square to a cube, but when it rendered on the screen it looked funky. Not only that, but it was flying around the screen making everyone nauseous. Let’s change a few lines of code and make this cube easier to inspect.

        //[EDMatrixTools applyScale:mvpMatrix x:0.25f y:0.25f z:0.25f]; // ED: Removed
        [EDMatrixTools applyScale:mvpMatrix x:0.50f y:0.50f z:0.50f]; // ED: Added
        //[EDMatrixTools applyRotation:mvpMatrix x:(transX / 4) y:(transX / 4) z:transY]; // ED: Removed
        [EDMatrixTools applyRotation:mvpMatrix x:(transX / 6) y:(transX / 6) z:0]; // ED: Added
        //[EDMatrixTools applyTranslation:mvpMatrix x:(cosf(transX) / 2.0f) y:(sinf(transY) / 2.0f) z:0.0f]; // ED: Removed

First, we’re going to make the cube a little larger, since the next two changes will make it rotate more slowly and stay in the center of the screen.

If we build and run this code, we get this.

 

 

Strange. Here’s what’s happening: when OpenGL draws our triangle strip, it draws it in the order it appears in the array. The way our program is currently written, OpenGL doesn’t do any depth checking, so while the cube may look 3D-ish to us, OpenGL is just drawing it out in two dimensions. In our vertex array, we draw the sides in the following order: front, right, back, left, bottom, and then top. Each side has a texture visible from the front and the back (the texture from the back is backwards). If a face is drawn with a visible side, even if it’s the back of the face, it will be drawn over whatever was there before.

One solution to this is culling. You can instruct OpenGL not to draw the front, back, or both faces of an object. If we cull the back faces of our cube, we will no longer have the backs of the cube faces (that we wouldn’t see in real life anyway) overlaying the fronts of the faces that we should be seeing.

The code to specify and enable face culling looks like this.

    glCullFace(GL_BACK); // ED: Added
    glEnable(GL_CULL_FACE); // ED: Added

Once we’ve made that change, the new drawFrame method in the EDCubeDemoViewController.m file looks like this.

- (void)drawFrame
{
    [(EAGLView *)self.view setFramebuffer];
   
    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
    };

    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];
   
    glCullFace(GL_BACK); // ED: Added
    glEnable(GL_CULL_FACE); // ED: Added
   
    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.25f y:0.25f z:0.25f]; // ED: Removed
        [EDMatrixTools applyScale:mvpMatrix x:0.50f y:0.50f z:0.50f]; // ED: Added
        //[EDMatrixTools applyRotation:mvpMatrix x:(transX / 4) y:(transX / 4) z:transY]; // ED: Removed
        [EDMatrixTools applyRotation:mvpMatrix x:(transX / 6) y:(transX / 6) z:0]; // ED: Added
        //[EDMatrixTools applyTranslation:mvpMatrix x:(cosf(transX) / 2.0f) y:(sinf(transY) / 2.0f) z:0.0f]; // ED: Removed
       
        [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, 3, GL_FLOAT, 0, 0, cubeVertices);
        glEnableVertexAttribArray(ATTRIB_VERTEX);
        glVertexAttribPointer(ATTRIB_TEXTURE_COORD, 2, GL_FLOAT, 0, 0, cubeTexCoords);
        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, 26);
   
    [(EAGLView *)self.view presentFramebuffer];
}

Let’s see what happens if we add this code and build and run our program again.

 

 

Much better, but there’s still a problem. Back-face culling is great, and is recommended for performance purposes, but there’s a caveat. Imagine if this wasn’t a closed cube, but rather an object that had visible back sides. If you culled the back faces, you’d see nothing when the object rotated around.

If the back faces of your objects are never visible, as is the case with this cube, back-face culling might solve all of your problems. If back-face culling won’t work for your model(s), there’s another solution.

OpenGL can be made to determine depth, and not render things that should be hidden when other things appear on top of them. In order to figure out these depth-related situations, however, OpenGL needs a place to ‘think’. We can give it that place in the form of the depth renderbuffer.

We’ll take a closer look at the depth renderbuffer and the associated changes to the projection matrix in the next chapter.

Part Fourteen | Index | Part Sixteen