Welcome to Blogs @ Andrew Qu
Blog Index
All blogs
Search results

Android OpenGL Graphics Start Application

Summary

In this post, I will write a simple Android application to do Open GL graphics programming using Android GLES2.0. The development is done using Android studio. Therefore, if you have not installed Android Stuio yet, please do so by following the previous post.

Starting a New Project
  1. Start Android Studion and click [New Project]
  2. In the new project window, enter Application:Gles20App; Company domain:your.name.com, then click [Next]
  3. In the next dialog, keep the default entry: [Phone and Tablet] ticked; Minimum SDK = API level 17 (Jellybean)
  4. In the next dialog, choose the default [Blank Acitivity]
  5. In the next diloag, use defaults, except change [Title] to "GLES2.0 Demo"
  6. Click [Finish].
  7. A default new project should be created, as shown bellow:
  8. If you have a real device, connect it to the USB port to your PC. Otherwise, you may use one of the emulators.
  9. Click [run] as circled in the above screenshot, it will ask you to select a device.
  10. If you decide to use an emulator, follow the prompts and select an smaller "device", say 4" Nexus S API 17 - armeabi-v7a (If you should choose x86, as suggested, you may need to enable HW acceleration).
  11. Once you have selected a device and if lucky, you will see the emulator running and starts you application as showing on the right
Adding GLES20 Skeleton Files

In this section, we are going to add code files that are required as a minimum.

Add a new Java class called "GLesSurfaceView" (alongside of MainActivity). Paste the code below:

public class GLesSurfaceView  extends GLSurfaceView {

    private GLesRender mRenderer;

    public GLesSurfaceView(Context context) {
        super(context);
        this.setEGLContextClientVersion(2);
        this.setPreserveEGLContextOnPause(true);
        mRenderer = new GLesRender(context);
        setRenderer(mRenderer);
    }

    @Override
    public void onPause() {
        super.onPause();
    }

    @Override
    public void onResume() {
        super.onResume();
    }
}

class GLesRender implements GLSurfaceView.Renderer {
    Context context;   // Application's context

    // Constructor with global application context
    public GLesRender(Context context) {
        this.context = context;
    }
    
    @Override
    public void onSurfaceCreated(GL10 unused, EGLConfig config) {
    }
    
    @Override
    public void onSurfaceChanged(GL10 unused, int w, int h) {
    }
    
    @Override
    public void onDrawFrame(GL10 unused) {
    }
}
Modify MainAcitivy.onCreate() method and add onPause(), onResume() so that it looks like below :
    private GLesSurfaceView mGLesView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // setContentView(R.layout.activity_main);
        mGLesView = new GLesSurfaceView(this);
        this.setContentView(mGLesView);
    }

    @Override
    public void onPause() {
        super.onPause();  // Always call the superclass method first
        if(mGLesView != null) mGLesView.onPause();
    }

    @Override
    public void onResume() {
        super.onResume();
        if (mGLesView != null) mGLesView.onResume();
    }

Add the following line to the Android amnifest file:

<uses-feature android:glEsVersion="0x00020000" android:required="true" />
<application>
       ...

Now, run the application, you should get a blank screen as shown right.

A GLSurfaceView is an UI surface that displays our opengl graphics. We replaced the application content view by this view so that the graphics will be displayed onto the application UI. This is done with setContentView() call.

The GLSurfaceView is only a template holder. To actually render graphics, it needs a renderer to draw the graphics. Therefore we defined a new class, GLesRender to draw our graphics. Of course, at the moment, it is not drawing anything yet.

this.GLesSurfaceView(Context context) tells opengl engine that we want to use GLES v2.0 (GLES=Graphics Library for Embedded System)

When the renderer is attached to a surface view (setRenderer), it calls the renderer's onSurfaceCreated() method and then onSurfaceChanged() with width and height of the drawng surface.

After that, the renderer's onDrawFrame() method is repeatedly called (at roughly 60 times a second, or 60fps(frame per seconds)).

Setting the display background color

We want to change the black background color to a gray color (or any other color you want). This can be done by modifying the the following methods:

    public void onSurfaceCreated(GL10 unused, EGLConfig config) {
        GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f); //red, green, blue, alpha
    }
    public void onDrawFrame(GL10 unused) {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
    }

glClear(GLES20.GL_COLOR_BUFFER_BIT) clears the background color. When clearing, it fills with the color last set in GLES20.glClearColor(). All color components (red,green,blue) are specified as float point numbers ranging from 0.0(black) to 1.0(white).

Now, run the application, you should see a gray background.

Draw a Colored Rectangle to Host a Character

To do this, we need to add an opengl draw object. Add a new Java class, GLesLetter, paste the following code:

public class GLesLetter {

    private FloatBuffer vertexBuffer;  // Buffer for vertex-array
    private int numVertices;
    private static int mShaderProgram = -1;

    // Initialize a rect with top/left at (0,0), of w x h
    public GLesLetter(float w, float h) {
        float[] vertices = new float[12];
        vertices[0] = 0.0f; // bottom/left
        vertices[1] = -h;
        vertices[2] = vertices[5] = vertices[8] = vertices[11] = 0.0f; // z=0.0 for all
        vertices[3] = w; // bottom right
        vertices[4] = -h;
        vertices[6] = 0.0f; // top left
        vertices[7] = 0.0f;
        vertices[9] = w;   // top right
        vertices[10] = 0.0f;
        numVertices = vertices.length / 3;

        ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
        vbb.order(ByteOrder.nativeOrder()); // Use native byte order
        vertexBuffer = vbb.asFloatBuffer(); // Convert from byte to float
        vertexBuffer.put(vertices);         // Copy data into buffer
        vertexBuffer.position(0);           // Rewind

        if( mShaderProgram == -1) { // Only load once
            mShaderProgram = loadShaderProgram(vertexShaderCode, fragmentShaderCode);
        }
    }

    public void draw(){
        GLES20.glUseProgram(mShaderProgram);

        // get handle to vertex shader's aPosition member
        int mPositionHandle = GLES20.glGetAttribLocation(mShaderProgram, "aPosition");

        // Enable a handle to the triangle vertices
        GLES20.glVertexAttribPointer(mPositionHandle, 3, GLES20.GL_FLOAT, false, 0, vertexBuffer);
        GLES20.glEnableVertexAttribArray(mPositionHandle);

        // Draw the rectangle (made of 2 triangles)
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, numVertices);

        GLES20.glDisableVertexAttribArray(mPositionHandle);
    }

    private static String vertexShaderCode =
        "attribute vec4 aPosition;" +
        "void main() {" +
        "  gl_Position = aPosition;" +
        "}";

    private static String fragmentShaderCode =
        "precision mediump float;" +
        "void main() {" +
        "  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);" +
        "}";

    public static int loadShaderProgram(String vertexCode, String fragmentCode){
        int shaderVertex = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);
        GLES20.glShaderSource(shaderVertex, vertexCode);
        GLES20.glCompileShader(shaderVertex);

        int shaderFragment = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER);
        GLES20.glShaderSource(shaderFragment, fragmentCode);
        GLES20.glCompileShader(shaderFragment);

        int shaderProgram = GLES20.glCreateProgram();
        GLES20.glAttachShader(shaderProgram, shaderVertex);
        GLES20.glAttachShader(shaderProgram, shaderFragment);
        GLES20.glLinkProgram(shaderProgram);

        return shaderProgram;
    }
}

Modify the renderer class GLesRender to draw a rectangle as follows:

    private GLesLetter mLetter;
    private int surfaceWidth;
    private int surfaceHeight;

    @Override
    public void onSurfaceChanged(GL10 unused, int w, int h) {
        surfaceHeight = h;
        surfaceWidth = w;
        // rect of 10x16 pixels
        mLetter = new GLesLetter(10.0f/surfaceWidth/2.0f, 16.0f/surfaceHeight/2.0f);
    }

    @Override
    public void onDrawFrame(GL10 unused) {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
        mLetter.draw();
    }

Now run the application, you should get something looks like on the right.

There are a lot going on here. First, we created an opengl class GLesLetter (to be discussed in more detail later). We then, in GLesRender class, created an GLesLetter object, instantiated in onSurfaceCreated() and its draw() method is called in onDrawFrame().

An opengl object mainly consists of 2 parts : 1) its geometry definition 2) how the geometry is rendered on screen. For the GLexLetter object, the geomtry is a 2D rectangle in x-y plan. Rendering of the object is to fill red color to its faces.

The geometry is to be defined in a FloatBuffer vertexBuffer. This is constructed in the constructor. In opengl, the screen coordinate is from -1.0 to 1.0. This is shown in the following diagram, together with rectangle.

The object is rendered using so called shader programs. These are litle program run by opengl engine (often passed directly to the GPU) to render the object. A shader program consists 2 parts (again), a vertex shader and a fragement shader.

A vertex shader operates on the vertices stored in vertexBuffer. For each vertex, it is called once, passing to it the (x,y,z) coordinate. The vertex shader may choose to transform the coordinates as necessary.

A fragment shader operates on the surface of the object, deciding what color to draw for each pixel (yes! it is called for every visible pixel mapped by the object's surface). In our case, we simply use a simple red color.

Now, lets look at the vertex shader program.
attribute vec4 aPosition; -- This defines an vec4 attribute that can be mapped to a vertex array
gl_Position = aPosition; -- gl_Position is a special opengl variable, like a return value.
          Here, for each vertex passed in, we simpy return the vertex with change.

In the fragment shader, for each pixel, we simply return a red color (as set the special variable gl_FragColor.

The shader programs are specified as 2 strings. Before they can be used by opengl engine, we have to compile and link them. This is done in the static method loadShaderProgram() which register a program ID and then sets its vertex and fragment shader programs. The program ID is returned, and kept in parameter "mShaderProgram" for later use.

The draw() method is called (~60 time per seconds) to draw the object. It first tells the opengl engine to use the shader program (glUseProgram()). The it searches for the "aPosition" attribute in the vertex shader to pass the vertexBuffer array in. Then it calls glDrawArrays() to draw the object.

Drawing a Character

There is no function to draw text in opengl. To do that, we need to first draw text into a bitmap. Then use the bitmap as texture to the rectangle face to show text in opengl. This is limited, but can be useful to show simple text, such as game scores in opengl.

Make the following code changes to GLesLetter class. Code in blue are the ones added.

public class GLesLetter {
    FloatBuffer texBuffer;
    ....

    // Initialize a rect with top/left at (0,0), of w x h
    public GLesLetter(float w, float h) {
        ....

        if( mShaderProgram == -1) { // Only load once
             mShaderProgram = loadShaderProgram(vertexShaderCode, fragmentShaderCode);
        }
        
        createLetterTextures();

        float[] texArray = {
                0.0f, 1.0f,
                1.0f, 1.0f,
                0.0f, 0.0f,
                1.0f, 0.0f
        };
        ByteBuffer tbb = ByteBuffer.allocateDirect(texArray.length * 4);
        tbb.order(ByteOrder.nativeOrder());
        texBuffer = tbb.asFloatBuffer();
        texBuffer.put(texArray);
        texBuffer.position(0);
    }
    
    public void draw(){
        GLES20.glUseProgram(mShaderProgram);

        // get handle to vertex shader's aPosition member
        int mPositionHandle = GLES20.glGetAttribLocation(mShaderProgram, "aPosition");
        int mTexCoordinateHandle = GLES20.glGetAttribLocation(mShaderProgram, "aTexCod");

        // Enable a handle to the triangle vertices
        GLES20.glVertexAttribPointer(mPositionHandle, 3, GLES20.GL_FLOAT, false, 0, vertexBuffer);
        GLES20.glEnableVertexAttribArray(mPositionHandle);
        
        GLES20.glVertexAttribPointer(mTexCoordinateHandle, 2, GLES20.GL_FLOAT, false, 0, texBuffer);
        GLES20.glEnableVertexAttribArray(mTexCoordinateHandle);

        // Set the active texture unit to texture unit 0.
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texIDs[0]);

        // Draw the rectangle (made of 2 triangles)
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, numVertices);

        GLES20.glDisableVertexAttribArray(mTexCoordinateHandle);
        GLES20.glDisableVertexAttribArray(mPositionHandle);
    }

    private static String vertexShaderCode =
        "attribute vec4 aPosition;" +
        "attribute vec2 aTexCod;" +
        "varying vec2 vTexCod;" +
        "void main() {" +
        "  gl_Position = aPosition;" +
        "  vTexCod = aTexCod;" +
        "}";

    private static String fragmentShaderCode =
        "precision mediump float;" +
        "uniform sampler2D uTexture;" +
        "varying vec2 vTexCod;" +
        "void main() {" +
//        "  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);" +
        "  gl_FragColor = texture2D(uTexture, vTexCod);" +
        "}";

    public static int loadShaderProgram(String vertexCode, String fragmentCode){
        ....
        return shaderProgram;
    }


    private static String lettersAllowed = "?0123456789.";
    private static int[] texIDs = null;

    private static void createLetterTextures() {
        if( texIDs != null ) return;
        texIDs = new int[lettersAllowed.length()];

        GLES20.glGenTextures(texIDs.length, texIDs, 0); // Generate texture-ID array
        for(int i=0; i<texIDs.length; i++) {
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texIDs[i]);
            GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
            GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
            GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, getBitmapForLetter(lettersAllowed, i), 0);
        }
    }

    private static Bitmap getBitmapForLetter(String letters, int index) {
        Bitmap bitmap = Bitmap.createBitmap(10, 18, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setTextSize(16.0f);
        paint.setColor(Color.WHITE);
        paint.setTextAlign(Paint.Align.CENTER);
        canvas.drawText(letters, index, index+1, 5.0f, 13.0f, paint);

        return bitmap;
    }

Now run the application, you should see the UI as shown in the picture on right.

To explain, lets start with method getBitmapForLetter() that obtains a in memory bitmap for a letter. It first creates an empty bitmap, then creates a canvas object using the bitmap as the drawing surface. It then draws the character from input string "letters" at the specified index. Finally return the bitmap object.

Next, static method createLetterTextures() creates opengl texture objects using the bitmaps created by calling getBitmapForLetter(). A texture is an image that is to be laid onto an opengl object's surfaces. Texture objects are stored in opengl engine. So we first obtain some texrure IDs (handles) by calling GLES20.glGenTextures(). The texture objects are then mapped to the bitmaps containing letters set in the static string "lettersAllowed". The texture objects are static and are therefore created only once.

createLetterTextures() is called in constructor GLexLetter().

In opengl, to lay a texture onto a (triangle) surface, the mapping is done using the so called texture coordinates. Each vertex of the triangle is mapped onto a position in the bitmap. The coordinate system of the texture is x to the right, y goes down. The origin is at top/left of the image. All coordinates are between 0.0 to 1.0.

For the letter rectangle, the texture coordinates of each vertex are specified in array texArray which is initialized and then prepared in the constructor.

To actually render the texture, we need to change the shader programs. First we need to pass the texture aray to the vertex shader (because this is a vertex quantity). This is done through attribute aTexCod in the vertexShaderCode. In the same code, we introduced a new variable "varying vec2 vTexCod;". This variable is simply set as "vTexCod = aTexCod;". In opengl, a varying variable means it varies per pixel of the surface. By default, its values are interpolated using the vertex values. For vTexCod, it simply means calculate vTexCod by interpolating aTexCod (the texture of the vertices). Therefore, the following table list some of the sample values of vTexCod:

PointvTexCod
Top/left0.0, 0.0
Bot/left0.0, 1.0
Bot/right1.0, 1.0
Left/mid0.0, 0.5
Bot/mid0.5, 1.0

Now come to the drawing of the texture. We first in the draw() method, activate and then bind the texture we prepared earlier

    GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texIDs[0]);

In the fragment shader, the texture is automatically passed to the special variable "uniform sampler2D uTexture". Then, for each pixel, we use the color in the texture bitmap at the interpolated texture coordinate for the pixel.

Drawing a Text String

Next, we are going to draw a text string at a specified location and rotation angle. Make the following changes to GLesRender

 class GLesRender implements GLSurfaceView.Renderer {
     private GLesLetter mLetter;     
     private int displayWidth;
     private int displayHeight;
 
     Context context;   // Application's context
     .....
 
     @Override
     public void onSurfaceCreated(GL10 unused, EGLConfig config) {
         //mLetter = new GLesLetter(0.2f, 0.2f);
         GLES20.glClearColor(0.75f, 0.75f, 0.75f, 1.0f); //red, green, blue, alpha 
     }
 
     @Override
     public void onSurfaceChanged(GL10 unused, int w, int h) {
         displayHeight = h;
         displayWidth = w;
         mLetter = new GLesLetter((float)(2.0*24.0/displayWidth), (float)(2.0*48.0/displayHeight));
     }
 
     @Override
     public void onDrawFrame(GL10 unused) {
         GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
         //mLetter.draw();
         mLetter.drawText("1000", 0.0f, 0.0f, 15.0f);
     }
 }

The above change keeps the display width and height. The character size is now initialized as 24x48 pixels converted to opengl display unit. In stead of calling draw(), we now call mLetter.drawText() with a location and rotation.

Now modify the code for GLesLetter :

public class GLesLetter {
     private static int mShaderProgram = -1;
     private float[] offsetRotate = {0.2f, 0.2f, 0.2f};
     float charWidth;

     // Initialize a rect with top/left at (0,0), of w x h
     public GLesLetter(float w, float h) {
        charWidth = w;
         ....
     }
     public void draw(char letter, float top, float left, float rotateRadian){
         GLES20.glUseProgram(mShaderProgram);
 
         int index = lettersAllowed.indexOf(letter);
         if(index == -1) index = 0;

         // get handle to vertex shader's aPosition member
         int mPositionHandle = GLES20.glGetAttribLocation(mShaderProgram, "aPosition");
         int mTexCoordinateHandle = GLES20.glGetAttribLocation(mShaderProgram, "aTexCod");

         int mOffsetHandle = GLES20.glGetUniformLocation(mShaderProgram, "offsetRotate");
         offsetRotate[0] = left;
         offsetRotate[1] = top;
         offsetRotate[2] = rotateRadian;
         GLES20.glUniform3fv(mOffsetHandle, 1, offsetRotate, 0);
 
         ....
 
         // Set the active texture unit to texture unit 0.
         GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
         GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texIDs[index]);
         ....
     }
     private static String vertexShaderCode =
         "attribute vec4 aPosition;" +
         "attribute vec2 aTexCod;" +
         "uniform vec3 offsetRotate;" +
         "varying vec2 vTexCod;" +
         "void main() {" +
         "  float x,y,xo,yo,r;" +
         "  xo = offsetRotate.x; yo = offsetRotate.y;  r = offsetRotate.z;" +
         "  x = aPosition.x; y = aPosition.y;" +
         "  gl_Position = vec4(xo+x*cos(r)-y*sin(r),yo+x*sin(r)+y*cos(r),0.0,1.0); " + //aPosition;" +
         "  vTexCod = aTexCod;" +
         "}";
         .........
     }
 
     public void drawText(String text, float top, float left, float rotateDegree) {
         float rotateRadian = (float) Math.toRadians(rotateDegree);
         for(int i=0; i<text.length(); i++) {
             draw(text.charAt(i), (float)(top + charWidth*Math.sin(rotateRadian)*i),
                     (float)(left + charWidth*Math.cos(rotateRadian)*i), rotateRadian);
         }
     }
 }
In the vertex shader code, we introduced a new uniform variable, offsetRotate, to pass in the offset and rotation. Each vertex is then moved and rotated in the vertex shader.

In the draw() method, the handle to this variable is obtainded. It is then set the value using
    GLES20.glUniform3fv(mOffsetHandle, 1, offsetRotate, 0);
Notice that, we declared array offsetRotate[] as a class variable, instead of a local variable. It is important that any heap memory allocation should be avoided in the draw() function. These function called ~60 time a second. Soon you may exhausting memory and requiring GC that is not efficient and will your application to stutter.

drawText() simply calls draw() for each char in the text. Before the call, its position is calculated.

You may notice that the text box is sheared. This is caused by the display aspect ratio, something that will be addressed in a future post.

Setting Text and Background Color
public class GLesLetter {
    .....
    float charWidth;
    private float[] backColor;
    private float[] textColor;
 
    // Initialize a rect with top/left at (0,0), of w x h
    public GLesLetter(float w, float h, float[] bkColor, float[] txColor) {
         backColor = bkColor;
         textColor = txColor;
         .....
    }
    public void draw(char letter, float top, float left, float rotateRadian){
         GLES20.glUseProgram(mShaderProgram);
         ......
         int mOffsetHandle = GLES20.glGetUniformLocation(mShaderProgram, "offsetRotate");
         int mBackColorHandle = GLES20.glGetUniformLocation(mShaderProgram, "backColor");
         int mTextColorHandle = GLES20.glGetUniformLocation(mShaderProgram, "textColor");
         offsetRotate[0] = left;
         offsetRotate[1] = top;
         offsetRotate[2] = rotateRadian;
         GLES20.glUniform3fv(mOffsetHandle, 1, offsetRotate, 0);
         GLES20.glUniform4fv(mBackColorHandle, 1, backColor, 0);
         GLES20.glUniform4fv(mTextColorHandle, 1, textColor, 0);
         .....
     }
     private static String fragmentShaderCode =
         "precision mediump float;" +
         "uniform sampler2D uTexture;" +
         "uniform vec4 backColor;" +
         "uniform vec4 textColor;" +
         "varying vec2 vTexCod;" +
         "void main() {" +
         "  vec4 color; float b,c;" +
         "  color = texture2D(uTexture, vTexCod); c=color.x; b=1.0-c;" +
         "  if(color.x<0.001 && color.y<0.001 && color.z<0.001) " +
         "    gl_FragColor = backColor;" +
         "  else {" +
         "    gl_FragColor = vec4(c*textColor.x + b*backColor.x, c*textColor.y + b*backColor.y, c*textColor.z + b*backColor.z,1.0);" +
         "  }" +
         "}";

Modify GLesRender.onSurfaceChanged() method as follow:

     @Override
    public void onSurfaceChanged(GL10 unused, int w, int h) {
        displayHeight = h;
        displayWidth = w;
        mLetter = new GLesLetter((float)(2.0*24.0/displayWidth), (float)(2.0*48.0/displayHeight),
                new float[]{1.0f, 0.0f, 0.0f, 1.0f}, new float[]{0.0f, 0.0f, 1.0f, 1.0f});
    }

In the GLesLetter() constructor, we've added two parameters: bkColor and txColor for the text string to be drawn. The parameters are passed and stored in class variables. These are passed to the fragmentShaderCode program as backColor and textColor in the draw() method.

The new code added to the fragementShaderCode demonstrates how fragment shader programs work. Originally the letter image is black and white grey scaled.  We get the texture color and get its x component (c=color.x). If c==0, then this is a black pixel, the color is assigned to the backColor. Otherwise, the color should be a blend of the backColor and textColor, tht is, when c=1, it take the textColor, when c=0, it takes the backColor.

Summary and Final Code

In this post, I have introduced the basic framework code necessary to enable the simplest opengl applicaiton in Android.

I then introduced  opengl's shader programs by drawing a very simple rectangle face. Recall that a vertex shader program deals with the render object's vertex data. It is called per vertex and the coordinates can be transformed. Here we demonstrated this by offseting and rotating the rectangle. The final output is the fixed variable gl_Position.

A varying variable in a vertex shader program is an interpolated variable whose value is interpolated between vertex values. For example, vTexCod is interpolated coordinate between vertex's texture coordinates. This variable is then passed to the fragment shader program for each pixel.

The workings of fragment shader program is demonstrated by setting the text string's color and background color. The final output is the fixed variable gl_FragColor.

Remember that the draw() functions are called ~60 time per seconds. Therefore, you should not try to dynamically allocate memory on the heap. That is, any new calls should be avoided. Any array should be declared as class members.

Download Final source code files in case the I have made any mistakes when cut/paste source code. If you are confused about where to insert code fragments, you may refer to the code files.

Ads from Google
Dr Li Anchor Profi
www.anchorprofi.de
Engineering anchorage plate design system
©Andrew Qu, 2015. All rights reserved. Code snippets may be used "AS IS" without any kind of warranty. DIY tips may be followed at your own risk.