Basic rendering with OpenGL ES
Let us add some graphics to our sample Android application App2
. Here, we show how to create an off-screen bitmap, and then copy it to the screen using the OpenGL ES Version 2 or 3 available on your Android device.
Note
Refer to the App3
sample in the book's downloadable code bundle for the full source code.
Getting ready
We assume that the reader is somewhat familiar with OpenGL and the GL Shading Language (GLSL). Refer to http://www.opengl.org/documentation for the desktop OpenGL, and http://www.khronos.org/opengles for the mobile OpenGL ES documentation.
How to do it…
- We need to write a simple vertex and fragment GLSL shader that will render our framebuffer on the screen using OpenGL ES. Let's put them directly into
jni/Wrappers.cpp
as strings. The following code shows the vertex shader:static const char g_vShaderStr[] = "#version 100\n" "precision highp float;\n" "attribute vec3 vPosition;\n" "attribute vec3 vCoords;\n" "varying vec2 Coords;\n" "void main()\n" "{\n" " Coords = vCoords.xy;\n" " gl_Position = vec4( vPosition, 1.0 );\n" "}\n";
- The fragment shader is as follows:
static const char g_fShaderStr[] = "#version 100\n" "precision highp float;\n" "varying vec2 Coords;\n" "uniform sampler2D Texture0;\n" "void main()\n" "{\n" " gl_FragColor = texture2D( Texture0, Coords );\n" "}\n";
- We will also need the following helper function to load our shaders into OpenGL ES:
static GLuint LoadShader( GLenum type, const char* shaderSrc ) { GLuint shader = glCreateShader( type ); glShaderSource ( shader, 1, &shaderSrc, NULL ); glCompileShader ( shader ); GLint compiled; glGetShaderiv ( shader, GL_COMPILE_STATUS, &compiled ); GLsizei MaxLength = 0; glGetShaderiv( shader, GL_INFO_LOG_LENGTH, &MaxLength ); char* InfoLog = new char[MaxLength]; glGetShaderInfoLog( shader, MaxLength, &MaxLength, InfoLog ); LOGI( "Shader info log: %s\n", InfoLog ); return shader; }
How it works…
We will not go into all the details about the OpenGL ES programming here, and will instead focus on a minimal application (App3
) that should initialize the GLView
in Java; create fragment and vertex programs, create and fill the vertex array consisting of two triangles that form a single quadrilateral, and then render them with a texture, which is updated from g_FrameBuffer
contents. This is it—just draw the offscreen framebuffer. The following is the code to draw the full-screen quad textured with the offscreen buffer content:
const GLfloat vVertices[] = { -1.0f, -1.0f, 0.0f, -1.0f, 1.0f, 0.0f, 1.0f, -1.0f, 0.0f, -1.0f, 1.0f, 0.0f, 1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 0.0f }; const GLfloat vCoords[] = { 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f }; glUseProgram ( g_ProgramObject );
These attribute variables are declared in a vertex shader. See the value of g_vShaderStr[]
in the preceding code.
GLint Loc1 = glGetAttribLocation(g_ProgramObject,"vPosition"); GLint Loc2 = glGetAttribLocation(g_ProgramObject,"vCoords"); glBindBuffer( GL_ARRAY_BUFFER, 0 ); glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 ); glVertexAttribPointer( Loc1, 3, GL_FLOAT, GL_FALSE, 0, vVertices ); glVertexAttribPointer( Loc2, 3, GL_FLOAT, GL_FALSE, 0, vCoords ); glEnableVertexAttribArray( Loc1 ); glEnableVertexAttribArray( Loc2 ); glDisable( GL_DEPTH_TEST ); glDrawArrays( GL_TRIANGLES, 0, 6 ); glUseProgram( 0 ); glDisableVertexAttribArray( Loc1 ); glDisableVertexAttribArray( Loc2 );
We also need a few JNI callbacks. The first one handles the surface size changes, as seen in the following code:
JNIEXPORT void JNICALLJava_com_packtpub_ndkcookbook_app3_App3Activity_SetSurfaceSize(JNIEnv* env, jclass clazz, int Width, int Height ) { LOGI( "SurfaceSize: %i x %i", Width, Height ); g_Width = Width; g_Height = Height; GLDebug_LoadStaticProgramObject(); glGenTextures( 1, &g_Texture ); glBindTexture( GL_TEXTURE_2D, g_Texture );
Disable mip-mapping through the following code:
glTexParameteri( GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_NEAREST ); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA,ImageWidth, ImageHeight, 0, GL_RGBA,GL_UNSIGNED_BYTE, g_FrameBuffer ); }
The second callback does the actual frame rendering:
JNIEXPORT void JNICALL Java_com_packtpub_ndkcookbook_app3_App3Activity_DrawFrame( JNIEnv* env, jobject obj ) {
Invoke our frame rendering callback through the following code:
OnDrawFrame(); glActiveTexture( GL_TEXTURE0 ); glBindTexture( GL_TEXTURE_2D, g_Texture ); glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0,ImageWidth, ImageHeight, GL_RGBA,GL_UNSIGNED_BYTE, g_FrameBuffer ); GLDebug_RenderTriangle(); }