Draw 3D photo cube with OpenGL in Android

Introduction:

      Android includes support for high performance 2D and 3D graphics with the Open Graphics Library (OpenGL®), specifically, the OpenGL ES API. OpenGL is a cross-platform graphics API that specifies a standard software interface for 3D graphics processing hardware. OpenGL ES is a flavor of the OpenGL specification intended for embedded devices. Android supports several versions of the OpenGL ES API:
  • OpenGL ES 1.0 and 1.1 - This API specification is supported by Android 1.0 and higher.
  • OpenGL ES 2.0 - This API specification is supported by Android 2.2 (API level 8) and higher.
  • OpenGL ES 3.0 - This API specification is supported by Android 4.3 (API level 18) and higher.
  • OpenGL ES 3.1 - This API specification is supported by Android 5.0 (API level 21) and higher.





Basics:
   
      Android supports OpenGL both through its framework API and the Native Development Kit (NDK). This topic focuses on the Android framework interfaces.

     There are two foundational classes in the Android framework that let you create and manipulate graphics with the OpenGL ES API: GLSurfaceView and GLSurfaceView.Renderer. If your goal is to use OpenGL in your Android application, understanding how to implement these classes in an activity should be your first objective.
GLSurfaceView
This class is a View where you can draw and manipulate objects using OpenGL API calls and is similar in function to a SurfaceView. You can use this class by creating an instance of GLSurfaceViewand adding your Renderer to it. However, if you want to capture touch screen events, you should extend the GLSurfaceView class to implement the touch listeners, as shown in OpenGL training lesson, Responding to Touch Events.
GLSurfaceView.Renderer
This interface defines the methods required for drawing graphics in a GLSurfaceView. You must provide an implementation of this interface as a separate class and attach it to your GLSurfaceView instance using GLSurfaceView.setRenderer().
The GLSurfaceView.Renderer interface requires that you implement the following methods:
  • onSurfaceCreated(): The system calls this method once, when creating the GLSurfaceView. Use this method to perform actions that need to happen only once, such as setting OpenGL environment parameters or initializing OpenGL graphic objects.
  • onDrawFrame(): The system calls this method on each redraw of the GLSurfaceView. Use this method as the primary execution point for drawing (and re-drawing) graphic objects.
  • onSurfaceChanged(): The system calls this method when the GLSurfaceView geometry changes, including changes in size of theGLSurfaceView or orientation of the device screen. For example, the system calls this method when the device changes from portrait to landscape orientation. Use this method to respond to changes in the GLSurfaceView container.

MainActivity.java

package kalidoss.com.photocube;

import android.graphics.PixelFormat;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.MotionEvent;
import android.widget.RelativeLayout;

/** * Created by Kalidoss on 18/07/16. */
public class MainActivity extends AppCompatActivity { public GLSurfaceView mGLSurfaceView; float mPreviousX,mPreviousY; private final float TOUCH_SCALE_FACTOR = 180.0f / 320; MyGLRender mRenderer; RelativeLayout cubelayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); cubelayout=(RelativeLayout)findViewById(R.id.cude_layout); mGLSurfaceView = new GLSurfaceView(this); // Allocate a GLSurfaceView mRenderer=new MyGLRender(this);

        //make cube transparent     
        mGLSurfaceView.setZOrderOnTop(true);
        mGLSurfaceView.setEGLConfigChooser( 8, 8, 8, 8, 16, 0 );
        mGLSurfaceView.getHolder().setFormat(PixelFormat.RGBA_8888);
        mGLSurfaceView.setRenderer(mRenderer);
        mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
        cubelayout.addView(mGLSurfaceView);
    }

    @Override    public boolean onTouchEvent(MotionEvent e) {
        float x = e.getX();
        float y = e.getY();

        switch (e.getAction()) {
            case MotionEvent.ACTION_MOVE:
                float dx = x - mPreviousX;
                float dy = y - mPreviousY;
                mRenderer.mAngleX += dx * TOUCH_SCALE_FACTOR;
                mRenderer.mAngleY += dy * TOUCH_SCALE_FACTOR;
                mGLSurfaceView.requestRender();
                break;
        }
        mPreviousX = x;
        mPreviousY = y;
        return true;
    }
}

MyGLRender.java

package kalidoss.com.photocube;

import android.content.Context;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

/** * Created by Kalidoss on 18/07/16. */

public class MyGLRender implements GLSurfaceView.Renderer {
    private PhotoCube cube;
    public  float mAngleX,mAngleY;

    // Constructor    public MyGLRender(Context context) {
        cube = new PhotoCube(context);
    }

    // Call back when the surface is first created or re-created.   
   @Override   
   public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        gl.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
        gl.glClearDepthf(1.0f);            // Set depth's clear-value to farthest      
        gl.glEnable(GL10.GL_DEPTH_TEST);   // Enables depth-buffer for hidden surface removal        
        gl.glDepthFunc(GL10.GL_LEQUAL);    // The type of depth testing to do        
        gl.glShadeModel(GL10.GL_SMOOTH);   // Enable smooth shading of color        
        gl.glDisable(GL10.GL_DITHER);      // Disable dithering for better performance       
 
        // Setup Texture, each time the surface is created (NEW)        
        cube.loadTexture(gl);             // Load images into textures (NEW)        
        gl.glEnable(GL10.GL_TEXTURE_2D);  // Enable texture (NEW)       
        gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,
                GL10.GL_FASTEST);
        gl.glEnable(GL10.GL_LIGHT1);   // Enable Light 1 (NEW)       
        gl.glEnable(GL10.GL_LIGHT0);   // Enable the default Light 0 (NEW)    
    }

    // Call back after onSurfaceCreated() or whenever the window's size changes   
    @Override   
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        gl.glViewport(0, 0, width, height);
        float ratio = (float) width / height;
        gl.glMatrixMode(GL10.GL_PROJECTION);
        gl.glLoadIdentity();
        gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
    }

    // Call back to draw the current frame.   
    @Override    
    public void onDrawFrame(final GL10 gl) {
        // Clear color and depth buffers        
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
        gl.glMatrixMode(GL10.GL_MODELVIEW);
        // ----- Render the Cube -----      
        gl.glLoadIdentity();                  
        // Reset the model-view matrix        
        gl.glTranslatef(0, 0, -3.0f);  // Translate into the screen       
        gl.glFrontFace(GL10.GL_CCW);    // Set the front face        
        gl.glEnable(GL10.GL_CULL_FACE); // Enable cull face        
        gl.glCullFace(GL10.GL_BACK);

        //rotate cube
        gl.glRotatef(mAngleX,0.0f, 1.0f, 0.0f);
        gl.glRotatef(mAngleY, 1, 0, 0);
        cube.draw(gl);
    }
}

PhotoCube.java

package kalidoss.com.photocube;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLUtils;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import javax.microedition.khronos.opengles.GL10;

/** * Created by Kalidoss on 18/07/16. */
public class PhotoCube {
    private FloatBuffer vertexBuffer;  // Vertex Buffer   
 private FloatBuffer texBuffer;     // Texture Coords Buffer
    private int numFaces = 6;
    private int[] imageFileIDs = {  // Image file IDs            
            R.drawable.circle,
            R.drawable.square,
            R.drawable.oval,
            R.drawable.star,
            R.drawable.blank,
            R.drawable.blank
    };
    private int[] textureIDs = new int[numFaces];
    private Bitmap[] bitmap = new Bitmap[numFaces];
    private float cubeHalfSize = 1.0f;

    // Constructor - Set up the vertex buffer    
    public PhotoCube(Context context) {
        // Allocate vertex buffer. An float has 4 bytes        
        ByteBuffer vbb = ByteBuffer.allocateDirect(12 * 4 * numFaces);
        vbb.order(ByteOrder.nativeOrder());
        vertexBuffer = vbb.asFloatBuffer();

        // Read images. Find the aspect ratio and adjust the vertices accordingly.       
        for (int face = 0; face < numFaces; face++) {
            bitmap[face] = BitmapFactory.decodeStream(
                    context.getResources().openRawResource(imageFileIDs[face]));
            int imgWidth = bitmap[face].getWidth();
            int imgHeight = bitmap[face].getHeight();
            float faceWidth = 2.0f;
            float faceHeight = 2.0f;
            // Adjust for aspect ratio            
            if (imgWidth > imgHeight) {
                faceHeight = faceHeight * imgHeight / imgWidth;
            } else {
                faceWidth = faceWidth * imgWidth / imgHeight;
            }
            float faceLeft = -faceWidth / 2;
            float faceRight = -faceLeft;
            float faceTop = faceHeight / 2;
            float faceBottom = -faceTop;

            // Define the vertices for this face            
            float[] vertices = {
                    faceLeft, faceBottom, 0.0f,  // 0. left-bottom-front                    
                    faceRight, faceBottom, 0.0f,  // 1. right-bottom-front                   
                    faceLeft, faceTop, 0.0f,  // 2. left-top-front                    
                    faceRight, faceTop, 0.0f,  // 3. right-top-front            
            };
            vertexBuffer.put(vertices);  // Populate        
        }
        vertexBuffer.position(0);    // Rewind
        // Allocate texture buffer. An float has 4 bytes. Repeat for 6 faces.       
        float[] texCoords = {
                0.0f, 1.0f,  // A. left-bottom               
                1.0f, 1.0f,  // B. right-bottom                
                0.0f, 0.0f,  // C. left-top                
                1.0f, 0.0f   // D. right-top        };
        ByteBuffer tbb = ByteBuffer.allocateDirect(texCoords.length * 4 * numFaces);
        tbb.order(ByteOrder.nativeOrder());
        texBuffer = tbb.asFloatBuffer();
        for (int face = 0; face < numFaces; face++) {
            texBuffer.put(texCoords);
        }
        texBuffer.position(0);   // Rewind        //return context;    }

    // Render the shape    public void draw(GL10 gl) {
        gl.glFrontFace(GL10.GL_CCW);

        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
        gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
        gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, texBuffer);

        // front        gl.glPushMatrix();
        gl.glTranslatef(0f, 0f, cubeHalfSize);
        gl.glBindTexture(GL10.GL_TEXTURE_2D, textureIDs[0]);
        gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);
        gl.glPopMatrix();

        // left        gl.glPushMatrix();
        gl.glRotatef(270.0f, 0f, 1f, 0f);
        gl.glTranslatef(0f, 0f, cubeHalfSize);
        gl.glBindTexture(GL10.GL_TEXTURE_2D, textureIDs[1]);
        gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 4, 4);
        gl.glPopMatrix();

        // back        gl.glPushMatrix();
        gl.glRotatef(180.0f, 0f, 1f, 0f);
        gl.glTranslatef(0f, 0f, cubeHalfSize);
        gl.glBindTexture(GL10.GL_TEXTURE_2D, textureIDs[2]);
        gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 16, 4);
        gl.glPopMatrix();

        // right        gl.glPushMatrix();
        gl.glRotatef(90.0f, 0f, 1f, 0f);
        gl.glTranslatef(0f, 0f, cubeHalfSize);
        gl.glBindTexture(GL10.GL_TEXTURE_2D, textureIDs[3]);
        gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 20, 4);
        gl.glPopMatrix();

        // top        gl.glPushMatrix();
        gl.glRotatef(90.0f, 1f, 0f, 0f);
        gl.glTranslatef(0f, 0f, cubeHalfSize);
        gl.glBindTexture(GL10.GL_TEXTURE_2D, textureIDs[4]);
        gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 16, 4);
        gl.glPopMatrix();

        // bottom        gl.glPushMatrix();
        gl.glRotatef(270.0f, 1f, 0f, 0f);
        gl.glTranslatef(0f, 0f, cubeHalfSize);
        gl.glBindTexture(GL10.GL_TEXTURE_2D, textureIDs[5]);
        gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 20, 4);
        gl.glPopMatrix();

        gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
        gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
    }

    // Load images into 6 GL textures    public void loadTexture(GL10 gl) {
        gl.glGenTextures(6, textureIDs, 0); // Generate texture-ID array for 6 IDs
        // Generate OpenGL texture images        for (int face = 0; face < numFaces; face++) {
            gl.glBindTexture(GL10.GL_TEXTURE_2D, textureIDs[face]);
            gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
            gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
            GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap[face], 0);
            bitmap[face].recycle();
        }
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 
 xmlns:android="http://schemas.android.com/apk/res/android"                
 android:orientation="vertical"                
 android:layout_width="match_parent"                
 android:layout_height="match_parent"                
 android:background="@drawable/background">

    <RelativeLayout        
      android:id="@+id/cude_layout"        
      android:layout_width="350dp"        
      android:layout_height="350dp"        
      android:layout_centerInParent="true">
    </RelativeLayout>
</RelativeLayout>

  res -> drawable here to place background.xml file

background.xml

<shape xmlns:android="http://schemas.android.com/apk/res/android" 
 android:shape="rectangle" >
    <gradient        
      android:startColor="#6586F0"        
      android:centerColor="#81BEF7"       
      android:endColor="#A9D0F5"       
      android:angle="90"/>
    <corners        
      android:radius="0dp"/>
</shape>


Happy Coding...
Previous
Next Post »

4 comments

Write comments
June 27, 2017 at 5:24 PM delete

This code working very well.

Reply
avatar
video_ads
AUTHOR
September 6, 2018 at 5:21 PM delete

it`s good but can set only upside, downside, rightside and left side move not all side means left and right triangle move

Reply
avatar
Unknown
AUTHOR
February 4, 2021 at 4:44 PM delete

There are many errors for me wile running the app please can you send me the complete source code. I am a beginner in android please help me.
email - thushargowda600@gmail.com

Reply
avatar
Jason Adams
AUTHOR
February 12, 2021 at 6:49 PM delete

This is truly an amazing post. Thanks for sharing.

Buy Custom Website

Reply
avatar