Using OpenGL ES to render a 3D object in Android App

Posted By : Daljeet Singh | 27-Jan-2019

OpenGL ES is a cross-platform graphics API provided by the Open Graphics Library(OpenGL) for creating high-performance 2D and 3D graphics.OpenGL provides a standard interface for hardware that usually processes 3D graphics. With the advent of AR and VR in the technological revolution, OpenGL will play a vital part in building immersive experiences for the users while simultaneously delivering top-notch GPU performance in devices across all segments.

 

OpenGL ES and the Android Framework :

Nowadays, a significant number of animation and graphics-heavy android apps are being powered by OpenGL ES under the hood. This is due to reliability in the performance of OpenGL and the support offered for it by the Android framework across a broad spectrum of devices. The different OpenGL versions supported by the Android framework are :

  • OpenGL ES 1.0 & 1.1: This version of the API supports devices that use Android API level 2 or higher
  • OpenGL ES 2.0: This version of the API supports devices that use Android API level 8 or higher
  • OpenGL ES 3.0: This version of the API supports devices that employ Android API level 18 or higher
  • OpenGL ES 3.1: This version of the API supports devices that use Android API level 21 or higher

For our implementation, we chose to utilize OpenGL ES 2.0 in order to target almost the entire range of active Android devices on the market. The Android framework provides us, two elementary classes, to help create and render graphics using the OpenGL ES API:

GLSurfaceView - GLSurfaceView acts as a binding mechanism between the OpenGL ES API and the View of the application. It allows a user to manipulate and draw 2D & 3D objects inside the app. A GLSurfaceView also helps us manage the OpenGL ES API with the lifecycle of the activity in which it is used. We can utilize the GLSurfaceView class by creating its instance and attaching a Renderer to it. We can further add touch events for the GLSurfaceView by making the class implement the appropriate touch listeners.

GLSurfaceView.Renderer - The Renderer interface helps draw graphics over the GLSurfaceView by providing three methods that should be implemented by any class that implements the Renderer :

  • onSurfaceCreated(): This method is called by the system when it creates the GLSurfaceView.It is generally used to set-up actions that do not need to be repeated during the rendering cycle such as initialization of the OpenGL graphics objects, enabling the z-buffer, color to clear the screen with, setup of environment parameters for OpenGL etc.
  • onDrawFrame(): This method is where the actual drawing of the objects on the GLSurfaceView takes place. It is called by the system on every redraw call of the GLSurfaceView.
  • onSurfaceChanged(): This method is called by the system when the geometrical proportions of the GLSurfaceView change in any manner. An example of a geometrical change may be a change in orientation of the device or a change in the GLSurfaceView size. You could respond to a change in geometry by resetting the aspect ratio.

 

Creating a 3D Cylinder Object :

We will generate and render a 3D cylinder object for the purpose of getting acquainted with OpenGL ES on Android. We will start off by generating the 3D cylinder. We will need to create and store the cylinder's vertices and normals in a ByteBuffer :

        int numberOfVertex=16;
        
        float[] vertex = new float[3 * (numberOfVertex + 1) * 2];
        byte[] baseVertexIndex = new byte[numberOfVertex];
        byte[] topVertexIndex = new byte[numberOfVertex];
        byte[] edgeVertexIndex = new byte[numberOfVertex*2 + 2];

        double anglePerVertex = 2 * Math.PI / numberOfVertex;

        int height = 1;
        for (int i = 0; i < numberOfVertex; i++) {
            double angle = i * anglePerVertex;
            int offset = 6 * i;

            int cx = 0;
            float radious = 1;
            vertex[offset] = (float)(Math.cos(angle) * radious) + cx;
            vertex[offset + 1] = -height;
            int cy = 0;
            vertex[offset + 2] = (float)(Math.sin(angle) * radious) + cy;

            vertex[offset + 3] = (float)(Math.cos(angle) * radious) + cx;
            vertex[offset + 4] = height;
            vertex[offset + 5] = (float)(Math.sin(angle) * radious) + cy;

            topVertexIndex[i] = (byte)(2*i);

            baseVertexIndex[i] = (byte)(2*i +1);

            edgeVertexIndex[2*i + 1] = baseVertexIndex[i];
            edgeVertexIndex[2*i] = topVertexIndex[i];

        }


        edgeVertexIndex[2*numberOfVertex] = topVertexIndex[0];
        edgeVertexIndex[2*numberOfVertex+1] = baseVertexIndex[0];

        ByteBuffer vbb = ByteBuffer
                .allocateDirect(vertex.length * 4)
                .order(ByteOrder.nativeOrder());

        FloatBuffer mFVertexBuffer = vbb.asFloatBuffer();
        mFVertexBuffer.put(vertex);
        mFVertexBuffer.position(0);

        FloatBuffer normalBuffer = mFVertexBuffer;

        ByteBuffer mCircleBottom = ByteBuffer.allocateDirect(baseVertexIndex.length);
        mCircleBottom.put(baseVertexIndex);
        mCircleBottom.position(0);

        ByteBuffer mCircleTop = ByteBuffer.allocateDirect(topVertexIndex.length);
        mCircleTop.put(topVertexIndex);
        mCircleTop.position(0);

        ByteBuffer mEdge = ByteBuffer.allocateDirect(edgeVertexIndex.length);
        mEdge.put(edgeVertexIndex);
        mEdge.position(0);

 

Using the base, top, and edge vertex buffers created above along with the normal buffer, we will draw the cylinder using the OpenGL ES API:

        gl.glCullFace(GL10.GL_BACK);
        gl.glEnable(GL10.GL_CULL_FACE);
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mFVertexBuffer);
        gl.glNormalPointer(GL10.GL_FLOAT, 0, normalBuffer);
        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
        gl.glRotatef(mAngle, 0, 1, 0);
        gl.glRotatef(mAngle*0.25f, 1, 0, 0);
        gl.glPushMatrix();
        
        gl.glColor4f(1f, 0, 0, 0);
        gl.glDrawElements( GL10.GL_TRIANGLE_FAN, numberOfVertex, GL10.GL_UNSIGNED_BYTE, mCircleTop);
        gl.glPopMatrix();
        gl.glPushMatrix();

        gl.glColor4f(1f, 0, 0f, 0);
        gl.glDrawElements( GL10.GL_TRIANGLE_STRIP, numberOfVertex * 2 + 2, GL10.GL_UNSIGNED_BYTE, mEdge);
        gl.glPopMatrix();
        gl.glPushMatrix();

        gl.glTranslatef(0, 2* height, 0);
        gl.glRotatef(-180, 1, 0, 0);

        gl.glColor4f(0f,1f, 0, 0);
        gl.glDrawElements( GL10.GL_TRIANGLE_FAN, numberOfVertex, GL10.GL_UNSIGNED_BYTE, mCircleBottom);
        gl.glPopMatrix();

 

Now, that we have created and drawn the 3D cylinder using OpenGL, we will need to set up a GLSurfaceView along with its Renderer to actually render it inside an activity so that it is visible to a user. Creating a GLSurfaceView class is fairly uncomplicated :

//The GLSurfaceView Class
public class CustomGLSurfaceView extends GLSurfaceView {
    public CustomGLSurfaceView(Context context) {
        super(context);
        initView();
    }

    public CustomGLSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    private void initView() {
        setPreserveEGLContextOnPause(true);
        CustomOpenGLRenderer customOpenGLRenderer=new CustomOpenGLRenderer();
        setRenderer(customOpenGLRenderer);
        customOpenGLRenderer.setAngle(210f);
    }
}

 

And finally, we will create a Renderer class to render the Cylinder object on top of the GLSurfaceView :

public class CustomOpenGLRenderer implements GLSurfaceView.Renderer {
    private Cylinder cylinder;

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        cylinder=new Cylinder();
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        GLES20.glViewport(0,0,width,height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        cylinder.generateAndDraw(gl,1f);
    }

    private float angle;

    public void setAngle(float angle){
        this.angle=angle;
    }

}

//The Cylinder class contains the generateAndDraw method 
// to generate and draw the cylinder using OpenGL ES using the code provided above in the blog

 

While the aforementioned example provides a pretty rudimentary use case for employing OpenGL ES in your Android app, its capabilities can be extended to build far more complex graphical interfaces.OpenGL can be utilized to build apps that need to render high-end 3-dimensional vector graphics without comprising on the performance as it interacts with the device at the hardware level. Further insights about developing Android apps using OpenGL ES can be found at this link.

About Author

Author Image
Daljeet Singh

Daljeet has experience developing android applications across a range of domains such as Cryptocurrency, Travel & Hotel Booking, Video Streaming and e-commerce. In his free time, he can be found playing/watching a game of football or reading up on either

Request for Proposal

Name is required

Comment is required

Sending message..