Thursday, January 24, 2013

Demo 2 - Basic Drawing Part 2 (iOS)

Hello and welcome to the second demo in a series of OpenGL ES 2.0 Demos. In the first demo we reviewed basic drawing in OpenGL ES 2.0. This demo is also about basic drawing but it will cover a different approach to the drawing. If you are not familiar with our helper classes or the basic setup for OpenGL ES 2.0 on iOS please review the previous demo so you can familiarize yourself. Although the first demo is very basic it is essential to know the foundation of setting up the projects as everything we do will be built on top of that foundation.

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.

Monday, January 21, 2013

Demo 2 - Basic Drawing Part 2 (android)

Hello and welcome to the second demo in a series of OpenGL ES 2.0 Demos. In the first demo we reviewed basic drawing in OpenGL ES 2.0. This demo is also about basic drawing but it will cover a different approach to the drawing. If you are not familiar with our helper classes or the basic setup for OpenGL ES 2.0 on android please review the previous demo so you can familiarize yourself. Although the first demo is very basic it is essential to know the foundation of setting up the projects as everything we do will be built on top of that foundation.

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.java file, MainActivity.java file, basic.fsh file and basic.vsh file. These files remain unchanged for this demo so we will only be reviewing the OGLES2Renderer.java file. Additionally, the OGLES2Renderer constructor and onSurfaceCreated function remain the same as the previous demo so we will not be reviewing those functions. 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 onSurfaceChanged function.

    public void onSurfaceChanged(GL10 arg0, int width, int height) {
        final float halfWidth = width / 2.0f;
        final float halfHeight = height / 2.0f;
        
        GLES20.glViewport(0, 0, width, height);
        
        //set matrices and pass to shader
        float projMatrix[] = new float[16];
        Matrix.orthoM(projMatrix, 0, -halfWidth, halfWidth, -halfHeight, halfHeight, -1, 1);
        final int progId = GraphicsUtils.currentProgramId();
        int uProjectionMatrix = GLES20.glGetUniformLocation(progId, "uProjectionMatrix");
        GLES20.glUniformMatrix4fv(uProjectionMatrix, 1, false, projMatrix, 0);
    }

The first lines of code are for defining the half width and half height of the surface. These values are used in the call to Matrix.orthoM. 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 Matrix.orthoM 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 onDrawFrame 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.

    public void onDrawFrame(GL10 arg0) {
        //clear screen
        GLES20.glClear(GLES20.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
        };
        FloatBuffer colorBuffer = ByteBuffer.allocateDirect(colors.length
                * FLOAT_SIZE).order(ByteOrder.nativeOrder()).asFloatBuffer();
        colorBuffer.put(colors).position(0);
        GLES20.glVertexAttribPointer(maColor, 4, GLES20.GL_FLOAT, false, 0, colorBuffer);

        //measure out vertices and set for quad
        final float SIZE = 50;
        final float leftX = -SIZE;
        final float rightX = SIZE;
        final float topY = SIZE;
        final float bottomY = -SIZE;
        
        FloatBuffer verticesBuffer;
        
        //triangles
        final float vertices[] = {
            leftX, bottomY
            , rightX, bottomY
            , leftX, topY
            , rightX, topY
        };
        verticesBuffer = ByteBuffer.allocateDirect(vertices.length
                * FLOAT_SIZE).order(ByteOrder.nativeOrder()).asFloatBuffer();
        verticesBuffer.put(vertices).position(0);        
        GLES20.glVertexAttribPointer(maVertices, 2, GLES20.GL_FLOAT, false, 0, verticesBuffer);
        
        ShortBuffer indexBuffer;
        
        //indices
        final short indices[] = {
            0, 1, 2, 1, 2, 3
        };
        indexBuffer = ByteBuffer.allocateDirect(indices.length
                * SHORT_SIZE).order(ByteOrder.nativeOrder()).asShortBuffer();
        indexBuffer.put(indices).position(0);

        GLES20.glDrawElements(GLES20.GL_TRIANGLES, 6, GLES20.GL_UNSIGNED_SHORT, indexBuffer);
    }


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 out an index buffer, which is a ShortBuffer, which will store the indices that we will use when drawing with glDrawElements.

I will take some time here to explain the 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 ShortBuffer for the indices. Previously we used only FloatBuffer 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 32,767) for this example. We pass the data to the ShortBuffer in a similar fashion as the FloatBuffer, 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 can be tricky, especially when working in Java, as even though we have defined our buffer as a ShortBuffer, the value needs to be GL_UNSIGNED_SHORT. The final parameter is the Buffer that holds 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.