VSShader Lib – Very Simple Shader Library
Shaders are the core of the rendering process. OpenGL core profile requires us to provide our own shaders, no more fixed function.
Using shaders means more flexibility, but it also implies more work. This is where this lib steps in.
VSShaderLib was designed to make our life easier. It allows to create programs, load shaders from files, associate vertex attribute names with locations, and work with uniforms, including blocks. It also provides access to the info logs.
In Action
Usage of this lib should be pretty straightforward. First we declare a variable of type VSShaderLib
.
VSShaderLib shader;
The first step to start using shader
, is to call init
. This creates the OpenGL program to which shaders will be attached to.
Afterwards we may load the shaders, providing a filename. The following example shows how to do this:
shader.init(); shader.loadShader( VSShaderLib::VERTEX_SHADER, "shaders/dirlightdiffambpix.vert"); shader.loadShader( VSShaderLib::FRAGMENT_SHADER, "shaders/dirlightdiffambpix.frag");
To bind a fragment output variable we can use function setProgramOutput
. Let’s assume that the fragment shader has an output variable named outputF
, and we want to bind it to output index 0. The following code shows how to do it:
shader.setProgramOutput(0,"outputF");
Next we can establish the semantics of the input attributes, for instance as shown in the following snippet:
shader.setVertexAttribName( VSShaderLib::VERTEX_COORD_ATTRIB, "position"); shader.setVertexAttribName( VSShaderLib::TEXTURE_COORD_ATTRIB, "texCoord"); shader.setVertexAttribName( VSShaderLib::NORMAL_ATTRIB, "normal"); shader.setVertexAttribName( VSShaderLib::TANGENT_ATTRIB, "tangent"); shader.setVertexAttribName( VSShaderLib::BITANGENT_ATTRIB, "bitangent");
Actually, we are establishing the locations of these attributes. Using pre defined locations, makes life easier when we want to use a shader to draw multiple objects created with different libs or functions.
After we have set the locations for both fragment outputs and input attributes, we can call prepareProgram
. This is a long function, that links the program, and gets all uniform data, including names, locations, data types, offsets, and so on.
shader.prepareProgram();
After this step the shader is ready to be used.
To set uniform values for an int
we can do as follows:
shaderTess.setUniform("texUnit", 0);
The function takes the name of the uniform, and its value. There are variations of this function for floats and pointers (for vecs and matrices).
Another scenario is when a uniform is in a block. For instance consider the following block:
layout (std140) uniform Material { vec4 diffuse; vec4 ambient; vec4 specular; vec4 emissive; float shininess; int texCount; };
In this case we can use the following function to set the diffuse member of the block:
float diffuse[4] = {0.8f, 0.8f, 0.8f, 1.0f}; shader.setBlockUniform("Material", "diffuse", diffuse);
It is also possible to set the full block at once. The following snippet of code shows how. Note that we must have a data structure that maps exactly how the memory is laid out in the uniform block. In the case of the block above this is pretty simple.
struct MyMaterial{ float diffuse[4]; float ambient[4]; float specular[4]; float emissive[4]; float shininess; int texCount; }; ... MyMaterial myMat; ... shader.setBlock("Material", &myMat);
Beware with byte alignment. Block are great, but they must be handled with care. Always check the offsets and strides to make sure your data in the application matches the data in the block. This is also true when setting individual uniforms. Always check your strides, particularly for matrices. For instance a mat3 declared in a uniform block requires the same memory as a 3×4 matrix. This implies that to set a mat3 in a block we should provide a 3×4 matrix, where the last element of each column, for column major matrices is ignored.
If you’re having problems with uniforms inside a block, just try placing that uniform outside the block. If the error is gone, then, most likely the problem is related to the data stride.
Once all uniforms are set and we want to use our program for rendering then we can write:
glUseProgram(shader.getProgramIndex());
Specification
Enumeration for attribute types
attributes can be added as required.
enum AttribType { VERTEX_COORD_ATTRIB, NORMAL_ATTRIB, TEXTURE_COORD_ATTRIB TANGENT_ATTRIB, BITANGENT_ATTRIB };
Enumeration for shader types
enum ShaderType { VERTEX_SHADER, GEOMETRY_SHADER, TESS_CONTROL_SHADER, TESS_EVAL_SHADER, FRAGMENT_SHADER, COUNT_SHADER_TYPE }
Functions
void init()
Call this function first for each instance. This should only be created after an OpenGL context is created.
Example
VSShaderLib myShader; myShader.init();
void loadShader( VSShaderLib::ShaderType st, std::string fileName)
Given a file name, load its text as the source of the shader, attaches it to the program and compiles it. The info log for the shader can be retrieved with getShaderInfoLog
and the compile status can be checked with isShaderCompiled
.
Example
shader.loadShader( VSShaderLib::VERTEX_SHADER, "shaders/dirlightdiffambpix.vert");
void setProgramOutput(int index, std::string name)
Binds fragment output location of variable name
to index
.
Example
shader.setProgramOutput(0,"outputF");
GLint getProgramOutput(std::string name)
Returns the fragment output location of fragment output variable name
Example
fragLoc = shader.getProgramOutput("outputF");
void setVertexAttribName( VSShaderLib::AttribType at, std::string name)
Defines the semantic for the attribute named name
. Binds the attribute to the location specified by at
.
Example
shader.setVertexAttribName( VSShaderLib::VERTEX_COORD_ATTRIB, "position");
void prepareProgram()
Links the program and discovers all uniforms, including uniform blocks. The info log for the program can be retrieved with getProgramInfoLog
. We can also check if the program linked successfuly, and if it is a valid program, with isProgramLinked
and isProgramValid
respectively.
Example
shader.prepareProgram();
void setUniform(std::string name, void *value) void setUniform(std::string name, int value) void setUniform(std::string name, float value)
Sets the uniform name
to value
. The value can be a float, int, or a pointer to a float, int, float array, or int array.
Example
shader.setUniform("texUnit", 0); float aVec[4] = {0.0f, 1.0f, 2.0f, 3.0f}; shader.setUniform("myVec", aVec);
static void setBlock(std::string name, void *value)
Block information is stored in static variables. The idea is that a block can be used in many different shaders, hence, two blocks with the same name in two different shaders are treated as the same block. Therefore, to set a block, no instance is required.
Use GL query commands to check for strides on the uniforms inside a block. Make sure that the data structure has the same length as the uniform block, and that every piece of data has the same alignment.
Example
VSShaderLib::setBlock("Matrices", matStruct);
static void setBlockUniform(std::string blockName, std::string uniformName, void *value)
Sets a uniform inside a named block. Take notice of the strides used in matrices and arrays. Use GL queries to make sure data is properly aligned.
Example:
VSShaderLib::setBlockUniform("Matrices", "NormalMatrix", mat);
static void setBlockUniformArrayElement( std::string blockName, std::string uniformName, int arrayIndex, void *value)
Sets an array component uniform inside a block.
Example
VSShaderLib::setBlockUniformArrayElement( "Material", "ColorComponent", 2, ambient);
GLuint getProgramIndex() GLuint getShaderIndex()
Returns the program and shader index
Example
glUseProgram(shader.getProgram());
std::string getProgramInfoLog() std::string getShaderInfoLog(VSShaderLib::ShaderType) std::string getAllInfoLogs()
Returns the respective info logs. The last function returns all info logs in a single string.
bool isProgramValid() bool isProgramLinked() bool isShaderCompiled(VSShaderLib::ShaderType)
Returns the status of several components of an instance
Sorry, I can not reproduce the above problem now.
When I posted the last message, I was using Nvidia Quadro 4800 and driver 304.64. One day q4800 was burnt and the driver got updated. Now I am using Nvidia GeForce GTX 570 and driver 304.88. vsShader works like charm.
Thanks for the lib. And sorry for the noise.
I use assimp.cpp as a start of one demo. It works.
But I encounter a problem with multiple sampler in frag shader. Suppose I have these lines in frag shader:
uniform sampler2D colorTex;
uniform sampler2D depthTex;
And these lines for binding texture unit:
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, g_color_tex);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, g_depth_tex);
And the following for set uniforms:
shaderQuad.init();
shaderQuad.loadShader;
shaderQuad.loadShader
shaderQuad.setProgramOutput(0,”outputF”);
shaderQuad.setVertexAttribName;
shaderQuad.prepareProgram();
shaderQuad.setUniform(“colorTex”, 0);
shaderQuad.setUniform(“depthTex”, 1);
The problem is only the texture in texture unit 0 is valid. All textures will be valid if I use glUniformi directly:
glCreateProgram, glCreateShader, glShaderSource, glAttachShader, glLinkProgram … tons of code
glUseProgram(quad_prog_id);
glUniform1i(glGetUniformLocation(quad_prog_id, “colorTex”), 0);
glUniform1i(glGetUniformLocation(quad_prog_id, “depthTex”), 1);
So please help me with multiple textures in shader and vsShaderLib.