© Jan Newmarch 2017

Jan Newmarch, Raspberry Pi GPU Audio Video Programming , 10.1007/978-1-4842-2472-4_6

6. OpenGL ES on the Raspberry Pi

Jan Newmarch

(1)Oakleigh, Victoria, Australia

OpenGL ES is one of the means of drawing graphics on the RPi . It is an extremely sophisticated system that I introduce in this chapter but do not attempt to cover in detail. The major contribution of this chapter is to establish how you use Dispmanx to create an EGL surface so you can do OpenGL ES rendering. It describes a version of esUtils.c from the Munshi et al. book adapted to the RPi and gives some examples of its use. This gives the basics of what is required to run OpenGL ES programs on the RPi but is not intended to be more than an introduction to general OpenGL ES programming.

Building Programs

Download Benosteen’s OpenGL ES files for the RPi.

git clone git://github.com/benosteen/opengles-book-samples.git

This will create a directory structure in the current directory, including the RPi files you will use in this chapter in the directory opengles-book-samples/Raspi/Common. For convenience, move this up to the current directory, as shown here:

mv opengles-book-samples/Raspi/Common.

Add the following Makefile to the directory Common:

#
# Raspbery Pi library for building 'OpenGL ES 2.0 Programming Guide' examples
#
LIBES=libRPes2pg.a


DMX_INC =  -I/opt/vc/include/ -I /opt/vc/include/interface/vmcs_host/ -I/opt/vc/include/interface/vcos/pthreads -I/opt/vc/include/interface/vmcs_host/linux
EGL_INC =
GLES_INC =
INCLUDES = $(DMX_INC) $(EGL_INC) $(GLES_INC)
CFLAGS = $(INCLUDES) -DRPI_NO_X -DPI_DISPLAY=4


LIBOBJ = esUtil.o esShader.o esShapes.o esTransform.o

$(LIBES): $(LIBOBJ)
        ar rc $(LIBES) $(LIBOBJ)
        ranlib $(LIBES)


$(LIBOBJ): Makefile
clean:
        rm -f $(LIBOBJ) $(LIBES)

The purpose of this Makefileis to create the library file libRPes2pg.a. Then the programs in this chapter can be built using the Makefile.

DMX_INC =  -I/opt/vc/include/ -I /opt/vc/include/interface/vmcs_host/ -I/opt/vc/include/interface/vcos/pthreads -I/opt/vc/include/interface/vmcs_host/linux
EGL_INC =
GLES_INC = -ICommon/
INCLUDES = $(DMX_INC) $(EGL_INC) $(GLES_INC)
CFLAGS = $(INCLUDES)


DMX_LIBS =  -L/opt/vc/lib/ -lbcm_host -lvcos -lvchiq_arm -lpthread
EGL_LIBS = -L/opt/vc/lib/ -lEGL -lGLESv2
ESP_LIBS = -LCommon -lRPes2pg
LDLIBS = $(DMX_LIBS) $(EGL_LIBS) $(ESP_LIBS) -lm


SRC = Hello_Triangle Hello_TriangleColour Simple_Image Simple_Texture2D Rotate_Image Hello_Square

all: $(SRC)

$(SRC): Common/libRPes2pg.a

Common/libRPes2pg.a:
    make -C Common

OpenGL ES

In this chapter, you actually get to draw things, using the OpenGL ES API supported by the RPi. OpenGL was first specified in 1992, arising out of earlier systems by Silicon Graphics . Obviously, being so old, it has accreted a certain amount of fluff. OpenGL ES is a simplified form, more suitable to low-profile computers such as the RPi. OpenGL ES is now up to version 3, but the RPi supports only version 2. (Actually, it also supports version 1, but we will ignore that.)

Note that the latest Raspbian image from February 2016 has experimental support for full OpenGL. This book will not discuss this; I would expect its main use would be to build in support for the forthcoming Vulkan graphics system .

OpenGL ES is a significantly complex system; generating 2D and 3D images is an exceedingly nontrivial task! The canonical reference for this API is the OpenGL ES 2.0 Programming Guide by Aaftab Munshi, Dan Ginsburg, and Dave Shreiner. It is nearly 500 pages and is packed with information about how to program OpenGL ES.

Obviously, I cannot hope to replicate all the material in that book. Nevertheless, I will address the issues of getting OpenGL ES running on the RPi and show you a number of simple examples.

The esUtil Functions

The Munshi et al. book tries to present a platform-independent view of OpenGL ES programming so that the example programs will run essentially unchanged across multiple platforms, including Linux, Windows, the iPhone, and Android. To do this, it has to abstract above the OS-dependent layers . It does so with a set of functions wrapped in files such as esUtil.c, one version for each OS.

The original book did not contain any version of this file for the RPi (it hadn’t been invented then), but authors such as Benosteen have created versions. His version contains code that will run both under the X Window System and as stand-alone; I will cover the stand-alone version.

The esUtil.c file essentially defines four functions.

  • esInitContext initializes their framework .

  • esCreateWindowcreates an EGLContext for drawing.

  • esRegisterDrawFuncregisters a suitable OpenGL ES drawing function.

  • esMainLoopdraws things in a loop.

The library uses a struct .

typedef struct _escontext
{
   void*       userData;
   GLint       width;
   GLint       height;
   EGLNativeWindowType  hWnd;
   EGLDisplay  eglDisplay;
   EGLContext  eglContext;
   EGLSurface  eglSurface;
   void (ESCALLBACK *drawFunc) ( struct _escontext * );
   void (ESCALLBACK *keyFunc) ( struct _escontext *, unsigned char, int, int );
   void (ESCALLBACK *updateFunc) ( struct _escontext *, float deltaTime );
} ESContext;

This gives OS-independent information needed for drawing OpenGL ES. The four fields of EGLNativeWindowType, EGLDisplay, EGLContext, and EGLSurface were all covered for the RPi in previous chapters and are defined for the RPi as follows:

///
// CreateEGLContext()
//
//    Creates an EGL rendering context and all associated elements
//
EGLBoolean CreateEGLContext  (EGLNativeWindowType hWnd, EGLDisplay* eglDisplay,
                              EGLContext* eglContext, EGLSurface* eglSurface,
                              EGLint attribList[])
{
   EGLint numConfigs;
   EGLint majorVersion;
   EGLint minorVersion;
   EGLDisplay display;
   EGLContext context;
   EGLSurface surface;
   EGLConfig config;
   EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE, EGL_NONE };


   // Get Display
   display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
   if ( display == EGL_NO_DISPLAY )
   {
      return EGL_FALSE;
   }


   // Initialize EGL
   if ( !eglInitialize(display, &majorVersion, &minorVersion) )
   {
      return EGL_FALSE;
   }


   // Get configs
   if ( !eglGetConfigs(display, NULL, 0, νmConfigs) )
   {
      return EGL_FALSE;
   }


   // Choose config
   if ( !eglChooseConfig(display, attribList, &config, 1, &numConfigs) )
   {
      return EGL_FALSE;
   }


   // Create a surface
   surface = eglCreateWindowSurface(display, config, (EGLNativeWindowType)hWnd, NULL);
   if ( surface == EGL_NO_SURFACE )
   {
      return EGL_FALSE;
   }


   // Create a GL context
   context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs );
   if ( context == EGL_NO_CONTEXT )
   {
      return EGL_FALSE;
   }  


   // Make the context current
   if ( !eglMakeCurrent(display, surface, surface, context) )
   {
      return EGL_FALSE;
   }


   *eglDisplay = display;
   *eglSurface = surface;
   *eglContext = context;
   return EGL_TRUE;
}


///
//  WinCreate()
//
//      This function initialized the native X11 display and window for EGL
//
EGLBoolean WinCreate(ESContext *esContext, const char *title)
{
    int32_t success = 0;  
    uint32_t screen_width;
    uint32_t screen_height;


    EGL_DISPMANX_WINDOW_T *nativewindow = malloc(sizeof(EGL_DISPMANX_WINDOW_T));
    DISPMANX_ELEMENT_HANDLE_T dispman_element;
    DISPMANX_DISPLAY_HANDLE_T dispman_display;
    DISPMANX_UPDATE_HANDLE_T dispman_update;
    VC_RECT_T dst_rect;
    VC_RECT_T src_rect;


    bcm_host_init();

    // create an EGL window surface
    success = graphics_get_display_size(0 /* LCD */,
                                        &screen_width,
                                        &screen_height);
    assert( success >= 0 );


    dst_rect.x = 0;
    dst_rect.y = 0;
    dst_rect.width = screen_width;
    dst_rect.height = screen_height;


    src_rect.x = 0;
    src_rect.y = 0;
    src_rect.width = screen_width << 16;
    src_rect.height = screen_height << 16;      


    dispman_display = vc_dispmanx_display_open( 0 /* LCD */);
    dispman_update = vc_dispmanx_update_start( 0 );


    dispman_element =
        vc_dispmanx_element_add(dispman_update, dispman_display,
                                0/*layer*/, &dst_rect, 0/*src*/,
                                &src_rect, DISPMANX_PROTECTION_NONE,
                                0 /*alpha*/, 0/*clamp*/, 0/*transform*/);


    // Build an EGL_DISPMANX_WINDOW_T from the Dispmanx window
    nativewindow->element = dispman_element;
    nativewindow->width = screen_width;
    nativewindow->height = screen_height;
    vc_dispmanx_update_submit_sync(dispman_update);


    printf("Got a Dispmanx window ");

    esContext->hWnd = (EGLNativeWindowType) nativewindow;

    return EGL_TRUE;
}

These are used in a typical application like this, where UserData is some application-dependent structure :

   ESContext esContext;
   UserData  userData;


   esInitContext ( &esContext );
   esContext.userData = &userData;


   esCreateWindow ( &esContext, "My application", 320, 240, ES_WINDOW_RGB );

Vertices

A key data structure for drawing is the vertex, which is a point in two- or three-dimensional space. Each vertex may have attributes associated with it such as a color. A line has two vertices, a triangle has three, a square has four, and a circle has as many points around the circumference as are needed to look like a circle.

A435266_1_En_6_Figa_HTML.jpg
Figure 6-1. OpenGL ES coordinate system
A435266_1_En_6_Figb_HTML.jpg
Figure 6-2. Triangle in the coordinate system

Using three-dimensional (x, y, z) coordinates, you can specify the vertices of a triangle by doing the following:

GLfloat vVertices[] = {  0.0f,  0.5f, 0.0f,
                        -0.5f, -0.5f, 0.0f,
                         0.5f, -0.5f, 0.0f };

Note that the coordinate system ( www.matrix44.net/cms/notes/opengl-3d-graphics/coordinate-systems-in-opengl ) for OpenGL ES is so that this triangle is (but note, you haven’t given the vertices any color yet, so it isn’t going to appear red until you do.)

Shaders

Shaders are programs that run on the GPU rather than on the CPU. There is a communications pipeline from the CPU to the GPU : the CPU sends programs and data to the GPU, which then executes them. There are two types of programs that run on the GPU: vertex shaders and fragment shaders. Simplifying horribly dramatically the relationship between these is

A435266_1_En_6_Figc_HTML.jpg
Figure 6-3. Communications path

The vertex shader is handed vertices one at a time and performs some operation such as moving the vertex to a new position. The OpenGL ES pipeline will then work out which pixels should be drawn and hand them one at a time to the fragment shader, which works out the color of the pixel.

How does OpenGL ES know which pixels are to be drawn? Because the application will have said, “Draw triangles incorporating these triplets of vertices and fill them in” (more details soon).

So, what do shaders look like? They are in a C-like language, custom designed for OpenGL ES. The language supports all the operations required for graphics and is quite extensive. I will need to cover some of it in the sequel. The specification is called the OpenGL ES Shading Language ( https://www.khronos.org/files/opengles_shading_language.pdf ).

Minimal Vertex Shader

The following is a really simple vertex shader:

attribute vec4 vPosition;
void main()
{
   gl_Position = vPosition;
}

This takes a parameter called vPosition and assigns it to the variable gl_Position. Every vertex shader must assign a value to this variable. This shader is taking an input value for the location of the vertex and assigning the final location of that vertex to the same value.

The data type of gl_Position is a four-dimensional vector (x, y, z, w). The first three components are the (x, y, z) values of the vertex, while the fourth is typically set to 1.

The shader is not C code; it is not compiled by the application. It is passed as a string to the GPU and compiled at runtime by the GPU.

Minimal Fragment Shader

A fragment shader is responsible for setting a color on a pixel. A color is also a four-dimensional vector, of the form (red, green, blue, alpha), with all values between 0 and 1. The alpha value is the amount of “transparency,” where 1 means opaque and 0 means transparent.

A minimal fragment shader just sets the pixel to opaque red.

void main()
{
   gl_FragColor = vec4 ( 1.0, 0.0, 0.0, 1.0 );
}

The variable gl_FragColor must be assigned a value.

This also is not compiled by the application but is passed as a string to the GPU, which compiles it at runtime.

Loading the Shaders

The shaders are given as source code strings to OpenGL ES. You have to create a shader object, tell it the source code, compile the shader, and check for errors. The code for this is as follows:

GLuint LoadShader ( GLenum type, const char *shaderSrc )
{
   GLuint shader;
   GLint compiled;


   // Create the shader object
   shader = glCreateShader ( type );


   if ( shader == 0 )
        return 0;


   // Load the shader source
   glShaderSource ( shader, 1, &shaderSrc, NULL );


   // Compile the shader
   glCompileShader ( shader );


   // Check the compile status
   glGetShaderiv ( shader, GL_COMPILE_STATUS, &compiled );


   if ( !compiled )
   {
      GLint infoLen = 0;


      glGetShaderiv ( shader, GL_INFO_LOG_LENGTH, &infoLen );

      if ( infoLen > 1 )
      {
         char* infoLog = malloc (sizeof(char) * infoLen );
         char* infoLog = malloc (sizeof(char) * infoLen );


         glGetShaderInfoLog ( shader, infoLen, NULL, infoLog );
         esLogMessage ( "Error compiling shader: %s ", infoLog );          


         free ( infoLog );
      }


      glDeleteShader ( shader );
      return 0;
   }


   return shader;

}

Creating the Program Object

The OpenGL ES program to run in the GPU must be created and the shaders attached. This is a bit messy as you have to define the shaders, load the shaders, create the program that will run the shaders, attach the shaders to the program, add the information about the attributes (here vPosition), link the program, and take error correction action if it fails.

The typical code to do this is as follows:

int Init ( ESContext *esContext )
{
   esContext->userData = malloc(sizeof(UserData));


   UserData *userData = esContext->userData;
   GLbyte vShaderStr[] =
      "attribute vec4 vPosition;     "
      "void main()                   "
      "{                             "
      "   gl_Position = vPosition;   "
      "}                             ";


   GLbyte fShaderStr[] =
       "void main()                                  "
      "{                                             "
      "  gl_FragColor = vec4 ( 1.0, 0.0, 0.0, 1.0 ); "
      "}                                             ";


   GLuint vertexShader;
   GLuint fragmentShader;
   GLuint programObject;
   GLint linked;


   // Load the vertex/fragment shaders
   vertexShader = LoadShader ( GL_VERTEX_SHADER, vShaderStr );
   fragmentShader = LoadShader ( GL_FRAGMENT_SHADER, fShaderStr );


   // Create the program object
   programObject = glCreateProgram ( );


   if ( programObject == 0 )
      return 0;


   glAttachShader ( programObject, vertexShader );
   glAttachShader ( programObject, fragmentShader );


   // Bind vPosition to attribute 0  
   glBindAttribLocation ( programObject, 0, "vPosition" );


   // Link the program
   glLinkProgram ( programObject );


   // Check the link status
   glGetProgramiv ( programObject, GL_LINK_STATUS, &linked );


   if ( !linked )
   {
      GLint infoLen = 0;


      glGetProgramiv ( programObject, GL_INFO_LOG_LENGTH, &infoLen );

      if ( infoLen > 1 )
      {
         char* infoLog = malloc (sizeof(char) * infoLen );


         glGetProgramInfoLog ( programObject, infoLen, NULL, infoLog );
         esLogMessage ( "Error linking program: %s ", infoLog );          


         free ( infoLog );
      }


      glDeleteProgram ( programObject );
      return GL_FALSE;
   }


   // Store the program object
   userData->programObject = programObject;


   glClearColor ( 0.0f, 0.0f, 0.0f, 0.0f );
   return GL_TRUE;
}

Drawing Something

That’s a lot of code so far, and we still haven’t drawn anything! That’s the purpose of the next function, which will set up the array of vertices, set a viewport in which to see the drawing, establish the vertices to be drawn, and finally draw them.

Establishing the vertices involves first setting the active program by glUseProgram; multiple programs can exist at any one time, but only one is active for drawing (you have defined only one so far).

The array of vertices given as C code must then be set so that OpenGL ES can hand each vertex to the attribute vPosition. The array is, for example, the set of triangle vertices that you already saw early on.

GLfloat vVertices[] = {  0.0f,  0.5f, 0.0f,
                        -0.5f, -0.5f, 0.0f,
                         0.5f, -0.5f, 0.0f };

The attribute that will be set to each vertex in turn is vPosition in the vertex shader. In the C code, you don’t use the string vPosition to identify it, but instead use the integer index 0 that you set once when creating and linking the program with the following:

// Bind vPosition to attribute 0  
glBindAttribLocation ( programObject, 0, "vPosition" );

The link is made with the following function:

VertexAttribPointer( uint index, int size, enum type,
                     boolean normalized, sizei stride, const
                     void *pointer );

where you specify 0 for the index attribute, 3 for the number of elements in each vertex (for x, y and z), GL_FLOAT for the data type of each array element, GL_FALSE for normalized data, and a stride of 0, meaning that you have only vertex data and nothing else in the vertex array.

glVertexAttribPointer ( 0, 3, GL_FLOAT, GL_FALSE, 0, vVertices );

One last little wrinkle and then you can draw: attributes by default are disabled, meaning that the C data will not be given to the attribute. The attribute (with index 0) must be enabled with the following:

glEnableVertexAttribArray ( 0 );

Now you draw using the function glDrawArrays, which takes three parameters: the drawing mode (you will use triangle mode for the single triangle), the initial vertex in the vertex array, and the number of vertices to be drawn.

glDrawArrays ( GL_TRIANGLES, 0, 3 );

The draw function is as follows:

void Draw ( ESContext *esContext )
{
   UserData *userData = esContext->userData;
   GLfloat vVertices[] = {  0.0f,  0.5f, 0.0f,
                           -0.5f, -0.5f, 0.0f,
                            0.5f, -0.5f, 0.0f };


   // Set the viewport
   glViewport ( 0, 0, esContext->width, esContext->height );


   // Clear the color buffer
   glClear ( GL_COLOR_BUFFER_BIT );


   // Use the program object
   glUseProgram ( userData->programObject );


   // Load the vertex data
   glVertexAttribPointer ( 0, 3, GL_FLOAT, GL_FALSE, 0, vVertices );
   glEnableVertexAttribArray ( 0 );


   glDrawArrays ( GL_TRIANGLES, 0, 3 );
}

Putting It Together

The main function brings it all together.

int main ( int argc, char *argv[] )
{
   ESContext esContext;
   UserData  userData;


   esInitContext ( &esContext );
   esContext.userData = &userData;


   esCreateWindow ( &esContext, "Hello Triangle", 320, 240, ES_WINDOW_RGB );

   if ( !Init ( &esContext ) )
      return 0;


   esRegisterDrawFunc ( &esContext, Draw );

   esMainLoop ( &esContext );
}

Drawing an Opaque Red Triangle

If you now take the set of functions from esUtil.c, the convenience functions defined earlier with the special values for arrays and shaders, then you can finally draw a red opaque triangle. The program is Hello_Triangle.c.

//
// Book:      OpenGL(R) ES 2.0 Programming Guide
// Authors:   Aaftab Munshi, Dan Ginsburg, Dave Shreiner
// ISBN-10:   0321502795
// ISBN-13:   9780321502797
// Publisher: Addison-Wesley Professional
// URLs:      http://safari.informit.com/9780321563835
//            http://www.opengles-book.com
//


// Hello_Triangle.c
//
//    This is a simple example that draws a single triangle with
//    a minimal vertex/fragment shader.  The purpose of this
//    example is to demonstrate the basic concepts of
//    OpenGL ES 2.0 rendering.
#include <stdlib.h>
#include "esUtil.h"


typedef struct
{
   // Handle to a program object
   GLuint programObject;


} UserData;

///
// Create a shader object, load the shader source, and
// compile the shader.
//
GLuint LoadShader ( GLenum type, const char *shaderSrc )
{
   GLuint shader;
   GLint compiled;


   // Create the shader object
   shader = glCreateShader ( type );


   if ( shader == 0 )
        return 0;


   // Load the shader source
   glShaderSource ( shader, 1, &shaderSrc, NULL );


   // Compile the shader
   glCompileShader ( shader );


   // Check the compile status
   glGetShaderiv ( shader, GL_COMPILE_STATUS, &compiled );


   if ( !compiled )
   {
      GLint infoLen = 0;


      glGetShaderiv ( shader, GL_INFO_LOG_LENGTH, &infoLen );

      if ( infoLen > 1 )
      {
         char* infoLog = malloc (sizeof(char) * infoLen );


         glGetShaderInfoLog ( shader, infoLen, NULL, infoLog );
         esLogMessage ( "Error compiling shader: %s ", infoLog );          


         free ( infoLog );
      }


      glDeleteShader ( shader );
      return 0;
   }


   return shader;

}

///
// Initialize the shader and program object
//
int Init ( ESContext *esContext )
{
   esContext->userData = malloc(sizeof(UserData));


   UserData *userData = esContext->userData;
   GLbyte vShaderStr[] =
      "attribute vec4 vPosition;     "
      "void main()                   "
      "{                             "
      "   gl_Position = vPosition;   "
      "}                             ";


   GLbyte fShaderStr[] =
      "precision mediump float; "
      "void main()                                   "
      "{                                             "
      "  gl_FragColor = vec4 ( 1.0, 0.0, 0.0, 1.0 ); "
      "}                                             ";


   GLuint vertexShader;
   GLuint fragmentShader;
   GLuint programObject;
   GLint linked;


   // Load the vertex/fragment shaders
   vertexShader = LoadShader ( GL_VERTEX_SHADER, vShaderStr );
   fragmentShader = LoadShader ( GL_FRAGMENT_SHADER, fShaderStr );


   // Create the program object
   programObject = glCreateProgram ( );


   if ( programObject == 0 )
      return 0;


   glAttachShader ( programObject, vertexShader );
   glAttachShader ( programObject, fragmentShader );


   // Bind vPosition to attribute 0  
   glBindAttribLocation ( programObject, 0, "vPosition" );


   // Link the program
   glLinkProgram ( programObject );


   // Check the link status
   glGetProgramiv ( programObject, GL_LINK_STATUS, &linked );


   if ( !linked )
   {
      GLint infoLen = 0;


      glGetProgramiv ( programObject, GL_INFO_LOG_LENGTH, &infoLen );

      if ( infoLen > 1 )
      {
         char* infoLog = malloc (sizeof(char) * infoLen );


         glGetProgramInfoLog ( programObject, infoLen, NULL, infoLog );
         esLogMessage ( "Error linking program: %s ", infoLog );          


         free ( infoLog );
      }


      glDeleteProgram ( programObject );
      return GL_FALSE;
   }


   // Store the program object
   userData->programObject = programObject;


   glClearColor ( 0.0f, 0.0f, 0.0f, 0.0f );
   return GL_TRUE;
}


///
// Draw a triangle using the shader pair created in Init()
//
void Draw ( ESContext *esContext )
{
   UserData *userData = esContext->userData;
   GLfloat vVertices[] = {  0.0f,  0.5f, 0.0f,
                           -0.5f, -0.5f, 0.0f,
                            0.5f, -0.5f, 0.0f };


   // Set the viewport
   glViewport ( 0, 0, esContext->width, esContext->height );


   // Clear the color buffer
   glClear ( GL_COLOR_BUFFER_BIT );


   // Use the program object
   glUseProgram ( userData->programObject );


   // Load the vertex data
   glVertexAttribPointer ( 0, 3, GL_FLOAT, GL_FALSE, 0, vVertices );
   glEnableVertexAttribArray ( 0 );


   glDrawArrays ( GL_TRIANGLES, 0, 3 );
}


int main ( int argc, char *argv[] )
{
   ESContext esContext;
   UserData  userData;


   esInitContext ( &esContext );
   esContext.userData = &userData;


   esCreateWindow ( &esContext, "Hello Triangle", 320, 240, ES_WINDOW_RGB );

   if ( !Init ( &esContext ) )
      return 0;


   esRegisterDrawFunc ( &esContext, Draw );

   esMainLoop ( &esContext );
}

The program is run by calling Hello_Triangle and draws a small red triangle.

A Colored Triangle

In the previous example, you drew a triangle with a solid red color. This was done by setting the fragment shader variable fragmentColor to opaque red. But what if you wanted to get a color triangle?

A435266_1_En_6_Figd_HTML.jpg
Figure 6-4. Coloured triangle

Do you have to calculate all of the color values yourself?

Varyings

You could do it by yourself, but fortunately OpenGL ES can do the work for you. It has the concept of varyings, which are variables with values interpolated from the vertex shader data. For the colored triangle, the interpolated values are calculated using the vertex colors, averaged out based on the distance from each vertex.

To use them, you define a variable to be varying in both the vertex and the fragment shader, with the same name. You set a value in each vertex using a new attribute and then can access the interpolated value in each fragment shader. The vertex shader is as follows:

attribute vec4 vPosition;
attribute vec4 vColour;


varying vFragmentColour;

void main()
{
   gl_Position = vPosition;
   vFragmentColour = vColour;
}    

The fragment shader is as follows:

varying vec4 vFragmentColour;

void main()
{
   gl_FragColor = vFragmentColour;
}

Passing Multiple Attributes to the Vertex Shader

In the simple triangle program, you passed a set of vertex values to the vertex shader by first setting the index of the attribute to 0 and then calling the vertex attribute pointer function. To add more attributes, the simplest way (but not the most efficient) is to set extra attributes to higher index values (one, two, and so on) and call the vertex attribute pointer function on these as well.

Here are the changes required to set colors for the color attribute:

   GLfloat vColours[] = {1.0f, 0.0f, 0.0f,
                         0.0f, 1.0f, 0.0f,
                         0.0f, 0.0f, 1.0f,};


   ...

   glVertexAttribPointer ( 1, 3, GL_FLOAT, GL_FALSE, 0, vColours );
   glEnableVertexAttribArray ( 1 );

Here are more changes:

   // Bind vColour to attribute 1
   glBindAttribLocation ( programObject, 1, "vColour" );

The program is Hello_TriangleColour.c. It isn’t worth including the full code here because only the previous lines differ from the Hello_Triangle.c program. It is run by Hello_TriangleColour.

Drawing Squares and Other Shapes

If you want to draw complex shapes such as squares, cubes, or even circles and spheres, you need to break them down into triangles. Squares and other regular shapes are easy to divide into triangles, but even then there can be many choices (divide on this diagonal or on that one?).

Far more complex, and more important, is how to label the vertices of these triangles, because that affects how you ask OpenGL ES to draw them. The OpenGL ES specification illustrates this with

A435266_1_En_6_Fige_HTML.jpg
Figure 6-5. Possible vertex labelling of figures

Making separate triangles is easiest for the programmer: you just have to cut the polygons into any triangles with any labeling and call glDrawArrays where every three vertices define a separate triangle. But it may cost more in processing time.

A square, for example, can be divided into two separate triangles with a total of six vertices. Doing so would look like this:

   GLfloat vVertices[] = { // first triangle
                          1.0f, -1.0f, 0.0f,
                          1.0f, 1.0f, 0.0f,
                          -1.0f, -1.0f, 0.0f,


                          // second triangle
                          -1.0f, 1.0f, 0.0f,
                          -1.0f, -1.0f, 0.0f,
                          1.0f, 1.0f, 0.0f
                         };

Here is the drawing code:

 glVertexAttribPointer ( 0, 3, GL_FLOAT, GL_FALSE, 0, vVertices );
 glEnableVertexAttribArray ( 0 );


 glDrawArrays ( GL_TRIANGLES, 0, 6 );

But a square can also be divided into a triangle strip or even a triangle fan with only four vertices. But you have to be more careful. If you want a strip, the first vertex must not be shared with any other triangle and neither can the last. Here is one possibility for a square at (±1, ±1):

   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
                         };

The square can then be drawn with the following:

 glVertexAttribPointer ( 0, 3, GL_FLOAT, GL_FALSE, 0, vVertices );
 glEnableVertexAttribArray ( 0 );


 glDrawArrays ( GL_TRIANGLE_STRIP, 0, 4 );

An alternative mechanism uses the vertex arrays as shown earlier but draws elements by explicit indexes into this array rather than the implicit indexes caused by the element ordering. This makes use of another array, this time of the indices. The vertex array must be used too, of course.

Even if an index array is used, there are still the choices of drawing individual triangles, strips, or fans. Using triangles, the code is as follows:

   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
                         };
   GLubyte vIndices[] = {
                         0, 1, 2,
                         3, 4, 5
                        };

(This is usually optimized by reusing some of the vertices as {0, 1, 2, 3, 2, 1} and omitting the last two entries of the vertices array.) The square can then be drawn with the following:

 glVertexAttribPointer ( 0, 3, GL_FLOAT, GL_FALSE, 0, vVertices );
 glEnableVertexAttribArray ( 0 );


 glDrawElements ( GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, vIndices );

Using strips, the code is as follows:

   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
                         };
   GLubyte vIndices[] = {
                         0, 1, 2, 3
                        };

The square can then be drawn with the following :

 glVertexAttribPointer ( 0, 3, GL_FLOAT, GL_FALSE, 0, vVertices );
 glEnableVertexAttribArray ( 0 );


 glDrawElements ( GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, vIndices );

These possibilities can all be seen in a revised Draw function, where the different choices described earlier can be made by setting TRIANGLES and ELEMENTS to 0 or 1:

void Draw ( ESContext *esContext )
{
#define TRIANGLES 1
#define ELEMENTS 1


   UserData *userData = esContext->userData;
   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,
#if TRIANGLES
                          -1.0f, -1.0f, 0.0f,
                          1.0f, 1.0f, 0.0f
#endif
                         };


#if ELEMENTS
   GLubyte vIndices[] = {
                         0, 1, 2,
                         3,
#  if TRIANGLES
                         4, 5
#  endif
                        };
#endif


   // Set the viewport
   glViewport ( 0, 0, esContext->width, esContext->height );


   // Clear the color buffer
   glClear ( GL_COLOR_BUFFER_BIT );


   // Use the program object
   glUseProgram ( userData->programObject );


   // Load the vertex data
   glVertexAttribPointer ( 0, 3, GL_FLOAT, GL_FALSE, 0, vVertices );
   glEnableVertexAttribArray ( 0 );


#if TRIANGLES
#  if ELEMENTS
   glDrawElements ( GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, vIndices );
#  else
   glDrawArrays ( GL_TRIANGLES, 0, 6 );
#  endif
#else
#  if ELEMENTS
   glDrawElements ( GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, vIndices );
#  else
   glDrawArrays ( GL_TRIANGLE_STRIP, 0, 4 );
#  endif
#endif
}

The full program is Hello_Square.c and is run by Hello_Square.

Which to Choose?

The choice of triangles, strip, or fan will depend on the geometry of the shape and any semantic context it might have; for example, drawing a wall of rectangles would use a strip, while drawing circles would use a fan.

The choice of drawing arrays or elements usually comes down in favor of elements for efficiency.

Textures

Textures are another means of filling triangles. A texture is image data that can be manipulated by the fragment shader. Textures can be two-dimensional rectangular images or the six sides of a cube. In this section, you will look only at two-dimensional images. Textures will typically come from image data stored in files or hard-coded into the program. The Munshi et al. book uses a simple hard-coded 2x2 image. Here you will take a file image.

TGA Files

There are many, many different file formats—some lossy, some lossless, some compressed, some not, some with metadata, some without. In this section, you will just use TGA ( www.fileformat.info/format/tga/egff.htm ), which is an uncompressed format with enough useful metadata that is simple to load.

TGA files can be created from, for example, JPEG files by using the convert utility from the Gimp drawing system. It is simple to convert a file from JPEG to TGA format. You just give the appropriate file extensions.

convert image.jpg image.tga

A TGA file has a header section that gives the width and height of the image from which its size can be calculated. The default format will be RGB, as in 24-bit pixels. Reading in such a file is just a matter of locating the dimensions, malloc’ing the right size buffer, skipping to the start of the image data, and reading it all in.

The esUtil.c file includes a function called esLoadTGAthat will read in an image, returning the image and its dimensions.

The default is for the origin of the image to be the top-left corner, with the y-axis growing down . OpenGL ES, on the other hand, has the origin in the bottom-left corner with the y-axis growing up. So, the image will be upside down relative to the OpenGL coordinates. This can be fixed by reading the data in differently or by using an OpenGL ES reflection. You will map the texture upside down to give the correct orientation.

Mipmaps

An application may need to render an image in fine detail or, say, if it is far away, in only coarse detail. To avoid GPU load, multiple images can be given for one texture at varying levels of detail. I won’t cover that here and will just set one image for all levels of detail.

Creating a Texture Object

Textures are stored in texture objects that have to be created using an image with a known format, dimensions, and pixel values. Assuming you have stored the width, height, and image in the userData field of an esContext, where the format is known to be RGB as unsigned bytes, the code is as follows:

GLuint CreateSimpleTexture2D(ESContext *esContext)
{
   // Texture object handle
   GLuint textureId;
   UserData *userData = esContext->userData;
   char *pixels = userData->image;


   // Use tightly packed data
   glPixelStorei ( GL_UNPACK_ALIGNMENT, 1 );


   // Generate a texture object
   glGenTextures ( 1, &textureId );


   // Bind the texture object
   glBindTexture ( GL_TEXTURE_2D, textureId );


   // Load the texture
   glTexImage2D ( GL_TEXTURE_2D, 0, GL_RGB,
                  userData->width, userData->height,
                  0, GL_RGB, GL_UNSIGNED_BYTE, pixels );


   // Set the filtering mode
   glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
   glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );


   return textureId;

}

Texture Attributes and the Shaders

Two-dimensional textures will only need (x, y) values as coordinates, unlike vertex coordinates that are described by four coordinates (x, y, z, w). The vertices of the 2D texture will need to be fed into the vertex shaders in order that the texture can be properly located within the final rendering. This will need a parameter within the shader of type vec2. The texture coordinates are limited to the square with vertices (0, 0), (1, 0), (0, 1), and (1, 1).

The fragment shader will need a varying texture coordinate, which would be initialized by the vertex shader but then interpolated. This will need to be integrated with the image pixel values, and this is done by a fragment shader function texture2D.

The vertex shader program will typically look like this:

      attribute vec4 a_position;
      attribute vec2 a_texCoord;
      varying vec2 v_texCoord;
      void main()            
      {                      
         gl_Position = a_position;
         v_texCoord = a_texCoord;
      }                        

The fragment shader will be as follows:

      precision mediump float;  
      varying vec2 v_texCoord;  
      uniform sampler2D s_texture;
      void main()                
      {                            
        gl_FragColor = texture2D( s_texture, v_texCoord );
      }                                                

These are all combined into a revised Init function as follows :

int Init ( ESContext *esContext )
{
    UserData *userData = esContext->userData;
    GLbyte vShaderStr[] =
      "attribute vec4 a_position;    "
      "attribute vec2 a_texCoord;    "
      "varying vec2 v_texCoord;      "
      "void main()                   "
      "{                             "
      "   gl_Position = a_position; "
      "   v_texCoord = a_texCoord;   "
      "}                             ";


    GLbyte fShaderStr[] =
      "precision mediump float;                             "
      "varying vec2 v_texCoord;                             "
      "uniform sampler2D s_texture;                         "
      "void main()                                          "
      "{                                                    "
      "  gl_FragColor = texture2D( s_texture, v_texCoord ); "
      "}                                                    ";


   // Load the shaders and get a linked program object
   userData->programObject = esLoadProgram ( vShaderStr, fShaderStr );


   // Get the attribute locations
   userData->positionLoc = glGetAttribLocation ( userData->programObject, "a_position" );
   userData->texCoordLoc = glGetAttribLocation ( userData->programObject, "a_texCoord" );


   // Get the sampler location
   userData->samplerLoc = glGetUniformLocation ( userData->programObject, "s_texture" );


   // Load the texture
   userData->textureId = CreateSimpleTexture2D (esContext);


   glClearColor ( 0.0f, 0.0f, 0.0f, 0.0f );
   return GL_TRUE;
}

Drawing the Texture

To draw, you need to pass in the vertex coordinates. You will draw a square for the vertices. For the texture coordinates, you also need to specify their four coordinates. This could be done using two arrays as follows:

   GLfloat vVertices[] = { -0.5f,  0.5f, 0.0f,  // Position 0
                           -0.5f, -0.5f, 0.0f,  // Position 1
                            0.5f, -0.5f, 0.0f,  // Position 2
                            0.5f,  0.5f, 0.0f,  // Position 3
                         };
    GLfloat tVertices[] = {
                            0.0f,  1.0f,        // TexCoord 0
                            0.0f,  0.0f,        // TexCoord 1
                            1.0f,  0.0f,        // TexCoord 2
                            1.0f,  1.0f         // TexCoord 3
                         };

More commonly, these arrays would be interleaved as follows:

    GLfloat vVertices[] = { -0.5f,  0.5f, 0.0f,  // Position 0
                            0.0f,  1.0f,        // TexCoord 0
                           -0.5f, -0.5f, 0.0f,  // Position 1
                            0.0f,  0.0f,        // TexCoord 1
                            0.5f, -0.5f, 0.0f,  // Position 2
                            1.0f,  0.0f,        // TexCoord 2
                            0.5f,  0.5f, 0.0f,  // Position 3
                            1.0f,  1.0f         // TexCoord 3
                         };

(Note that you are rendering the texture upside down to compensate for it being upside down relative to OpenGL ES coordinates.)

The question then is how to get—separately—the two sets of vertices. This is where the stride parameter comes into play in the call to glVertexAttribPointer. Basically, you want to say the following:

Each vertex co-ordinate occurs in a set of three in every five values,
   starting at index zero
Each texture co-ordinate occurs in a set of two in every five values,
   starting at index three

The code for this is as follows:

   // Load the vertex position
   glVertexAttribPointer ( userData->positionLoc, 3, GL_FLOAT,
                           GL_FALSE, 5 * sizeof(GLfloat), &vVertices[0] );
   // Load the texture coordinate
   glVertexAttribPointer ( userData-gt;texCoordLoc, 2, GL_FLOAT,
                           GL_FALSE, 5 * sizeof(GLfloat), &vVertices[3] );

The drawing code is then as follows:

void Draw ( ESContext *esContext )
{
   UserData *userData = esContext->userData;
   GLfloat vVertices[] = { -0.5f,  0.5f, 0.0f,  // Position 0
                            0.0f,  1.0f,        // TexCoord 0
                           -0.5f, -0.5f, 0.0f,  // Position 1
                            0.0f,  0.0f,        // TexCoord 1
                            0.5f, -0.5f, 0.0f,  // Position 2
                            1.0f,  0.0f,        // TexCoord 2
                            0.5f,  0.5f, 0.0f,  // Position 3
                            1.0f,  1.0f         // TexCoord 3
                         };
   GLushort indices[] = { 0, 1, 2, 0, 2, 3 };


   // Set the viewport
   glViewport ( 0, 0, esContext->width, esContext->height );


   // Clear the color buffer
   glClear ( GL_COLOR_BUFFER_BIT );


   // Use the program object
   glUseProgram ( userData->programObject );


   // Load the vertex position
   glVertexAttribPointer ( userData->positionLoc, 3, GL_FLOAT,
                           GL_FALSE, 5 * sizeof(GLfloat), vVertices );
   // Load the texture coordinate
   glVertexAttribPointer ( userData->texCoordLoc, 2, GL_FLOAT,
                           GL_FALSE, 5 * sizeof(GLfloat), &vVertices[3] );


   glEnableVertexAttribArray ( userData->positionLoc );
   glEnableVertexAttribArray ( userData->texCoordLoc );


   // Bind the texture
   glActiveTexture ( GL_TEXTURE0 );
   glBindTexture ( GL_TEXTURE_2D, userData->textureId );


   // Set the sampler texture unit to 0
   glUniform1i ( userData->samplerLoc, 0 );


   glDrawElements ( GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices );

}

Complete Code for Drawing an Image

The final code is Simple_Image.c. There appears to be a minor bug in the function esLoadTGA in the Common/esUtil.c file. It gets the colors of the image a little wrong.

// Authors:   Aaftab Munshi, Dan Ginsburg, Dave Shreiner
// Copyright: the authors and Jan Newmarch


// Simple_Image.c
//
//    This is a simple example that draws an image with a 2D
//    texture image.
//
#include <stdlib.h>
#include <stdio.h>
#include "esUtil.h"


typedef struct
{
   // Handle to a program object
   GLuint programObject;


   // Attribute locations
   GLint  positionLoc;
   GLint  texCoordLoc;


   // Sampler location
   GLint samplerLoc;


   // Texture handle
   GLuint textureId;


   GLubyte *image;
    int width, height;
} UserData;


///
// Create a simple 2x2 texture image with four different colors
//
GLuint CreateSimpleTexture2D(ESContext *esContext)
{
   // Texture object handle
   GLuint textureId;
   UserData *userData = esContext->userData;


   GLubyte *pixels = userData->image;
   userData->width = esContext->width;
   userData->height = esContext->height;


   // Use tightly packed data
   glPixelStorei ( GL_UNPACK_ALIGNMENT, 1 );


   // Generate a texture object
   glGenTextures ( 1, &textureId );


   // Bind the texture object
   glBindTexture ( GL_TEXTURE_2D, textureId );


   // Load the texture
   glTexImage2D ( GL_TEXTURE_2D, 0, GL_RGB,
                  userData->width, userData->height,
                  0, GL_RGB, GL_UNSIGNED_BYTE, pixels );


   // Set the filtering mode
   glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
   glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );


   return textureId;

}

///
// Initialize the shader and program object
//
int Init ( ESContext *esContext )
{
    UserData *userData = esContext->userData;
    GLbyte vShaderStr[] =
      "attribute vec4 a_position;    "
      "attribute vec2 a_texCoord;    "
      "varying vec2 v_texCoord;      "
      "void main()                   "
      "{                             "
      "   gl_Position = a_position; "
      "   v_texCoord = a_texCoord;   "
      "}                             ";


    GLbyte fShaderStr[] =
      "precision mediump float;                             "
      "varying vec2 v_texCoord;                             "
      "uniform sampler2D s_texture;                         "
      "void main()                                          "
      "{                                                    "
      "  gl_FragColor = texture2D( s_texture, v_texCoord ); "
      "}                                                    ";


   // Load the shaders and get a linked program object
   userData->programObject = esLoadProgram ( vShaderStr, fShaderStr );


   // Get the attribute locations
   userData->positionLoc = glGetAttribLocation ( userData->programObject, "a_position" );
   userData->texCoordLoc = glGetAttribLocation ( userData->programObject, "a_texCoord" );
   http://www.netmanners.com/e-mail-etiquette-tips/
   // Get the sampler location
   userData->samplerLoc = glGetUniformLocation ( userData->programObject, "s_texture" );


   // Load the texture
   userData->textureId = CreateSimpleTexture2D (esContext);


   glClearColor ( 0.0f, 0.0f, 0.0f, 0.0f );
   return GL_TRUE;
}


///
// Draw a triangle using the shader pair created in Init()
//
void Draw ( ESContext *esContext )
{
   UserData *userData = esContext->userData;
   GLfloat vVertices[] = { -0.5f,  0.5f, 0.0f,  // Position 0
                            0.0f,  1.0f,        // TexCoord 0
                           -0.5f, -0.5f, 0.0f,  // Position 1
                            0.0f,  0.0f,        // TexCoord 1
                            0.5f, -0.5f, 0.0f,  // Position 2
                            1.0f,  0.0f,        // TexCoord 2
                            0.5f,  0.5f, 0.0f,  // Position 3http://www.netmanners.com/e-mail-etiquette-tips/
                            1.0f,  1.0f         // TexCoord 3
                         };
   GLushort indices[] = { 0, 1, 2, 0, 2, 3 };


   // Set the viewport
   glViewport ( 0, 0, esContext->width, esContext->height );


   // Clear the color buffer
   glClear ( GL_COLOR_BUFFER_BIT );


   // Use the program object
   glUseProgram ( userData->programObject );


   // Load the vertex position
   glVertexAttribPointer ( userData->positionLoc, 3, GL_FLOAT,
                           GL_FALSE, 5 * sizeof(GLfloat), vVertices );
   // Load the texture coordinate
   glVertexAttribPointer ( userData->texCoordLoc, 2, GL_FLOAT,
                           GL_FALSE, 5 * sizeof(GLfloat), &vVertices[3] );


   glEnableVertexAttribArray ( userData->positionLoc );
   glEnableVertexAttribArray ( userData->texCoordLoc );


   // Bind the texture
   glActiveTexture ( GL_TEXTURE0 );
   glBindTexture ( GL_TEXTURE_2D, userData->textureId );


   // Set the sampler texture unit to 0
   glUniform1i ( userData->samplerLoc, 0 );


   glDrawElements ( GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices );

}

///
// Cleanup
//
void ShutDown ( ESContext *esContext )
{
   UserData *userData = esContext->userData;


   // Delete texture object
   glDeleteTextures ( 1, &userData->textureId );


   // Delete program object
   glDeleteProgram ( userData->programObject );


   free(esContext->userData);
}


int main ( int argc, char *argv[] )
{
   ESContext esContext;
   UserData  userData;


   int width, height;
   GLubyte *image;


   image = esLoadTGA("jan.tga", &width, &height);
   if (image == NULL) {
       fprintf(stderr, "No such image ");
       exit(1);
   }
   printf("Width %d height %d ", width, height);


   userData.image = image;
   userData.width = width;
   userData.height = height;


   esInitContext ( &esContext );
   esContext.userData = &userData;


   esCreateWindow ( &esContext, "Simple Texture 2D", width, height, ES_WINDOW_RGB );

   if ( !Init ( &esContext ) )
      return 0;


   esRegisterDrawFunc ( &esContext, Draw );

   esMainLoop ( &esContext );

   ShutDown ( &esContext );
}

Run this by Simple_Image. The image used is of me since that is possibly more interesting than a wall tile.

Animation: Rotating an Image

In this section, you will take a brief look at some of the issues and techniques in animating the graphics display. As you will have seen from multiple computer games and digital movies, the animations can be amazingly sophisticated and also involve much programmer time, program complexity, and CPU/GPU time.

You will do about the simplest: make the image of the previous section rotate around an axis.

Matrices

I cut my teeth on matrices years ago writing programs in Algol 60 to solve simultaneous linear equations. Now it is easier, with libraries to manage most calculations. You will use various ES functions from the Munshi et al. book to simplify things.

There are several primary operations you can carry out on graphic images to fit them into scenes: you can rotate them, move them nearer or further, apply perspective views so that the “parallel lines meet at the horizon,” or do any other effects you may choose.

The operations are carried out on vectors such as the (x, y, z, w) coordinates of a vertex. The operations are represented by matrices that are square (or rectangular) arrays of numbers. The main operation is to multiply a vector by a matrix. Any book on linear algebra will give details on how to do this.

esUtil

These are the principal functions supplied by the esUtil package:

  • Create an identity matrix with esMatrixLoadIdentity

  • Multiply two matrices with esMatrixMultiply

  • Translate a vector from one point to another with esTranslate

  • Rotate a matrix through an angle around an axis with esRotate

  • Scale a matrix by a factor with esScale

  • Multiply a matrix by a perspective matrix with esPerspective

These may remove the need to explicitly specify actual matrices.

Invoking Animation

You already have a Draw function. You add to this an Update function to make the changes needed for the next Draw:

  esRegisterDrawFunc ( &esContext, Draw );
  esRegisterUpdateFunc ( &esContext, Update );

The Update function for a simple rotation can be as follows:

void Update ( ESContext *esContext, float deltaTime )
{
   UserData *userData = (UserData*) esContext->userData;


   // Compute a rotation angle based on time to rotate the cube
   userData->angle += ( deltaTime * 40.0f );
   if( userData->angle >= 360.0f )
      userData->angle -= 360.0f;


   // Generate an identity matrix before rotating the square
   esMatrixLoadIdentity(  &userData->rotateMx );


   // Rotate the square about the (1, 0, 1) axis
   esRotate(  &userData->rotateMx, userData->angle, 1.0, 0.0, 1.0 );
}

This uses a current rotation angle stored in userData and applies it to an identity matrix about a vector specified here as (x, y, z) = (1.0, 0.0, 1.0).

Uniform Parameters: The Rotation Matrix

If you are rotating an entire image, the rotation has to be applied to every pixel in the drawn image, and it is the same rotation matrix in each case. So, this matrix needs to be constant over all the vertex and fragment shaders. Such a matrix is a uniform parameter and is specified as such in both the vertex and fragment shaders.

Working out the value of a uniform parameter is something that needs to be done once in each drawing iteration; so, it is done in the application’s Update C code, not in the shaders. It is then passed in as a parameter on each draw call by glUniformMatrix4fv.

Extra fields are added to UserData for the rotation angle and rotation matrix. Apart from that and the earlier changes, the standard program structure holds. The program to rotate an image loaded from a TGA file is Rotate_Image.c.

        //
// Book:      OpenGL(R) ES 2.0 Programming Guide
// Authors:   Aaftab Munshi, Dan Ginsburg, Dave Shreiner
// ISBN-10:   0321502795
// ISBN-13:   9780321502797
// Publisher: Addison-Wesley Professional
// URLs:      http://safari.informit.com/9780321563835
//            http://www.opengles-book.com
//


// Simple_Texture2D.c
//
//    This is a simple example that draws a quad with a 2D
//    texture image. The purpose of this example is to demonstrate
//    the basics of 2D texturing
//
#include <stdlib.h>
#include <stdio.h>
#include "esUtil.h"


typedef struct
{
   // Handle to a program object
   GLuint programObject;


   // Attribute locations
   GLint  positionLoc;
   GLint  texCoordLoc;


   // Uniform locations
   GLint  rotateLoc;


   // Sampler location
   GLint samplerLoc;


   // Texture handle
   GLuint textureId;


   GLubyte *image;
    int width, height;


   // Rotation angle
   GLfloat   angle;


   // rotate matrix
   ESMatrix  rotateMx;
} UserData;


///
// Create a simple 2x2 texture image with four different colors
//
GLuint CreateSimpleTexture2D(ESContext *esContext)
{
   // Texture object handle
   GLuint textureId;
   UserData *userData = esContext->userData;


#if 0
   // 2x2 Image, 3 bytes per pixel (R, G, B)
   GLubyte pixels[4 * 3] =
   {
      255,   0,   0, // Red
        0, 255,   0, // Green
        0,   0, 255, // Blue
      255, 255,   0  // Yellow
   };
   userData->width = 2;
   userData->height = 2;
#else
   GLubyte *pixels = userData->image;
   userData->width = esContext->width;
   userData->height = esContext->height;
#endif
   // Use tightly packed data
   glPixelStorei ( GL_UNPACK_ALIGNMENT, 1 );


   // Generate a texture object
   glGenTextures ( 1, &textureId );


   // Bind the texture object
   glBindTexture ( GL_TEXTURE_2D, textureId );


   // Load the texture
   glTexImage2D ( GL_TEXTURE_2D, 0, GL_RGB,
                  userData->width, userData->height,
                  0, GL_RGB, GL_UNSIGNED_BYTE, pixels );


   // Set the filtering mode
   glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
   glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );


   return textureId;

}

///
// Initialize the shader and program object
//
int Init ( ESContext *esContext )
{
    UserData *userData = esContext->userData;
    GLbyte vShaderStr[] =
      "uniform mat4 u_rotateMx;                   "
      "attribute vec4 a_position;                 "
      "attribute vec2 a_texCoord;                 "
      "varying vec2 v_texCoord;                   "
      "void main()                                "
      "{                                          "
      "   gl_Position = u_rotateMx * a_position; "
      "   v_texCoord = a_texCoord;                "
      "}                                          ";


    GLbyte fShaderStr[] =
      "precision mediump float;                             "
      "varying vec2 v_texCoord;                             "
      "uniform sampler2D s_texture;                         "
      "void main()                                          "
      "{                                                    "
      "  gl_FragColor = texture2D( s_texture, v_texCoord ); "
      "}                                                    ";


   // Load the shaders and get a linked program object
   userData->programObject = esLoadProgram ( vShaderStr, fShaderStr );


   // Get the attribute locations
   userData->positionLoc = glGetAttribLocation ( userData->programObject, "a_position" );
   userData->texCoordLoc = glGetAttribLocation ( userData->programObject, "a_texCoord" );
   userData->rotateLoc = glGetUniformLocation( userData->programObject, "u_rotateMx" );
   // Starting rotation angle for the square
   userData->angle = 0.0f;


   // Get the sampler location
   userData->samplerLoc = glGetUniformLocation ( userData->programObject, "s_texture" );


   // Load the texture
   userData->textureId = CreateSimpleTexture2D (esContext);


   glClearColor ( 0.0f, 0.0f, 0.0f, 0.0f );  

   return GL_TRUE;
}


///
// Update rotate matrix based on time
//
void Update ( ESContext *esContext, float deltaTime )
{
   UserData *userData = (UserData*) esContext->userData;


   // Compute a rotation angle based on time to rotate the cube
   userData->angle += ( deltaTime * 40.0f );
   if( userData->angle >= 360.0f )
      userData->angle -= 360.0f;


   // Generate an identity matrix before rotating the square
   esMatrixLoadIdentity(  &userData->rotateMx );


   // Rotate the square
   esRotate(  &userData->rotateMx, userData->angle, 1.0, 0.0, 1.0 );
}


///
// Draw a triangle using the shader pair created in Init()
//
void Draw ( ESContext *esContext )
{
   UserData *userData = esContext->userData;
   GLfloat vVertices[] = { -0.5f,  0.5f, 0.0f,  // Position 0
                            0.0f,  1.0f,        // TexCoord 0
                           -0.5f, -0.5f, 0.0f,  // Position 1
                            0.0f,  0.0f,        // TexCoord 1
                            0.5f, -0.5f, 0.0f,  // Position 2
                            1.0f,  0.0f,        // TexCoord 2
                            0.5f,  0.5f, 0.0f,  // Position 3
                            1.0f,  1.0f         // TexCoord 3
                         };
   GLushort indices[] = { 0, 1, 2, 0, 2, 3 };


   // Set the viewport
   glViewport ( 0, 0, esContext->width, esContext->height );


   // Clear the color buffer
   glClear ( GL_COLOR_BUFFER_BIT );


   // Use the program object
   glUseProgram ( userData->programObject );


   // Load the vertex position
   glVertexAttribPointer ( userData->positionLoc, 3, GL_FLOAT,
                           GL_FALSE, 5 * sizeof(GLfloat), vVertices );
   // Load the texture coordinate
   glVertexAttribPointer ( userData->texCoordLoc, 2, GL_FLOAT,
                           GL_FALSE, 5 * sizeof(GLfloat), &vVertices[3] );


   glEnableVertexAttribArray ( userData->positionLoc );
   glEnableVertexAttribArray ( userData->texCoordLoc );


   // Load the rotate matrix
   glUniformMatrix4fv( userData->rotateLoc, // userData->mvpLoc,
                       1, GL_FALSE, (GLfloat*) &userData->rotateMx.m[0][0] );


   // Bind the texture
   glActiveTexture ( GL_TEXTURE0 );
   glBindTexture ( GL_TEXTURE_2D, userData->textureId );


   // Set the sampler texture unit to 0
   glUniform1i ( userData->samplerLoc, 0 );


   glDrawElements ( GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices );

}

///
// Cleanup
//
void ShutDown ( ESContext *esContext )
{
   UserData *userData = esContext->userData;


   // Delete texture object
   glDeleteTextures ( 1, &userData->textureId );


   // Delete program object
   glDeleteProgram ( userData->programObject );


   free(esContext->userData);
}


int main ( int argc, char *argv[] )
{
   ESContext esContext;
   UserData  userData;


   int width, height;
   GLubyte *image;


   image = esLoadTGA("jan.tga", &width, &height);
   if (image == NULL) {
       fprintf(stderr, "No such image ");
       exit(1);
   }
   printf("Width %d height %d ", width, height);


   userData.image = image;
   userData.width = width;
   userData.height = height;


   esInitContext ( &esContext );
   esContext.userData = &userData;


   esCreateWindow ( &esContext, "Simple Texture 2D", width, height, ES_WINDOW_RGB );

   if ( !Init ( &esContext ) )
      return 0;


   esRegisterDrawFunc ( &esContext, Draw );
   esRegisterUpdateFunc ( &esContext, Update );


   esMainLoop ( &esContext );

   ShutDown ( &esContext );
}

Run this by Rotate_Image. It shows the picture of me in a rotating square.

Conclusion

OpenGL ES is an amazingly complex and sophisticated system that I have made a small attempt to explain. This chapter covered drawing on the EGL surface discussed in earlier chapters and showed a number of relatively simple cases. These have been standard OpenGL ES; the only RPi-specific part has been the EGL surface.

Resources

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset