the Flagship blog & community for App Developers with main focus on Samsung's bada and cross-platform technologies
Bada Tutorials, UI & Graphics

OpenGL ES 2.0 on Bada: Shaders and Programs

Hello there lonesome bada developers! It is getting dark over here and I sense that it will take some time before I can publish this post, so I am getting ready for a long night with a hot cup of my favorite green tea.

This is basically the next part in my series about OpenGL ES 2.0 development on Bada. You can find the Introduction to OpenGL Es 2.0 here, which you should read, because this part will be built upon the example project we developed last week. In this part we will take a first look into Shaders and Programs – those are fundamental concepts of OpenGL ES 2.x needed to render anything. Using those objects you have direct control over the most important parts of OpenGL’s Graphics Pipeline. This control gives you an immense flexibility in rendering scenes as you like them, with a multitude of cool effects.

You will see more about those effects later and learn how to use these tools properly to achieve amazing things. For now, we will simply create the most simple Shaders and Program possible to render a multi-colored triangle.

Before we start, please make sure you have taken a look into the project download-able from the last tutorial. At the end of this tutorial you can download the upgraded project.

Shaders and Programs

What are Shaders? (I will simplify the answer as much as possible – those interested can read this up somewhere else) In OpneGL ES 2.x shaders are programmable components of the rendering pipeline. There are two types of shaders:

  1. Vertex shaders: programmable components that allow general operations on vertices.
  2. Fragment shaders (sometimes also called Pixel Shaders): programmable components that allow general operations on fragments (i.e. final pixels)

As you might have noticed:  it is all about creating OpenGL own executable binary objects that later have direct influence over the Graphical Pipeline.

  • First of all, a shader is created using a C-syntax-like OpenGL ES Shading Language.
  • Then it is compiled into the final Shader object.
  • Once you have your compiled Shader objects, you link them together into a Program object.
  • And finally, you tell the OpenGL ES 2.x API which Program object to use for rendering at a particular moment.

Once we have done this, we can start drawing our triangle. We do this by simply passing the vertices and colors we’d like rendered to OpenGL’s API. OpenGL ES 2.x takes these arrays and “drags them” ( :-P ) through the graphical pipeline. When they reach our own programmed shader logic, these points/lines/polygons/pixels are modified according to this shader logic. Finally a rendered image is delivered to our form.

Multi-Colored Triangle – Demo Project

OpenGLForm Modifications

For this example we have to modify the OpenGLForm of our last project. As you may remember, we want to use OpenGLForm as base class for other specialized forms dedicated to render specific OpenGL scenes. However, in the last project we only initiated the “canvas” we would be using for rendering. Now we have to add some special functionality to include compiling and linking of Shader and Program objects.

#include <FBase.h>
#include <FUi.h>
#include <FGraphics.h>
#include <FGraphicsOpengl2.h>

class OpenGLForm :
 public Osp::Ui::Controls::Form
{
public:
 OpenGLForm(){};
 OpenGLForm(char* fShaderStr, char* vShaderStr);
 virtual ~OpenGLForm();
 result Construct();
 result OnInitializing(void);
 virtual result OnDraw(void){}; // override this
protected:
 Osp::Graphics::Opengl::EGLDisplay  __eglDisplay;
 Osp::Graphics::Opengl::EGLSurface  __eglSurface;
 Osp::Graphics::Opengl::EGLConfig   __eglConfig;
 Osp::Graphics::Opengl::EGLContext  __eglContext;
 Osp::Graphics::Opengl::GLuint      __programObject;
 char* __fShaderStr;
 char* __vShaderStr;

 void Commit();
 virtual void bindShaderVariables(){}; //override this, used to link/load all attributes
private:
 bool InitEGL();
 bool InitGL();
 Osp::Graphics::Opengl::GLuint LoadShader(Osp::Graphics::Opengl::GLenum type, const char *shaderSrc);
 void DestroyGL();

};
  • First of all, we create a new constructor that accepts two strings with Shaders source code. We store a reference to these strings as member objects (see lines: 11,  22, 23)
  • Secondly we leave OnDraw() callback method blank. Derived forms shall override it to draw their specific OpenGL scenes (line: 15).  To commit changes after the drawing to the OpenGL ES API, we create a special method Commit() (line: 25)
  • Then we also create a member object that will store reference to the Program object (where our Shaders are linked) (line: 21)
  • Furthermore we create a new method that will initialize the program object: InitGL() (line: 29)
  • Then we will need a method bindShaderVariables() that links attributes (as well as other “variables”) in Shader’s scripts to our own C++ variables. We will leave this blank – it is up to the derived classes to use/link the attributes they need (line: 26). Don’t worry about the meaning of this right now – you’ll see an example in a few minutes.
  • And finally, we need a method that can create and compile our shader objects by giving it the source code of the shader – LoadShader(type, source) (line: 30)

Let us take a look into how a shader is created and compiled.

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

 shader = glCreateShader(type);
 if(shader==0) return 0;

 glShaderSource(shader, 1, &shaderSrc, NULL);
 glCompileShader(shader);
 glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
 if(!compiled) return 0;

 return shader;
}

The code is more or less self-explanatory:

  • We create the shader based on the type we want – vertex of fragment shader (GL_VERTEX_SHADER or GL_FRAGMENT_SHADER) and check success of this operation
  • We set the shader’s source code, compile it and check success. At this point we could add additional code that extracts the type of error that could have happened if the operation is not successful. For simplicity reasons we will leave it like this for now, though.
  • Finally we return an ID that identifies the shader’s object in OpenGL ES API

Now let’s bring the shaders together into a program object and link it:

bool
OpenGLForm::InitGL()
{
 GLuint vertexShader;
 GLuint fragmentShader;
 GLint linked;

 vertexShader = LoadShader(GL_VERTEX_SHADER, __vShaderStr);
 fragmentShader = LoadShader(GL_FRAGMENT_SHADER, __fShaderStr);

 __programObject = glCreateProgram();

 if(__programObject==0 || vertexShader==0 || fragmentShader==0){
     AppLog("OpenGLForm:InitGL: problem loading shaders and/or creating program!");
     return false;
 }

 glAttachShader(__programObject, vertexShader);
 glAttachShader(__programObject, fragmentShader);
 glLinkProgram(__programObject);
 glGetProgramiv(__programObject, GL_LINK_STATUS, &linked);
 if(!linked){
     AppLog("OpenGLForm:InitGL: problem linking program!");
     return false;
 }

 //load all attributes
 bindShaderVariables();
 //use the program
 glUseProgram(__programObject);
 //clear before use
 glClearColor(0, 0, 0, 1);
 glClear(GL_COLOR_BUFFER_BIT);
 return true;
}
  • As you see, we use the previous method to create and compile two shader objects based on their source code.
  • Then we create a program object and check if all these “creative operations” were successful ;-)
  • In the next step we attach our shaders to the program object, link it and check success of this operation.
  • Next we call our local method to eventually bind any needed attributes from the shader’s source code to our own variables. This will be used in higher, derived, classes of OpenGLForm.
  • Finally we tell OpenGL ES API which program object to use (we have only one :-) ) and clear the color buffer.

One final thing to do in our OpenGLForm is to define the Commit() method:

void
OpenGLForm::Commit()
{
    glFinish();
    eglSwapBuffers(__eglDisplay, __eglSurface);
}

All it does is to wait for the rendering to finish and swap the double buffer to the screen.

Now we are basically done with our “generic” OpenGL form base class. Now let’s actually render our triangle…

My3D Form – Rendering the Triangle

We create a derived form from OpenGLForm, we will call it My3D

First of all, we pass our base class two scripts for the shaders we would like to use. Keep in mind that for our simple example we do not need any “effects”. So we keep the shader scripts as simple and straightforward as possible:

static char* vShaderStr =
		  "attribute vec4 a_position;    \n"
		  "attribute vec4 a_color;       \n"
		  "varying vec4 v_color;         \n"
		  "void main()                   \n"
		  "{                             \n"
		  "   v_color = a_color;	 \n"
		  "   gl_Position = a_position;  \n"
		  "}                             \n";

static char* fShaderStr =
		"#ifdef GL_ES                  \n"
		"precision highp float;        \n"
		"#endif                        \n"
		"                              \n"
		"varying vec4 v_color;         \n"
		"                              \n"
		"void main (void)              \n"
		"{                             \n"
		"    gl_FragColor = v_color;   \n"
		"}";

// Later in code...

My3D::My3D() : OpenGLForm(fShaderStr, vShaderStr)
{}

We will take a deeper look into the Shader Language in a later tutorial. At this moment, simply note a few things:

  • each Shader has a main() method like in a typical C program. This code is executed for each vertex (in a vertex shader) or each fragment/pixel (in a fragment shader)
  • Regardless of the transformations/effects you program, the final position of each vertex is set by assigning coordinates to gl_Position; the final color for each pixel is, on the other hand, set by assigning a color to gl_FragColor.
  • attributes are variables that we can bind to our external C++ code. Since the vertex shader is the first component in the pipeline, we pass to it both, attributes for position and color, and let it “pass” the color attribute to the fragment shader via an internal variable (varying v_color).
  • Both position and color attributes (a_position and a_color) are in a vector format with 4 coordinates (x,y,z,w) for position and (r,g,b,a) for color.
  • In our example we do not perform any modifications on the final vertex position nor pixel color, so all we need to do is to assign the attributes directly to gl_Position (or gl_FragColor by using the passed varying v_color)
  • if you insert the source code of the shaders directly into your C++ code, like I do, do not forget the line breaks “\n”

Now, we need two local variables to keep them connected to our shaders: one for the position and one for the color. You will see them in action in a minute:

class My3D :
public OpenGLForm
{
public:
 My3D();
 virtual ~My3D();
 virtual result OnDraw(void);
protected:
 virtual void bindShaderVariables();
private:
 Osp::Graphics::Opengl::GLint attColor;
 Osp::Graphics::Opengl::GLint attPosition;
};

// later

void
My3D::bindShaderVariables()
{
    attPosition = glGetAttribLocation(__programObject, "a_position");
    attColor = glGetAttribLocation(__programObject, "a_color");
}

As you may remember, when the the program object is initialized within InitGL() of our base class, bindShaderVariables() is automatically called. So, on initialization of the form, we are all set to start drawing. This is how we draw our multi-colored triangle:

result
My3D::OnDraw(void)
{
 result r = E_SUCCESS;

 glViewport(0, 0, this->GetWidth(), this->GetHeight());
 glClearColor ( 0.0f, 0.0f, 0.0f, 1.0f );
 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

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

 glVertexAttribPointer(attPosition, 3, GL_FLOAT, GL_FALSE, 0, vVertices);
 glVertexAttribPointer(attColor, 4, GL_FLOAT, GL_FALSE, 0, vColors);

 glEnableVertexAttribArray(attPosition);
 glEnableVertexAttribArray(attColor);

 glDrawArrays(GL_TRIANGLES, 0, 3);

 Commit();
 return r;
}
  • First of all, we set the viewport and clear the background to pitch black :-)
  • Secondly,  we define three vertices as corners for our triangle and three colors that will be used for each of those vertices.
  • Then we basically “point” our member variables for position and color (attPosition and attColor) to these arrays. We specify whether the vectors are composed of three dimensions (like in the case for vertices) or four dimensions ( like in the case of our colors, that include alphas). There are additional parameters you can play with here, but they are outside of our scope right now.
  • Furthermore, we enable our member variables for shader’s attributes – thus storing both arrays in OpenGL ES Graphics Pipeline.
  • And finally, we ask OpenGL ES to draw triangles using the arrays stored in the graphical pipeline at this point. And commit the rendering to the screen.

Done!

Fragment Shader Modification

For fun, let us modify slightly the pixel shader, so that you see what it does. Modify Fragment Shader’s source at this line:

gl_FragColor = v_color;

to this:

gl_FragColor = v_color * vec4(0.5, 0.5, 0.5, 1.0);

As you may have guessed, this modification will make the whole rendered image by about 50% darker! Let’s see if you can spot the difference:

Let this simple example serve you as a small introduction into one of our future OpenGL tutorials about the Shader Language. :-)

Conclusions

You have my respect, if you have read the whole article up to this point – it was probably my longest tutorial till now :-D .  Nevertheless, no words can explain more than the code itself, therefore please download the whole project here and experiment yourself!

I am finally posting this tutorial on the next day – it was indeed a bit too much for one night! ;-)

I will continue posting more OpenGL ES 2.0 articles if you guys want to see more! See you next time!

static char* vShaderStr =
“attribute vec4 a_position;    \n”
“attribute vec4 a_color;         \n”
“varying vec4 v_color;         \n”
“void main()                  \n”
“{                            \n”
“   v_color = a_color;        \n”
“   gl_Position = a_position;  \n”
“}                            \n”;

Related posts:

  1. OpenGL ES 2.0 : Interactive Triangle Example On Bada
  2. Introduction to OpenGL ES 2.0 on Bada
  3. Introduction to OpenGL ES 1.1 on Bada
  1. 8 Responses to “OpenGL ES 2.0 on Bada: Shaders and Programs”

  2. By Mal Loth on Jul 29, 2010 | Reply

    :jawdrop: Ahh! It’s the greatest article about OGLES I’ve ever encountered. So clear and simple. Makes me feel more up to creating some 3D game. Thanks very much for this one Wit :) . Shaders were always a mystery to me.

  3. By sparky on Jul 30, 2010 | Reply

    Hah! Wit, I am already looking forward to your OGLES2.0 texturing tutorial ;-)

  4. By James on Aug 17, 2010 | Reply

    This is great! Please continue writing, we really appreciate it!

  5. By wit on Aug 17, 2010 | Reply

    Yes, the topics are waiting on my shelf :-)
    OpenGL is always somewhat time-intensive, but I’m working on it! Arrr!

  6. By kEhYo on Sep 8, 2010 | Reply

    Nice tutorial. I’m waiting for more. I think that texturing should be in the next part :)

  7. By howord on Feb 18, 2011 | Reply

    thank you! this is the best tutorial I have seen.

  8. By coupons and vouchers 2012 on Jul 19, 2012 | Reply

    Awesome! Its truly awesome piece of writing, I have
    got much clear idea concerning from this paragraph.

  1. 1 Trackback(s)

  2. Oct 29, 2011: Could Samsung’s Bada operating system pose a threat to Android or Symbian?‏ | What Is Android

Post a Comment

Editor's picks

Copyright 2009-2010 BadaDev.com (unless otherwise stated). All rights reserved! Powered by Wordpress!