Update: There’s a re-worked and improved version of this shader loading code here: https://r3dux.org/2015/01/a-simple-c-opengl-shader-loader-improved/ – you should probably use that instead of this.
I’ve been doing a bunch of OpenGL programming recently and wanted to create my own shader classes to make setting up shaders as easy as possible – so I did ;-) To create vertex and fragment shaders and tie them into a shader program you can just import the Shader.hpp and ShaderProgram.hpp classes and use code like the following:
// Set up vertex shader Shader vertexShader(GL_VERTEX_SHADER); vertexShader.loadFromFile("MyVertexShader.vert"); vertexShader.compile(); // Set up fragment shader Shader fragmentShader(GL_FRAGMENT_SHADER); fragmentShader.loadFromFile("MyFragmentShader.frag"); fragmentShader.compile(); // Set up shader program shaderProgram = new ShaderProgram(); shaderProgram->attachShader(vertexShader); shaderProgram->attachShader(fragmentShader); shaderProgram->linkProgram();
There’s also a loadFromString(some-string-containing-GLSL-source-code) method, if that’s your preference.
The ShaderProgram class uses a string/int map as a key/value pair, so to add attributes or uniforms you just specify their name and they’ll have a location assigned to them:
// Add the shader attributes shaderProgram->addAttribute("vVertex"); // ... // Add the shader uniforms shaderProgram->addUniform("pMatrix"); // ...
The ShaderProgram class then uses two methods called attribute and uniform to return the bound locations (you could argue that I should have called these methods getAttribute and getUniform – but I felt that just attribute and uniform were cleaner in use. Feel free to mod if you feel strongly about it). When binding vertex attribute pointers you can use code like this:
// Set up a vertex buffer object to hold the vertex position data GLuint vertexBufferId; glGenBuffers(1, &vertexBufferId); glBindBuffer(GL_ARRAY_BUFFER, vertexBufferId); glBufferData(GL_ARRAY_BUFFER, model.getVertexDataSizeBytes(), model.getVertexData(), GL_STATIC_DRAW); // Set up the vertex attribute pointer for the vVertex attribute glVertexAttribPointer( shaderProgram->attribute("vVertex"), // Attribute location VERTEX_COMPONENTS, // Number of elements per vertex, here (x,y,z), so 3 GL_FLOAT, // Data type of each element GL_FALSE, // Normalised? 0, // Stride 0 // Offset );
Finally, when drawing your geometry you can get just enable the shader program, provide the location and data for bound uniforms, and then disable it like this (I’m using the GL Mathematics library for matrices – you can use anything you fancy):
shaderProgram->use(); // Provide uniform data glUniformMatrix4fv( shaderProgram->uniform("mMatrix"), 1, GL_FALSE, glm::value_ptr(mMatrix) ); // ... // Draw stuff shaderProgram->disable();
That’s pretty much it – nice and simple. I haven’t done anything with geometry shaders yet so I’ve no idea if there’s anything else you’ll need, but if so it likely won’t be too tricky a job to implement it yourself. Anyways, you can look at the source code for the classes themselves below, and I’ll put the two classes in a zip file here: ShaderHelperClasses.zip.
As a final note, you can’t create anything shader-y without having a valid OpenGL rendering context (i.e. a window to draw stuff to) or the code will segfault – that’s just how it works. The easiest way around this if you want to keep a global ShaderProgram object around is to create it as a pointer (i.e. ShaderProgram *shaderProgram;) and then initialise it later on when you’ve got the window open with shaderProgram = new ShaderProgram(); like I’ve done above.