In the previous post I discussed the design of the ShaderProgram class, in this final instalment I will discuss the overall ShaderManager class and it's basic usage.
The basic design consideration for the manager is it will allow for the creation of Shaders and ShaderPrograms that will be independent of each other, these will be stored by name as a std::string and basic methods may be called to configure and load these.
It will also have the ability to access the ShaderPrograms via a lookup using the [] operator passing in the name of the shader.
The class diagram is as follows
The std::map containers for the shader programs and Shaders contain pointers to the classes so each of them can live as separate entities and Shader Programs may share multiple shaders.
Construction of the class will also create a nullProgram object as discussed in the previous post. The motivation for this is to allow the return of a class which will be a glUseProgram(0) but still be an object capable of being callable for methods without crashing.
ShaderManager::ShaderManager() { m_debugState=true; m_nullProgram = new ShaderProgram("NULL"); }To create a shader program we use the following method to construct a new ShaderProgram class
void ShaderManager::createShaderProgram(std::string _name) { std::cerr<<"creating empty ShaderProgram "<<_name.c_str()<<"\n"; m_shaderPrograms[_name]= new ShaderProgram(_name); }Next we can add shaders using the following methods
void ShaderManager::attachShader( std::string _name, SHADERTYPE _type ) { m_shaders[_name]= new Shader(_name,_type); }At this stage the shader may not have any source attached to it so we have a method to allow the loading of the shader source.
void ShaderManager::loadShaderSource(std::string _shaderName, std::string _sourceFile) { std::map <std::string, Shader * >::const_iterator shader=m_shaders.find(_shaderName); // make sure we have a valid shader and program if(shader!=m_shaders.end() ) { shader->second->load(_sourceFile); } else {std::cerr<<"Warning shader not know in loadShaderSource "<<_shaderName.c_str();} }
Once the source is loaded to compile the Shader we do the following
void ShaderManager::compileShader(std::string _name) { // get an iterator to the shaders std::map <std::string, Shader * >::const_iterator shader=m_shaders.find(_name); // make sure we have a valid shader if(shader!=m_shaders.end()) { // grab the pointer to the shader and call compile shader->second->compile(); } else {std::cerr<<"Warning shader not know in compile "<<_name.c_str();} }
Next we need to attach the shader to a program, as both classes are contained in maps we have to search for both and then call the relevant methods as show in the following code
void ShaderManager::attachShaderToProgram(std::string _program,std::string _shader) { // get an iterator to the shader and program std::map <std::string, Shader * >::const_iterator shader=m_shaders.find(_shader); std::map <std::string, ShaderProgram * >::const_iterator program=m_shaderPrograms.find(_program); // make sure we have a valid shader and program if(shader!=m_shaders.end() && program !=m_shaderPrograms.end()) { // now attach the shader to the program program->second->attatchShader(shader->second); // now increment the shader ref count so we know if how many references shader->second->incrementRefCount(); if (m_debugState == true) { std::cerr<<_shader.c_str()<<" attached to program "<<_program.c_str()<<"\n"; } } else {std::cerr<<"Warning cant attach "<<_shader.c_str() <<" to "<<_program.c_str()<<"\n";} }
To overload the [] operator to work with a std::string we use the following
ShaderProgram * ShaderManager::operator[](const std::string &_programName) { std::map <std::string, ShaderProgram * >::const_iterator program=m_shaderPrograms.find(_programName); // make sure we have a valid program if(program!=m_shaderPrograms.end() ) { return program->second; } else { std::cerr<<"Warning Program not know in [] "<<_programName.c_str(); std::cerr<<"returning a null program and hoping for the best\n"; return m_nullProgram; } }For brevity the rest of the methods can be seen in the source download here (lecture 8)
Using the ShaderManager
The file GLWindow.cpp demonstrates the use of the ShaderManager, we must first initialise GLEW if we are using linux or windows (Mac OS X is fine)
The following example program is based on the code here and is basically OpenGL 3.x compatible, however I use GLSL Version 120 as the basis as mac OSX doesn't support higher at present (come on APPLE !)
We uses very basic shaders that allow the basic vertex and colour attributes to be named in the client program and the shader.
#version 120 attribute vec3 inPosition; attribute vec3 inColour; varying vec3 vertColour; void main() { gl_Position = vec4(inPosition, 1.0); vertColour = inColour; }
#version 120 varying vec3 vertColour; void main() { gl_FragColor = vec4(vertColour,1.0); }Generating Vertex data
The vertex data is created using a vertex buffer object and a vertex array object these will then be bound to attributes in the shaders
void GLWindow::createTriangle() { // First simple object float* vert = new float[9]; // vertex array float* col = new float[9]; // color array vert[0] =-0.3; vert[1] = 0.5; vert[2] =-1.0; vert[3] =-0.8; vert[4] =-0.5; vert[5] =-1.0; vert[6] = 0.2; vert[7] =-0.5; vert[8]= -1.0; col[0] = 1.0; col[1] = 0.0; col[2] = 0.0; col[3] = 0.0; col[4] = 1.0; col[5] = 0.0; col[6] = 0.0; col[7] = 0.0; col[8] = 1.0; // Second simple object float* vert2 = new float[9]; // vertex array vert2[0] =-0.2; vert2[1] = 0.5; vert2[2] =-1.0; vert2[3] = 0.3; vert2[4] =-0.5; vert2[5] =-1.0; vert2[6] = 0.8; vert2[7] = 0.5; vert2[8]= -1.0; // Two VAOs allocation glGenVertexArrays(2, &m_vaoID[0]); // First VAO setup glBindVertexArray(m_vaoID[0]); glGenBuffers(2, m_vboID); glBindBuffer(GL_ARRAY_BUFFER, m_vboID[0]); glBufferData(GL_ARRAY_BUFFER, 9*sizeof(GLfloat), vert, GL_STATIC_DRAW); m_shaderManager["Simple"]->vertexAttribPointer("inPosition",3,GL_FLOAT,0,0); m_shaderManager["Simple"]->enableAttribArray("inPosition"); glBindBuffer(GL_ARRAY_BUFFER, m_vboID[1]); glBufferData(GL_ARRAY_BUFFER, 9*sizeof(GLfloat), col, GL_STATIC_DRAW); m_shaderManager["Simple"]->vertexAttribPointer("inColour",3,GL_FLOAT,0,0); m_shaderManager["Simple"]->enableAttribArray("inColour"); // Second VAO setup glBindVertexArray(m_vaoID[1]); glGenBuffers(1, &m_vboID[2]); glBindBuffer(GL_ARRAY_BUFFER, m_vboID[2]); ceckGLError(__FILE__,__LINE__); glBufferData(GL_ARRAY_BUFFER, 9*sizeof(GLfloat), vert2, GL_STATIC_DRAW); glVertexAttribPointer((GLuint)0, 3, GL_FLOAT, GL_FALSE, 0, 0); glEnableVertexAttribArray(0); glBindVertexArray(0); delete [] vert; delete [] vert2; delete [] col; }Next we load and build the Shader program using the ShaderManager class
void GLWindow::initializeGL() { ngl::NGLInit *init = ngl::NGLInit::instance(); init->initGlew(); glClearColor(0.4f, 0.4f, 0.4f, 1.0f); m_shaderManager.createShaderProgram("Simple"); m_shaderManager.attachShader("SimpleVertex",VERTEX); m_shaderManager.attachShader("SimpleFragment",FRAGMENT); m_shaderManager.loadShaderSource("SimpleVertex","shaders/Vertex.vs"); m_shaderManager.loadShaderSource("SimpleFragment","shaders/Fragment.fs"); m_shaderManager.compileShader("SimpleVertex"); m_shaderManager.compileShader("SimpleFragment"); m_shaderManager.attachShaderToProgram("Simple","SimpleVertex"); m_shaderManager.attachShaderToProgram("Simple","SimpleFragment"); m_shaderManager.bindAttribute("Simple",0,"inPosition"); m_shaderManager.bindAttribute("Simple",1,"inColour"); m_shaderManager.linkProgramObject("Simple"); m_shaderManager["Simple"]->use(); createTriangle(); }You will notice before we link the shader program we assign the inPosition and inColour attributes to the attribute locations 0 and 1 respectively. This will be used in the drawing routine later.
void GLWindow::paintGL() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); m_shaderManager["Simple"]->use(); glBindVertexArray(m_vaoID[0]); // select first VAO glDrawArrays(GL_TRIANGLES, 0, 3); // draw first object glBindVertexArray(m_vaoID[1]); // select second VAO m_shaderManager["Simple"]->vertexAttrib3f("inColour",1.0,0.0,0.0); //glVertexAttrib3f((GLuint)1, 1.0, 0.0, 0.0); // set constant color attribute glDrawArrays(GL_TRIANGLES, 0, 3); // draw second object }
Next Step
One of the initial goals of this design was for a standalone version of a shader manager that people could use in their own project, with very few external library dependancies (at present we only need OpenGL, GLEW and Qt for the project and some string IO).
The next step is to integrate this into my existing ngl:: library and replace the existing ShaderManager class. This is going to happen very soon and I will post an update on the integration and a fuller critique of the design once this integration has happened, and finally a teapot
No comments:
Post a Comment