In our previous post we wrote the shaders that will be required to display our first quad using OpenGL on our iOS device. Our next step would be to compile and link the shaders so they are ready to be used within our program.
In order to use our shaders we need a handle to the shader and also the attributes that are defined inside the shaders so we can bind our model’s (in this case the quad) vertex data to it.
Create the class and header file for Compiling Shaders:
We start off by creating a new class that complies the shaders and returns a handle to the shader program. Shader program is the OpenGl’s object to which shaders can be attached. To create this class first we had a new class to our xcode project by “ctrl + click” on our project file in the navigator column of xcode. Next we select the C++ class type from the pop-out window and name it “Shader“. We next rename the “Shader.cpp” to “Shader.mm“. Now we have the empty classes to hold the implementation of the class.
Header Implementation:
We start of by importing the OpenGL’s headers that will be required. These are
#import <OpenGLES/ES2/gl.h>
#import <OpenGLES/ES2/glext.h>
As you can see we are using the OpenGL ES 2.0 version. Later on the series we will look into using OpenGL 3.0 implementation for the iOS7 and IPhone 5S and above devices.
Next we start the implementation of our class and adding the function and variable declarations to the header:
//our shader class encapsulates all methods needed for loading and compiling shaders from a file class Shader { public: //create a shader with these fragment and vertex shaders Shader(const std::string& vertexShaderFilename, const std::string& fragmentShaderFilename); //destructor just calls cleanup() ~Shader();</pre> //this methods invokes loading the files from disk, compiles the two shaders //and combines them to one shader program. //returns false if errors occurred bool compileAndLink(); //returns the ID of the shader program which is needed to bind it etc unsigned int getProgram(); //this methods invokes loading the files from disk, compiles the two shaders //and combines them to one shader program. //returns false if errors occurred bool compileAndLink(); //returns the ID of the shader program which is needed to bind it etc unsigned int getProgram(); <pre> private: //cleans up the shaders, deletes them in the OpenGL context</pre> void cleanup(); //loads a file and compiles it to either a GL_VERTEX_SHADER or GL_FRAGMENT_SHADER unsigned int loadAndCompileShaderFile(GLenum type, const std::string& filename); //links the program bool linkProgram(); //--------------------------------------------------------- //ids for the vertex and fragment shaders, and the final shader program unsigned int m_fragmentShader; unsigned int m_vertexShader; unsigned int m_shaderProgram; //store the filenames for the shaders until we actually load them std::string m_fragmentShaderFilename; std::string m_vertexShaderFilename; }; <pre>
We start off with the constructor that takes the name of the vertex shader and the fragment shader. Then a declaration of the destructor.
The CompileAndLink() is where the bulk of the work is done in terms of loading and initializing the shaders.This function will load the shader files, compile them and create the shader program. It returns a bool flag which tell us if the operations where successful or not.
The getProgram() function just returns the handle to the shader program using which we will set the model’s data and other variables for drawing.
The private section of the declarations holds the private members and functions that will be called internally to perform the actions and store the values that will be needed for the class. We will take a better look at them in the next section which explains the implementation of the class.
Class Implementation:
Here is the completed class implementation:
#include "Shader.h" //constructor //initializes our member variables with default values and stores the shader file names Shader::Shader(const std::string& vertexShaderFilename, const std::string& fragmentShaderFilename): m_fragmentShader(0), m_vertexShader(0), m_shaderProgram(0), m_fragmentShaderFilename(fragmentShaderFilename), m_vertexShaderFilename(vertexShaderFilename) { } //destructor just calls cleanup Shader::~Shader() { cleanup(); } //frees the OpenGL IDs for the shaders if they are present void Shader::cleanup() { if (m_vertexShader) { glDeleteShader(m_vertexShader); m_vertexShader = 0; } if (m_fragmentShader) { glDeleteShader(m_fragmentShader); m_fragmentShader = 0; } if (m_shaderProgram) { glDeleteProgram(m_shaderProgram); m_shaderProgram = 0; } } //returns the shader program id unsigned int Shader::getProgram() { return m_shaderProgram; } //this methods invokes loading the files from disk, compiles the two shaders //and combines them to one shader program. //returns false if errors occurred bool Shader::compileAndLink() { //create the shader program m_shaderProgram = glCreateProgram(); //load and compile the vertex and fragment shaders: m_vertexShader = loadAndCompileShaderFile(GL_VERTEX_SHADER, m_vertexShaderFilename); m_fragmentShader = loadAndCompileShaderFile(GL_FRAGMENT_SHADER, m_fragmentShaderFilename); //check for errors if(m_vertexShader == 0 || m_fragmentShader == 0) { fprintf(stderr, "Error while compiling shaders"); cleanup(); // destroy everything that we allocated as it is not valid return false; //if one of the shaders didn't load or compile, we return false } //attach the vertex and fragment shader to our shader program glAttachShader(m_shaderProgram, m_vertexShader); glAttachShader(m_shaderProgram, m_fragmentShader); //link the program to make it ready for use if(!linkProgram()) { //if something went wrong, cleanup and return false cleanup(); return false; } //if we reach this point, then the program was successfully created return true; } unsigned int Shader::loadAndCompileShaderFile(GLenum type, const std::string& filename) { //separate extension and filename: std::string baseFileName = filename.substr(0, filename.find_last_of('.')); std::string extension = filename.substr(filename.find_last_of('.') + 1, filename.length()-1); NSString *fn = [[NSString alloc] initWithCString:baseFileName.c_str() encoding:NSUTF8StringEncoding]; NSString *ext = [[NSString alloc] initWithCString:extension.c_str() encoding:NSUTF8StringEncoding]; NSString *path = [[NSBundle mainBundle] pathForResource:fn ofType:ext]; //load const GLchar *sources = (GLchar *)[[NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil] UTF8String]; //make sure that we were able to load the source code if (!sources) { NSLog(@"Failed to load shader: %s", filename.c_str()); return false; } //create shader GLuint shader = glCreateShader(type); //specify the loaded source code for this shader glShaderSource(shader, 1, &sources, NULL); //compile the shader glCompileShader(shader); //now we check for any errors GLint status; glGetShaderiv(shader, GL_COMPILE_STATUS, &status); if (status == GL_FALSE) //we had errors { NSLog(@"Failed to compile shader: %s", filename.c_str()); //OpenGL provides us with an info log, which contains the compile errors //we first have to query its length and then allocate memory for the log message GLint logLength; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength); if (logLength > 0) { //allocate memory for the log unsigned char *log = new unsigned char[logLength]; //retrieve the log glGetShaderInfoLog(shader, logLength, &logLength, (GLchar*)&log[0]); //print it to the console NSLog(@"\n%s", log); //free the allocated memory delete log; } //return an invalid shader id to show that something went wrong return 0; } //no errors -> return the shader return shader; } // link the shader program (binding the vertex and fragment shader together) // so we can use it for rendering bool Shader::linkProgram() { //link the program, which makes sure a vertex and fragment shader are present glLinkProgram(m_shaderProgram); //check for errors GLint status; glGetProgramiv(m_shaderProgram, GL_LINK_STATUS, &status); if (status == GL_FALSE) NSLog(@"Failed to link program"); //return the status which is true if everyhing went well return status; }
I wont go into all the details of explaining this class. If there is something that I have not explained here especially with OpenGL function calls you can refer to the OpenGL SDK Manual. I will explain the functions that I have written and give an overview of what they do.
The constructor of the class basically just sets all program, vertex shader and fragment shaders handle variables declared in the header to 0 (just a int value not matching any real handle). It also sets the shader file names from the arguments to the declared variables.
The destructor calls the cleanup() function which in turn releases the shader and program handles.
The loadAndCompileShaderFile(GLenum type, const std::string& filename) function takes the type of shader and the shader file name as arguments. The type of the shader is GL_VERTEX_SHADER or GL_FRAGMENT_SHADER. These are OpenGL’s enum types to identify the shader type. The function first reads the shader file as a string, creates a empty shader variable of the type that is specified and compiles the shader. If this compilation fails it writes a Log of what went wrong else it just returns the compiled shader.
The compileAndLink() function creates an empty program, calls the loadAndCompileShaderFile for compiling the vertex and fragment shaders and sets the returned shader variables to the respective declared variables. It then attaches the shaders to the empty program that we had previously and links the program.
This class now has the functionality to take the shaders that we had written, compile them and create the shader program that we will use to draw our quad which is coming in the next post. For now lets create a Shader object and get our program to compile successfully.
Create a Shader Object:
In the GameMain.h we include the newly created Shader.h and declare a private Shader object.
#include <iostream> #include "Shader.h" class GameMain { private: //the shader program we use for rendering Shader *m_shader; public:
Then in the GameMain.mm class’s Initialize() function we Initialize our Shader object
void GameMain::Initialize() { //load our shader m_shader = new Shader("shader.vert", "shader.frag"); if(!m_shader->compileAndLink()) { fprintf(stderr,"Encountered problems when loading shader, application will crash..."); } //tell OpenGL to use this shader for all coming rendering glUseProgram(m_shader->getProgram()); }
Building and Running the project now will throw a Log message that the compilation failed! That is because we have not added the shader files to the target’s bundle and so the program is unable to find the shader source files (which is treated as text files, hence a resource that needs to be copied).
To add the shaders to the Resource bundle of the target you need to select the target in the project navigator. Then under the “Build Phases” tab unfold the “Copy Bundle Resources“. You should be able to find a small “+” sign at the bottom of the tab. Click on this and select the shader files that we created in the previous post.
Now you should be able to compile and run the code safely. You can grab the project from GIT repository tagged vOGL_2D.003.
Next we will make the quad and draw it on the screen to finally start seeing some results!
Reblogged this on Sutoprise Avenue, A SutoCom Source.
Reblogged this on Sutoprise Avenue, A SutoCom Source.