Help end child hunger

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
 

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: