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 aSurfaceView
. You can use this class by creating an instance ofGLSurfaceView
and adding yourRenderer
to it. However, if you want to capture touch screen events, you should extend theGLSurfaceView
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 yourGLSurfaceView
instance usingGLSurfaceView.setRenderer()
.
TheGLSurfaceView.Renderer
interface requires that you implement the following methods:onSurfaceCreated()
: The system calls this method once, when creating theGLSurfaceView
. 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 theGLSurfaceView
. Use this method as the primary execution point for drawing (and re-drawing) graphic objects.onSurfaceChanged()
: The system calls this method when theGLSurfaceView
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 theGLSurfaceView
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...
4 comments
Write commentsThis code working very well.
Replyit`s good but can set only upside, downside, rightside and left side move not all side means left and right triangle move
ReplyThere 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.
Replyemail - thushargowda600@gmail.com
This is truly an amazing post. Thanks for sharing.
ReplyBuy Custom Website
EmoticonEmoticon