GLSL Tutorial – OpenGL skeleton
Prev: Interpolation Issues | Next: Hello World |
Before we start by presenting the OpenGL shaders, we’ll go through the OpenGL application that we’ll use, at least in the first shaders. To cover for some of the lost functionality in the OpenGL core versions, and to prevent the code from getting too extense we’ll be using two of the Very Simple Libraries: Math and Shaders.
So let’s have a look at some relevant bits in the OpenGL application.
main function
Lets start with the main function. To create the OpenGL context and provide the windowing interface we’ll resort to freeGLUT. To access the extensions and OpenGL functionality we’ll adopt GLEW. The initialization procedure involves asking GLUT to create a context, as described in the initialization section of the GLUT tutorial.
int main(int argc, char **argv) { // GLUT initialization glutInit(&argc, argv); glutInitDisplayMode(GLUT_DEPTH|GLUT_DOUBLE|GLUT_RGBA|GLUT_MULTISAMPLE); glutInitContextVersion (3, 3); glutInitContextProfile (GLUT_CORE_PROFILE ); glutInitContextFlags(GLUT_DEBUG); glutInitWindowPosition(100,100); glutInitWindowSize(512,512); glutCreateWindow("Lighthouse3D - Simple Shader Demo"); ...
We then proceed to the callback registration, also detailed in the GLUT tutorial.
... // Callback Registration glutDisplayFunc(renderScene); glutReshapeFunc(changeSize); glutIdleFunc(renderScene); // Mouse and Keyboard Callbacks glutKeyboardFunc(processKeys); glutMouseFunc(processMouseButtons); glutMotionFunc(processMouseMotion); glutMouseWheelFunc ( mouseWheel ) ; ...
Moving on, we initialize GLEW and print some information about the OpenGL context we created.
... // Init GLEW glewExperimental = GL_TRUE; glewInit(); // print context information printf ("Vendor: %s\n", glGetString (GL_VENDOR)); printf ("Renderer: %s\n", glGetString (GL_RENDERER)); printf ("Version: %s\n", glGetString (GL_VERSION)); printf ("GLSL: %s\n", glGetString (GL_SHADING_LANGUAGE_VERSION)); ...
Up until now it is more or less standard stuff that we’ll see over and over. Now we’re ready to really start doing our stuff. We’re going to call the setup functions. One for the shaders, one to initialize our buffers and some OpenGL settings, and finally a function to initialize the VSL libs we’re using. Then we’ll call glutMainLoop
.
... if (!setupShaders()) return(1); initOpenGL() initVSL(); // GLUT main loop glutMainLoop(); return(0); }
And that concludes our main function.
Setting up the shaders
Setting up the shaders in our examples will be done using VSShaderLib to simplify the code. The code starts by loading and compiling each of the source code files, for both the vertex and fragment shader. Afterwards it sets the semantics of the variables that appear in the shaders, see the Hello World example.
The call to prepareProgram
links the program, and if successfull it retrieves all the information regarding the usage of uniform variables present in the shaders. This will be useful later to set those variables.
We then display the infoLog for both the individual shaders and the program, and return a value indication if we have a valid program.
The code for this function is as follows:
GLuint setupShaders() { // Shader for drawing the cube shader.init(); shader.loadShader(VSShaderLib::VERTEX_SHADER, "shaders/helloWorld.vert"); shader.loadShader(VSShaderLib::FRAGMENT_SHADER, "shaders/helloWorld.frag"); // set semantics for the shader variables shader.setProgramOutput(0,"outputF"); shader.setVertexAttribName(VSShaderLib::VERTEX_COORD_ATTRIB, "position"); shader.prepareProgram(); printf("InfoLog for Hello World Shader\n%s\n\n", shader.getAllInfoLogs().c_str()); return(shader.isProgramValid()); }
OpenGL and buffer initialization
In this function we’re performing more of the initialization required for our application. This includes, computing the initial camera position based on spherical coordinates, some OpenGL settings, and the Vertex Array Object (VAO) creation.
The camera position is placed in a sphere centered on the origin and with radius r. It is defined with spherical coordinates (alpha, beta, r), all global variables, and the mouse controls these variables. Before we can set up the camera in our rendering function we need to convert them to cartesian coordinates. This must be done every time the camera moves, and here, in the initialization to get the initial cartesian values.
We then move on to some common OpenGL initializations, such as enabling culling, multisampling, and the depth test.
The last step is to set up the Vertex Array Object which will contain our geometry, in this case a cube whose data is defined in the header file. We begin the generating the VAO, and binding it. Then we create four buffers to hold the data, three for vertex attributes, and one for the indexes.
void initOpenGL() { // set the camera position based on its spherical coordinates camX = r * sin(alpha * 3.14f / 180.0f) * cos(beta * 3.14f / 180.0f); camZ = r * cos(alpha * 3.14f / 180.0f) * cos(beta * 3.14f / 180.0f); camY = r * sin(beta * 3.14f / 180.0f); // some GL settings glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); glEnable(GL_MULTISAMPLE); glClearColor(1.0f, 1.0f, 1.0f, 1.0f); // create the VAO glGenVertexArrays(1, &vao); glBindVertexArray(vao); // create buffers for our vertex data GLuint buffers[4]; glGenBuffers(4, buffers); //vertex coordinates buffer glBindBuffer(GL_ARRAY_BUFFER, buffers[0]); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glEnableVertexAttribArray(VSShaderLib::VERTEX_COORD_ATTRIB); glVertexAttribPointer(VSShaderLib::VERTEX_COORD_ATTRIB, 4, GL_FLOAT, 0, 0, 0); //texture coordinates buffer glBindBuffer(GL_ARRAY_BUFFER, buffers[1]); glBufferData(GL_ARRAY_BUFFER, sizeof(texCoords), texCoords, GL_STATIC_DRAW); glEnableVertexAttribArray(VSShaderLib::TEXTURE_COORD_ATTRIB); glVertexAttribPointer(VSShaderLib::TEXTURE_COORD_ATTRIB, 2, GL_FLOAT, 0, 0, 0); //normals buffer glBindBuffer(GL_ARRAY_BUFFER, buffers[2]); glBufferData(GL_ARRAY_BUFFER, sizeof(normals), normals, GL_STATIC_DRAW); glEnableVertexAttribArray(VSShaderLib::NORMAL_ATTRIB); glVertexAttribPointer(VSShaderLib::NORMAL_ATTRIB, 3, GL_FLOAT, 0, 0, 0); //index buffer glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[3]); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(faceIndex), faceIndex, GL_STATIC_DRAW); // unbind the VAO glBindVertexArray(0); }
VSL initialization
VSL, in particular the math lib, requires a small bit of initialization to facilitate the communication with the shaders. Setting the uniforms for the matrices we’re going to use is done with a single function call as we’ll see in the rendering function, but in order to do that we need to define some semantics for the uniform variables defined in the shaders. In here we’re assuming that our shaders will define all our matrix uniforms inside a block named Matrices.
void initVSL() { vsml = VSMathLib::getInstance(); // tell VSL the uniform block name vsml->setUniformBlockName("Matrices"); // set semantics for the matrix variables vsml->setUniformName(VSMathLib::PROJ_VIEW_MODEL, "pvm"); vsml->setUniformName(VSMathLib::NORMAL, "normal"); vsml->setUniformName(VSMathLib::VIEW_MODEL, "viewModel"); vsml->setUniformName(VSMathLib::VIEW, "view"); }
changeSize function
The viewport is set to be the entire window. Then the function uses the math lib to set up the projection matrix. The functions of this lib are very similar to those in the deprecated OpenGL and GLU. See the documentation for more info.
void changeSize(int w, int h) { float ratio; // prevent a divide by zero, when window is zero height if(h == 0) h = 1; // set the viewport to be the entire window glViewport(0, 0, w, h); // set the projection matrix ratio = (1.0f * w) / h; vsml->loadIdentity(VSMathLib::PROJECTION); vsml->perspective(53.13f, ratio, 0.1f, 1000.0f); }
The rendering function
This function starts by clearing the color and depth buffers, loads the identity matrix on the model and view matrices, sets up the camera, asks the math lib to make the matrices accessible from the shaders, and then binds and renders our VAO.
void renderScene(void) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // load identity matrices vsml->loadIdentity(VSMathLib::VIEW); vsml->loadIdentity(VSMathLib::MODEL); // set the camera vsml->lookAt(camX, camY, camZ, 0,0,0, 0,1,0); // use our shader glUseProgram(shader.getProgramIndex()); // send the matrices to the uniform buffer vsml->matricesToGL(); // draw VAO glBindVertexArray(vao); glDrawElements(GL_TRIANGLES, faceCount*3, GL_UNSIGNED_INT, 0); //swap buffers glutSwapBuffers(); }
And that’s it! Move on to the next section to see the first shader example.
Prev: Interpolation Issues | Next: Hello World |