Professional Documents
Culture Documents
// Set up rendering context, call by reference void setupRC(SDL_WindowID &window, SDL_GLContext &context) { if (SDL_Init(SDL_INIT_VIDEO) < 0) // Initialize video exitFatalError("Unable to initialize SDL"); // Request an OpenGL 3.0 context. Not able to use // should default to core profile with OpenGL 3.2 // If you request a context not supported by your // will be created. SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL to choose profile (yet), or later drivers, no OpenGL context 3); 0);
// double buffering on
// Turn on x4 multisampling anti-aliasing (MSAA) SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4); // Create 800x600 window window = SDL_CreateWindow("SDL/GLM/OpenGL Demo", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN ); if (!window) // Check window was created OK exitFatalError("Unable to create window"); context = SDL_GL_CreateContext(window); // Create OpenGL context // Set swap buffers to sync with monitor's vertical refresh rate SDL_GL_SetSwapInterval(1); }
Additionally, edit the clean up calls at the end of main( ) to include a call to delete the OpenGL context:
SDL_GL_DeleteContext(glContext); SDL_DestroyWindow(hWindow); SDL_Quit();
Test OpenGL
At this point you should have just enough code to test OpenGL! Create a draw function to set a glClearColor, and call glClear(GL_COLOR_BUFFER_BIT) to clear the window. Try setting the clear color to red. End the draw function with a call to the SDL swap buffers function:
SDL_GL_SwapWindow(window); // swap buffers
Call the draw function from the event loop and test your program.
The complete draw function might simply consist of the following lines at this point:
void draw(const SDL_WindowID &window) { glClearColor(1.0, 1.0, 1.0, 1.0); // set background colour glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clear window // this is where we will be drawing stuff! SDL_GL_SwapWindow(window); // swap buffers }
} else { cout << "Unable to open file " << fname << endl; // should ideally set a flag or use exception handling // so that calling function can decide what to do now } return memblock; } // printShaderError // Display (hopefully) useful error messages if shader fails to compile or link void printShaderError(GLint shader) { int maxLength = 0; int logLength = 0; GLchar *logMessage; // Find out how long the error message is if (glIsShader(shader)) glGetProgramiv(shader, GL_INFO_LOG_LENGTH, &maxLength); else glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength); if (maxLength > 0) // If message has length > 0 { logMessage = new GLchar[maxLength]; if (glIsShader(shader)) glGetProgramInfoLog(shader, maxLength, &logLength, logMessage); else glGetShaderInfoLog(shader,maxLength, &logLength, logMessage); cout << "Shader Info Log:" << endl << logMessage << endl; delete [] logMessage; } } GLuint initShaders(char *vertFile, char *fragFile) { GLuint p, f, v; // Handles for shader program & vertex and fragment shaders v = glCreateShader(GL_VERTEX_SHADER); // Create vertex shader handle f = glCreateShader(GL_FRAGMENT_SHADER); // " fragment shader handle const char *vertSource = loadFile(vertFile); // load vertex shader source const char *fragSource = loadFile(fragFile); // load frag shader source // Send the shader source to the GPU // Strings here are null terminated - a non-zero final parameter can be // used to indicate the length of the shader source instead glShaderSource(v, 1, &vertSource,0); glShaderSource(f, 1, &fragSource,0); GLint compiled, linked; // return values for checking for compile & link errors // compile the vertex shader and test for errors glCompileShader(v); glGetShaderiv(v, GL_COMPILE_STATUS, &compiled); if (!compiled) { cout << "Vertex shader not compiled." << endl;
printShaderError(v); } // compile the fragment shader and test for errors glCompileShader(f); glGetShaderiv(f, GL_COMPILE_STATUS, &compiled); if (!compiled) { cout << "Fragment shader not compiled." << endl; printShaderError(f); } p = glCreateProgram(); // create the handle for the shader program glAttachShader(p,v); // attach vertex shader to program glAttachShader(p,f); // attach fragment shader to program glBindAttribLocation(p,0,"in_Position"); // Bind position to attribute 0 glBindAttribLocation(p,1,"in_Color"); // Bind color to attribute 0 glLinkProgram(p); // link the shader program and test for errors glGetProgramiv(p, GL_LINK_STATUS, &linked); if(!linked) { cout << "Program not linked." << endl; printShaderError(p); } glUseProgram(p); // Make the shader program the current active program
delete [] vertSource; // Don't forget to free allocated memory delete [] fragSource; // We allocated this in the loadFile function... return p; // Return the shader program handle }
The Vertex and Fragment Shader Programs You also need to provide the shader programs themselves. First, a handle is required for the compiled shader program set this up as a global variable for now:
GLuint shaderprogram; // handle for shader program
The following code provides the listings for the vertex and fragment shaders:
// simple vertex shader - simple.vert #version 130 // OpenGL 3.2 replace above with #version 150 uniform mat4x4 MVP; in vec3 in_Position; in vec3 in_Color; out vec3 ex_Color; // simple shader program // multiply each vertex position by the MVP matrix void main(void) { gl_Position = MVP * vec4(in_Position, 1.0); ex_Color = in_Color; // pass colour unmodified }
// simple fragment shader simple.frag #version 130 // OpenGL 3.2 replace above with #version 150 // Some drivers require the following precision highp float; in // // // // vec3 ex_Color; GLSL versions after 1.3 remove the built in type gl_FragColor If using a shader lang version greater than #version 130 you *may* need to uncomment the following line: out vec4 gl_FragColor
Set up the Vertex Array and Buffer Objects You still need to specify the mesh data itself, and set up the Vertex Buffer Objects and Vertex Array Object that will hold the mesh data. You can do this in an init function. This should be called from main() before entering the event loop. The init function is also a good place to enable various OpenGL options - such as depth testing and back face culling. Variables are also required for the array and buffer objects set these up as globals for now.
GLuint vao, vbo[2]; // Handles for a VAO and two VBOs void init(void) { const GLfloat pyramid[5][3] = { // a simple pyramid { 0.0, 0.5, 0.0 }, // top { -1.0, -0.5, 1.0 }, // front bottom left { 1.0, -0.5, 1.0 }, // front bottom right { 1.0, -0.5, -1.0 }, // back bottom right { -1.0, -0.5, -1.0 } }; // back bottom left const GLfloat colours[5][3] = { { 0.0, 0.0, 0.0 }, // black { 1.0, 0.0, 0.0 }, // red { 0.0, 1.0, 0.0 }, // green { 0.0, 0.0, 1.0 }, // blue { 1.0, 1.0, 0.0 } }; // yellow // Create and start shader program shaderprogram = initShaders("simple.vert", "simple.frag"); glGenVertexArrays(1, &vao); // Allocate & assign Vertex Array Object (VAO) glBindVertexArray(vao); // Bind VAO as current object glGenBuffers(2, vbo); // Allocate two Vertex Buffer Objects (VBO) glBindBuffer(GL_ARRAY_BUFFER, vbo[0]); // Bind 1st VBO as active buffer object // Copy the vertex data from diamond to the VBO // 15 * sizeof(GLfloat) is the size of the pyramid array glBufferData(GL_ARRAY_BUFFER, 15 * sizeof(GLfloat), pyramid, GL_STATIC_DRAW); // Position data is going into attribute index 0 & has 3 floats per vertex glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0); glEnableVertexAttribArray(0); // Enable attribute index 0 (position)
glBindBuffer(GL_ARRAY_BUFFER, vbo[1]); // bind 2nd VBO as active buf obj // Copy the colour data from colours to our buffer // 15 * sizeof(GLfloat) is the size of the colours array glBufferData(GL_ARRAY_BUFFER, 15 * sizeof(GLfloat), colours, GL_STATIC_DRAW); // Colour data is going into attribute index 1 & has 3 floats per vertex glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, 0); glEnableVertexAttribArray(1); // Enable attribute index 1 (color) // OpenGL is state based - we normally need to keep references to shader, VAO, // VBO, etc., to allow swapping between different ones. In this program there // is only one shader program and one VAO glEnable(GL_DEPTH_TEST); // enable depth testing }
Cleanup Function Having created a range of data objects, it is wise to clean these up too. Create a cleanup function, to be called from main() after leaving the event loop:
void cleanup(void) { glUseProgram(0); glDisableVertexAttribArray(0); glDisableVertexAttribArray(1); // could also detach shaders glDeleteProgram(shaderprogram); glDeleteBuffers(2, vbo); glDeleteVertexArrays(1, &vao); }
Actually Draw Something! After all that, we can finally get to the rendering code itself. To keep the current angle of rotation of the pyramid you can either create a global variable (easy but messy), or add a variable to main that gets passed into the draw function (not very difficult, a nicer approach). Taking the easy option for now, create a global variable, r, of type float. To use GLM, the core GLM library needs to be included and we will also be using two of the extension libraries. Add the following includes:
#include <glm\glm.hpp> #include <glm\gtc\matrix_transform.hpp> #include <glm\gtc\type_ptr.hpp> // some versions of glm require: #include <glm\gtc\matrix_projection.hpp> // and may require the type_ptr include to be changed to <glm\gtx\type_ptr.hpp>
Then add the following draw function (dont forget to uncomment the call to draw() in the event loop):
void draw(const SDL_WindowID &window) { glClearColor(1.0, 1.0, 1.0, 1.0); // set background colour glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clear window
// Create perspective projection matrix glm::mat4 projection = glm::perspective(45.0f, 4.0f / 3.0f, 1.0f, 100.f); // Apply model view transformations glm::mat4 identity(1.0); glm::mat4 modelview = glm::translate( identity, glm::vec3(0.0f, 0.0f, -4.0f)); modelview = glm::rotate( modelview, r, glm::vec3(0.0f,1.0f,0.0f)); glm::mat4 MVP = projection * modelview; // Calculate final MVP matrix // pass MVP as uniform into shader int mvpIndex = glGetUniformLocation(shaderprogram, "MVP"); glUniformMatrix4fv(mvpIndex, 1, GL_FALSE, glm::value_ptr(MVP)); glDrawArrays(GL_TRIANGLE_FAN, 0, 5); // draw the pyramid r+=0.5; SDL_GL_SwapWindow(window); // swap buffers }
Further Exercises
1. Rotate the pyramid around one of its corners instead of the centre 2. Change the program to load mesh data from file instead of having it hardcoded 3. Create a modification to one or both of the simple shaders, and re-write your program to draw two rotating pyramids: one rendered with each shader (e.g. one colour pyramid, one monochrome pyramid) 4. Research SDL i/o and event handling modify the code to allow the user to move the pyramid around using the arrow keys and/or the WASD keys and/or the mouse. 5. Explore how the error handling in the program could be improved can you find any crash bugs in the code? How would you resolve these?
Troubleshooting
Perhaps the most confusing errors are run-time unhandled exception errors. From experience, the most common causes are either (a) using lib and dll files from different versions of the GLEW or SDL libraries (perhaps with some dll files left in the System folders from previous work) or (b) omitting the code to generate the VBOs. If you modify your program to use extra buffer objects, forgetting to change the code to create the extra buffer objects is an easy mistake.
License
This work by Daniel Livingstone, University of the West of Scotland, is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to Creative Commons, 444 Castro Street, Suite 900, Mountain View, California, 94041, USA.