Drawing 3D graphics with the OpenGL ES 2.0 API

The previous recipes describe OpenGL ES 1.x on the Android NDK. This recipe covers how to use OpenGL ES 2.0 in Android NDK.

Getting ready

Readers are recommended to read the introduction of this chapter before going through this recipe. A lot of graphic basics are covered in the following recipes; it is suggested that we go through them first:

  • Drawing 2D graphics and applying transforms with OpenGL ES 1.x API
  • Drawing 3D graphics and lighting up the scene with OpenGL ES 1.x API

How to do it...

The following steps create an Android project that renders a 3D cube with OpenGL ES 2.0 API in Android NDK:

  1. Create an Android application named CubeG2. Set the package name as cookbook.chapter4.cubeg2. Please refer to the Loading native libraries and registering native methods recipe of Chapter 2, Java Native Interface, if you want more detailed instructions.
  2. Right-click on the project CubeG2, select Android Tools | Add Native Support.
  3. Add three Java files, namely MyActivity.java, MyRenderer.java, and MySurfaceView.java. We only list a part of MyRenderer.java here, since the other two files—MyActivity.java and MySurfaceView.java—are similar to the files in the previous recipe:
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        String vertexShaderStr = LoadShaderStr(mContext, R.raw.vshader);
        String fragmentShaderStr = LoadShaderStr(mContext, R.raw.fshader);
        naInitGL20(vertexShaderStr, fragmentShaderStr);
    }
    @Override
    public void onDrawFrame(GL10 gl) {
      naDrawGraphics(mAngleX, mAngleY);
    }
    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
      naSurfaceChanged(width, height);
    }
  4. Add the Cube.cpp, matrix.cpp, CubeG2.cpp, Cube.h, matrix.h, and mylog.h files under the jni folder. The content of the files are summarized as follows:
    • Cube.cpp and Cube.h: They define a Cube object and method to draw a 3D cube.
    • matrix.cpp and matrix.h: These matrix operations, including creating translation, scale and rotation matrices, and matrix multiplication.
    • CubeG2.cpp: They create and load shaders. They also create, link, and use programs and apply transformations to the 3D cube.
    • mylog.h: They define macros for Android NDK logging.

    Here, we list a part of Cube.cpp and CubeG2.cpp.

    Cube.cpp:

    …
    void Cube::draw(GLuint pvPositionHandle) {
      glVertexAttribPointer(pvPositionHandle, 3, GL_FLOAT, GL_FALSE, 0, vertices);
      glEnableVertexAttribArray(pvPositionHandle);
      glDrawArrays(GL_TRIANGLES, 0, 36);
    }
    ...

    CubeG2.cpp: It includes the loadShader, createProgram, naInitGL20, and naDrawGraphics methods, which are explained as follows:

    • loadShader: This method creates a shader, attaches a source, and compiles the shader:
      GLuint loadShader(GLenum shaderType, const char* pSource) {
         GLuint shader = glCreateShader(shaderType);
         if (shader) {
             glShaderSource(shader, 1, &pSource, NULL);
             glCompileShader(shader);
             GLint compiled = 0;
             glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
             if (!compiled) {
                 GLint infoLen = 0;
                 glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
                 if (infoLen) {
                     char* buf = (char*) malloc(infoLen);
                     if (buf) {
                         glGetShaderInfoLog(shader, infoLen, NULL, buf);
                         free(buf);
                     }
                     glDeleteShader(shader);
                     shader = 0;
                 }
             }
         }
         return shader;
      }
    • createProgram: This method creates a program object, attaches shaders, and links the program:
      GLuint createProgram(const char* pVertexSource, const char* pFragmentSource) {
         GLuint vertexShader = loadShader(GL_VERTEX_SHADER, pVertexSource);
         GLuint pixelShader = loadShader(GL_FRAGMENT_SHADER, pFragmentSource);
         GLuint program = glCreateProgram();
         if (program) {
             glAttachShader(program, vertexShader);
             glAttachShader(program, pixelShader);
             glLinkProgram(program);
         }
         return program;
      }
    • naInitGL20: This method sets up the OpenGL ES 2.0 environment, gets the shader source string, and gets the shader attribute and uniform positions:
      void naInitGL20(JNIEnv* env, jclass clazz, jstring vertexShaderStr, jstring fragmentShaderStr) {
        glDisable(GL_DITHER);  
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);  
      glClearDepthf(1.0f);  
        glEnable(GL_DEPTH_TEST);  
        glDepthFunc(GL_LEQUAL);    
          const char *vertexStr, *fragmentStr;
        vertexStr = env->GetStringUTFChars(vertexShaderStr, NULL);
        fragmentStr = env->GetStringUTFChars(fragmentShaderStr, NULL);
        setupShaders(vertexStr, fragmentStr);
        env->ReleaseStringUTFChars(vertexShaderStr, vertexStr);
        env->ReleaseStringUTFChars(fragmentShaderStr, fragmentStr);
        gvPositionHandle = glGetAttribLocation(gProgram, "vPosition");
        gmvP = glGetUniformLocation(gProgram, "mvp");
      
      }
    • naDrawGraphics: This method applies model transforms (rotate, scale, and translate) and the projection transform:
      void naDrawGraphics(JNIEnv* env, jclass clazz, float pAngleX, float pAngleY) {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glClearColor(0.0, 0.0, 0.0, 1.0f);
        glUseProgram(gProgram);
      //  GL1x: glRotatef(pAngleX, 0, 1, 0);  //rotate around y-axis
      //  GL1x: glRotatef(pAngleY, 1, 0, 0);  //rotate around x-axis
        //rotate
        rotate_matrix(pAngleX, 0.0, 1.0, 0.0, aRotate);
        rotate_matrix(pAngleY, 1.0, 0.0, 0.0, aModelView);
        multiply_matrix(aRotate, aModelView, aModelView);
      //  GL1x: glScalef(0.3f, 0.3f, 0.3f);      // Scale down
        scale_matrix(0.5, 0.5, 0.5, aScale);
        multiply_matrix(aScale, aModelView, aModelView);
      // GL1x: glTranslate(0.0f, 0.0f, -3.5f);
        translate_matrix(0.0f, 0.0f, -3.5f, aTranslate);
        multiply_matrix(aTranslate, aModelView, aModelView);
      //  gluPerspective(45, aspect, 0.1, 100);
        perspective_matrix(45.0, (float)gWidth/(float)gHeight, 0.1, 100.0, aPerspective);
        multiply_matrix(aPerspective, aModelView, aMVP);
        glUniformMatrix4fv(gmvP, 1, GL_FALSE, aMVP);
        mCube.draw(gvPositionHandle);
      }
  5. Create a folder named raw under the res folder, and add the following two files to it:
    • vshader: This is the vertex shader source:
      attribute vec4 vPosition;
      uniform mat4 mvp;
      void main() 
      {
         gl_Position = mvp * vPosition;
      }
    • fshader: This is the fragment shader source:
      void main()
      {
         gl_FragColor = vec4(0.0,0.5,0.0,1.0);
      }
  6. Add the Android.mk file under the jni folder as follows. Note that we must link to OpenGL ES 2.0 by LOCAL_LDLIBS := -lGLESv2:
    LOCAL_PATH := $(call my-dir)
    include $(CLEAR_VARS)
    LOCAL_MODULE    := CubeG2
    LOCAL_SRC_FILES := matrix.cpp Cube.cpp CubeG2.cpp
    LOCAL_LDLIBS := -lGLESv2 -llog
    include $(BUILD_SHARED_LIBRARY)
  7. Add the following line before <application>...</application> in the AndroidManifest.xml file, which indicates that the Android application uses the OpenGL ES 2.0 feature:
    <uses-feature android:glEsVersion="0x00020000" android:required="true" />
  8. Build the Android NDK application and run it on an Android device. The app will display a cube and we can touch to rotate the cube:
    How to do it...

How it works...

The sample project renders a 3D cube using OpenGL ES 2.0. OpenGL ES 2.0 provides a programmable pipeline, where a vertex shader and fragment shader can be supplied to control how the vertex and fragment are processed:

  • Vertex shader: It's executed for every vertex. Transforms, lighting, texture mapping, and so on are usually performed using it.
  • Fragment shader: It's executed for every fragment produced by the rasterizer. A typical processing is to adding colors to every fragment.

Shaders are programmed using OpenGL Shading Language, which is discussed next.

OpenGL Shading Language (GLSL)

Here, we briefly introduce GLSL.

  • Data types: They are of four main types, including bool, int, float, and sampler. There are also vector types for the first three types—bvec2, bvec3, bvec4 refer to 2D, 3D, and 4D boolean vectors. ivec2, ivec3, and ivec4 represent integer vectors. vec2, vec3, and vec4 refer to floating point vectors. Samplers are used for texture sampling and have to be uniform.
  • Attributes, uniforms, and varyings: A shader includes three types of inputs and outputs, including uniforms, attributes, and varyings. All three types have to be global:
    • Uniform: It is of read-only type and doesn't need to be changed during rendering. For example, light position.
    • Attribute: It is of read-only type and is only available as an input to the vertex shader. It changes for every vertex. For example, vertex position.
    • Varying: It is used to pass data from the vertex shader to the fragment shader. It is readable and writable in the vertex shader, but only readable in the fragment shader.
  • Built-in types: GLSL has various built-in attributes, uniforms, and varyings for shaders. We highlight a few of them as follows:
    • gl_Vertex: It is an attribute—a 4D vector representing the vertex position.
    • gl_Color: It is an attribute—a 4D vector representing the vertex color.
    • gl_ModelViewMatrix: It is an uniform—the 4x4 model view matrix.
    • gl_ModelViewProjectionMatrix: It is a uniform. The 4x4 model view projection matrix.
    • gl_Position: It is only available as vertex shader output. It's a 4D vector representing the final processed vertex position.
    • gl_FragColor: It is only available as fragment shader output. It's a 4D vector representing the final color to be written to the frame buffer.

How to use shader:

In our sample project, the vertex shader program simply multiplies every cube vertex with the model-view-projection matrix, and the fragment shader sets green color to every fragment. The following steps should be followed to use the shader source code:

  1. Create Shaders: The following OpenGL ES 2.0 methods are called:
    • glCreateShader: It creates a GL_VERTEX_SHADER or GL_FRAGMENT_SHADER shader. A non-zero value is returned by it, by which the shader can be referenced.
    • glShaderSource: It puts the source code in a shader object. The source code stored previously will be completely replaced.
    • glCompileShader: It compiles the source code of the shader object.
  2. Create a program and attach the shaders: The following methods are called:
    • glCreateProgram: It creates an empty program object to which shaders can be attached. Program objects essentially provide a mechanism to link everything needed to be executed together.
    • glAttachShader: It attaches a shader to a program object.
    • glLinkProgram: It links a program object. If any GL_VERTEX_SHADER objects are attached to the program object, they will be used to create an executable running on the vertex processor. If any GL_FRAGMENT_SHADER shaders are attached, they will be used to create an executable running on the fragment processor.
  3. Use the program: We use the following calls to pass data to shaders and perform OpenGL operations:
    • glUseProgram: A program object as part of current rendering state is installed
    • glGetAttribLocation: It returns an attribute variable's location
    • glVertexAttribPointer: It specifies the location and data format of the array of generic vertex attributes to use at rendering
    • glEnableVertexAttribArray: It enables a vertex attribute array
    • glGetUniformLocation: It returns a uniform variable's location
    • glUniform: It specifies the value of a uniform variable
    • glDrawArrays: It renders primitives from the array data.

There's more...

The sample project performs model-view transform and projection transform through matrix operations. The details of these transforms are tedious and not within the scope of this book, therefore we won't cover them here. However, detailed comments are provided along with the code. Interested readers could also easily find online resources about these operations.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset