Tutorial1: Rendering shapes with glDrawRangeElements, VAO, VBO, shaders (C++ / freeGLUT)
Overview
This is just a short tutorial about drawing primitives in OpenGL 3.3 without using deprecated functionality. We'll just draw 1 quad (as 2 triangles) and 1 triangle.
You can use any C++ compiler on any OS.
The code demonstrates a few key features :
All geometry is uploaded to IBO/VBO. We also wrap these up in a VAO. Rendering is done with glDrawRangeElements instead of the typical glDrawArrays or glDrawElements. The shaders are GL 3.3 specific although they can easily be ported to 3.2 or 3.1 or 3.0 or 2.1 and 2.0.
We are using freeGLUT which is similar to GLUT except it is open source and also up to date.
We'll be calling glutInitContextVersion(3, 3) and glutInitContextFlags(GLUT_CORE_PROFILE).
You can download from http://freeglut.sourceforge.net
We'll be using GLEW which will get all the GL function pointers for us. If you are new to GL, read about it at http://www.opengl.org/wiki/Getting_started#Accessing_OpenGL_functions
Download GLEW from http://glew.sourceforge.net
The Code
The entire code is a single C++ file. Copy it to your computer.
As for the shaders, there are 4 files. Copy them to your computer. Name them Shader1.vert, Shader1.frag, Shader2.vert, Shader2.frag.
Some things to note are that I don't know what would happen if you call glutInitContextVersion(3, 3) on a computer that doesn't support GL 3.3. The documentation doesn't say anything http://freeglut.sourceforge.net/docs/api.php
We are also using __glutCreateWindowWithExit so that we could clean up when the program exits, but it doesn't seem to work.
So we use __glutCreateWindowWithExit to create the window and it also sets up the GL 3.3 context. Then we init GLEW and this is what gets the function pointers. We then just get the GL version and run a dummy code that checks extensions. We then load all shaders, create the geometry and render the scene.
The vertex format for the quad is VC which means vertex and color. The vertices are float x, y, z. The color is unsigned int and RGBA. The shader outputs the RGBA color. You'll see a nicely colored quad.
The vertex format for the triangle is VNT which means vertex, normal and texcoord. We aren't using texcoords in this example. The normals are used for a simple lighting.
//In this example, we will use a few IBO/VBO and a few VAO. //We will use glDrawRangeElements to render some shapes. //We will use a couple of simple shaders. GL 3.3 shaders. //We will create a 3.3 forward context with freeGLUT. // //We are using GLEW to get function pointers. // //As for the VBO, we will use an interleaved vertex format. //Vertex, texcoords and normals. //Vertex and color. // //As for the IBO, we will use 16 bit unsigned integers. // //http://freeglut.sourceforge.net //http://glew.sourceforge.net #include <GL/glew.h> #include <GL/freeglut.h> #include <iostream> #include <fstream> #include <string> #include <sstream> using namespace std; #define BUFFER_OFFSET(i) ((void*)(i)) //Vertex, tex0 // //SIZE : 4+4+4 +4+4 = 4*6 = 20 bytes //It's better to make it multiple of 32 //32-20 = 12 bytes (of garbage should be added) //12/4 = 3 floats should be added struct TVertex_VT { float x, y, z; float s0, t0; float padding[3]; }; //Vertex, normal, tex0 // //SIZE : 4+4+4 +4+4+4 +4+4 = 4*8 = 32 bytes struct TVertex_VNT { float x, y, z; float nx, ny, nz; float s0, t0; }; //Vertex, color // //SIZE : 4+4+4 +4 = 4*4 = 16 bytes //It's better to make it multiple of 32 //32-16 = 16 bytes (of garbage should be added) //16/4 = 4 floats should be added struct TVertex_VC { float x, y, z; unsigned int color; float padding[4]; }; //Globals //A quad GLushort pindex_quad[6]; TVertex_VC pvertex_quad[4]; //A triangle GLushort pindex_triangle[3]; TVertex_VNT pvertex_triangle[3]; //1 VAO for the quad //1 VAO for the triangle GLuint VAOID[2]; //1 IBO for the quad (Index Buffer Object) //1 IBO for the triangle GLuint IBOID[2]; //1 IBO for the quad (Vertex Buffer Object) //1 IBO for the triangle GLuint VBOID[2]; //1 shader for the quad //1 shader for the triangle GLuint ShaderProgram[2]; GLuint VertexShader[2]; GLuint FragmentShader[2]; int ProjectionModelviewMatrix_Loc[2]; //The location of ProjectionModelviewMatrix in the shaders // loadFile - loads text file into char* fname // allocates memory - so need to delete after use // size of file returned in fSize std::string loadFile(const char *fname) { std::ifstream file(fname); if(!file.is_open()) { cout << "Unable to open file " << fname << endl; exit(1); } std::stringstream fileData; fileData << file.rdbuf(); file.close(); return fileData.str(); } // printShaderInfoLog // From OpenGL Shading Language 3rd Edition, p215-216 // Display (hopefully) useful error messages if shader fails to compile void printShaderInfoLog(GLint shader) { int infoLogLen = 0; int charsWritten = 0; GLchar *infoLog; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLen); if (infoLogLen > 0) { infoLog = new GLchar[infoLogLen]; // error check for fail to allocate memory omitted glGetShaderInfoLog(shader, infoLogLen, &charsWritten, infoLog); cout << "InfoLog : " << endl << infoLog << endl; delete [] infoLog; } } void InitGLStates() { glShadeModel(GL_SMOOTH); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glReadBuffer(GL_BACK); glDrawBuffer(GL_BACK); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); glDepthMask(TRUE); glDisable(GL_STENCIL_TEST); glStencilMask(0xFFFFFFFF); glStencilFunc(GL_EQUAL, 0x00000000, 0x00000001); glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); glFrontFace(GL_CCW); glCullFace(GL_BACK); glEnable(GL_CULL_FACE); glClearColor(1.0, 0.0, 0.0, 0.0); glClearDepth(1.0); glClearStencil(0); glDisable(GL_BLEND); glDisable(GL_ALPHA_TEST); glDisable(GL_DITHER); glActiveTexture(GL_TEXTURE0); } int LoadShader(const char *pfilePath_vs, const char *pfilePath_fs, bool bindTexCoord0, bool bindNormal, bool bindColor, GLuint &shaderProgram, GLuint &vertexShader, GLuint &fragmentShader) { shaderProgram=0; vertexShader=0; fragmentShader=0; // load shaders & get length of each int vlen; int flen; std::string vertexShaderString = loadFile(pfilePath_vs); std::string fragmentShaderString = loadFile(pfilePath_fs); vlen = vertexShaderString.length(); flen = fragmentShaderString.length(); if(vertexShaderString.empty()) { return -1; } if(fragmentShaderString.empty()) { return -1; } vertexShader = glCreateShader(GL_VERTEX_SHADER); fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); const char *vertexShaderCStr = vertexShaderString.c_str(); const char *fragmentShaderCStr = fragmentShaderString.c_str(); glShaderSource(vertexShader, 1, (const GLchar **)&vertexShaderCStr, &vlen); glShaderSource(fragmentShader, 1, (const GLchar **)&fragmentShaderCStr, &flen); GLint compiled; glCompileShader(vertexShader); glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &compiled); if(compiled==FALSE) { cout << "Vertex shader not compiled." << endl; printShaderInfoLog(vertexShader); glDeleteShader(vertexShader); vertexShader=0; glDeleteShader(fragmentShader); fragmentShader=0; return -1; } glCompileShader(fragmentShader); glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &compiled); if(compiled==FALSE) { cout << "Fragment shader not compiled." << endl; printShaderInfoLog(fragmentShader); glDeleteShader(vertexShader); vertexShader=0; glDeleteShader(fragmentShader); fragmentShader=0; return -1; } shaderProgram = glCreateProgram(); glAttachShader(shaderProgram, vertexShader); glAttachShader(shaderProgram, fragmentShader); glBindAttribLocation(shaderProgram, 0, "InVertex"); if(bindTexCoord0) glBindAttribLocation(shaderProgram, 1, "InTexCoord0"); if(bindNormal) glBindAttribLocation(shaderProgram, 2, "InNormal"); if(bindColor) glBindAttribLocation(shaderProgram, 3, "InColor"); glLinkProgram(shaderProgram); GLint IsLinked; glGetProgramiv(shaderProgram, GL_LINK_STATUS, (GLint *)&IsLinked); if(IsLinked==FALSE) { cout << "Failed to link shader." << endl; GLint maxLength; glGetProgramiv(shaderProgram, GL_INFO_LOG_LENGTH, &maxLength); if(maxLength>0) { char *pLinkInfoLog = new char[maxLength]; glGetProgramInfoLog(shaderProgram, maxLength, &maxLength, pLinkInfoLog); cout << pLinkInfoLog << endl; delete [] pLinkInfoLog; } glDetachShader(shaderProgram, vertexShader); glDetachShader(shaderProgram, fragmentShader); glDeleteShader(vertexShader); vertexShader=0; glDeleteShader(fragmentShader); fragmentShader=0; glDeleteProgram(shaderProgram); shaderProgram=0; return -1; } return 1; //Success } void CreateGeometry() { //A quad pvertex_quad[0].x=-0.8f; pvertex_quad[0].y=-0.5f; pvertex_quad[0].z=-0.9f; pvertex_quad[0].color=0xFFFFFFFF; pvertex_quad[1].x=0.0f; pvertex_quad[1].y=-0.5f; pvertex_quad[1].z=-0.9f; pvertex_quad[1].color=0xFFFF0000; pvertex_quad[2].x=-0.8f; pvertex_quad[2].y=0.5f; pvertex_quad[2].z=-0.9f; pvertex_quad[2].color=0xFF00FF00; pvertex_quad[3].x=0.0f; pvertex_quad[3].y=0.5f; pvertex_quad[3].z=-0.9f; pvertex_quad[3].color=0xFF0000FF; pindex_quad[0]=0; pindex_quad[1]=1; pindex_quad[2]=2; pindex_quad[3]=2; pindex_quad[4]=1; pindex_quad[5]=3; //The triangle pvertex_triangle[0].x=0.0f; pvertex_triangle[0].y=0.5f; pvertex_triangle[0].z=-1.0f; pvertex_triangle[0].nx=0.0f; pvertex_triangle[0].ny=0.0f; pvertex_triangle[0].nz=1.0f; pvertex_triangle[0].s0=0.0f; pvertex_triangle[0].t0=0.0f; pvertex_triangle[1].x=0.3f; pvertex_triangle[1].y=-0.5f; pvertex_triangle[1].z=-1.0f; pvertex_triangle[1].nx=0.0f; pvertex_triangle[1].ny=0.0f; pvertex_triangle[1].nz=1.0f; pvertex_triangle[1].s0=1.0f; pvertex_triangle[1].t0=0.0f; pvertex_triangle[2].x=0.8f; pvertex_triangle[2].y=0.5f; pvertex_triangle[2].z=-1.0f; pvertex_triangle[2].nx=0.0f; pvertex_triangle[2].ny=0.0f; pvertex_triangle[2].nz=1.0f; pvertex_triangle[2].s0=0.5f; pvertex_triangle[2].t0=1.0f; pindex_triangle[0]=0; pindex_triangle[1]=1; pindex_triangle[2]=2; //Create the IBO for the quad //16 bit indices glGenBuffers(1, &IBOID[0]); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBOID[0]); glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6*sizeof(GLushort), pindex_quad, GL_STATIC_DRAW); GLenum error=glGetError(); //Create the IBO for the triangle //16 bit indices //We could have actually made one big IBO for both the quad and triangle. glGenBuffers(1, &IBOID[1]); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBOID[1]); glBufferData(GL_ELEMENT_ARRAY_BUFFER, 3*sizeof(GLushort), pindex_triangle, GL_STATIC_DRAW); error=glGetError(); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); error=glGetError(); //Create VBO for the quad glGenBuffers(1, &VBOID[0]); glBindBuffer(GL_ARRAY_BUFFER, VBOID[0]); glBufferData(GL_ARRAY_BUFFER, 4*sizeof(TVertex_VC), pvertex_quad, GL_STATIC_DRAW); error=glGetError(); //Just testing glBindBuffer(GL_ARRAY_BUFFER, 0); //Create VBO for the triangle glGenBuffers(1, &VBOID[1]); glBindBuffer(GL_ARRAY_BUFFER, VBOID[1]); glBufferData(GL_ARRAY_BUFFER, 3*sizeof(TVertex_VNT), pvertex_triangle, GL_STATIC_DRAW); //Just testing glBindBuffer(GL_ARRAY_BUFFER, 0); error=glGetError(); //VAO for the quad ********************* glGenVertexArrays(1, &VAOID[0]); glBindVertexArray(VAOID[0]); //Bind the VBO and setup pointers for the VAO glBindBuffer(GL_ARRAY_BUFFER, VBOID[0]); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(TVertex_VC), BUFFER_OFFSET(0)); glVertexAttribPointer(3, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(TVertex_VC), BUFFER_OFFSET(sizeof(float)*3)); glEnableVertexAttribArray(0); glDisableVertexAttribArray(1); glDisableVertexAttribArray(2); glEnableVertexAttribArray(3); //Bind the IBO for the VAO glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBOID[0]); //Second VAO setup ******************* //This is for the triangle glGenVertexArrays(1, &VAOID[1]); glBindVertexArray(VAOID[1]); //Bind the VBO and setup pointers for the VAO glBindBuffer(GL_ARRAY_BUFFER, VBOID[1]); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(TVertex_VNT), BUFFER_OFFSET(0)); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(TVertex_VNT), BUFFER_OFFSET(sizeof(float)*3)); glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(TVertex_VNT), BUFFER_OFFSET(sizeof(float)*6)); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glEnableVertexAttribArray(2); glDisableVertexAttribArray(3); //Bind the IBO for the VAO glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBOID[1]); //Just testing glBindVertexArray(0); glDisableVertexAttribArray(0); glDisableVertexAttribArray(1); glDisableVertexAttribArray(2); glDisableVertexAttribArray(3); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } void display() { float projectionModelviewMatrix[16]; //Just set it to identity matrix memset(projectionModelviewMatrix, 0, sizeof(float)*16); projectionModelviewMatrix[0]=1.0; projectionModelviewMatrix[5]=1.0; projectionModelviewMatrix[10]=1.0; projectionModelviewMatrix[15]=1.0; //Clear all the buffers glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); //Bind the shader that we want to use glUseProgram(ShaderProgram[0]); //Setup all uniforms for your shader glUniformMatrix4fv(ProjectionModelviewMatrix_Loc[0], 1, FALSE, projectionModelviewMatrix); //Bind the VAO glBindVertexArray(VAOID[0]); //At this point, we would bind textures but we aren't using textures in this example //Draw command //The first to last vertex is 0 to 3 //6 indices will be used to render the 2 triangles. This make our quad. //The last parameter is the start address in the IBO => zero glDrawRangeElements(GL_TRIANGLES, 0, 3, 6, GL_UNSIGNED_SHORT, NULL); //Bind the shader that we want to use glUseProgram(ShaderProgram[1]); //Setup all uniforms for your shader glUniformMatrix4fv(ProjectionModelviewMatrix_Loc[1], 1, FALSE, projectionModelviewMatrix); //Bind the VAO glBindVertexArray(VAOID[1]); //At this point, we would bind textures but we aren't using textures in this example //Draw command //The first to last vertex is 0 to 3 //3 indices will be used to render 1 triangle. //The last parameter is the start address in the IBO => zero glDrawRangeElements(GL_TRIANGLES, 0, 3, 3, GL_UNSIGNED_SHORT, NULL); glutSwapBuffers(); } void reshape(int w, int h) { glViewport(0, 0, w, h); } void ExitFunction(int value) { cout<<"Exit called."<<endl; glBindVertexArray(0); glDisableVertexAttribArray(0); glDisableVertexAttribArray(1); glDisableVertexAttribArray(2); glDisableVertexAttribArray(3); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glUseProgram(0); glDeleteBuffers(1, &IBOID[0]); glDeleteBuffers(1, &IBOID[1]); glDeleteBuffers(1, &VBOID[0]); glDeleteBuffers(1, &IBOID[1]); glDeleteVertexArrays(1, &VAOID[0]); glDeleteVertexArrays(1, &VAOID[1]); glDetachShader(ShaderProgram[0], VertexShader[0]); glDetachShader(ShaderProgram[0], FragmentShader[0]); glDeleteShader(VertexShader[0]); glDeleteShader(FragmentShader[0]); glDeleteProgram(ShaderProgram[0]); glDetachShader(ShaderProgram[1], VertexShader[1]); glDetachShader(ShaderProgram[1], FragmentShader[1]); glDeleteShader(VertexShader[1]); glDeleteShader(FragmentShader[1]); glDeleteProgram(ShaderProgram[1]); } int main (int argc, char* argv[]) { int i; int NumberOfExtensions; int OpenGLVersion[2]; glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL); //We want to make a GL 3.3 context glutInitContextVersion(3, 3); glutInitContextFlags(GLUT_CORE_PROFILE); glutInitWindowPosition(100, 50); glutInitWindowSize(600, 600); __glutCreateWindowWithExit("GL 3.3 Test", ExitFunction); //Currently, GLEW uses glGetString(GL_EXTENSIONS) which is not legal code //in GL 3.3, therefore GLEW would fail if we don't set this to TRUE. //GLEW will avoid looking for extensions and will just get function pointers for all GL functions. glewExperimental=TRUE; GLenum err=glewInit(); if(err!=GLEW_OK) { //Problem: glewInit failed, something is seriously wrong. cout<<"glewInit failed, aborting."<<endl; exit(1); } //The old way of getting the GL version //This will give you something like 3.3.2895 WinXP SSE //where the 3.3 would be the version number and the rest is vendor //dependent information. //In this case, 2895 is a build number. //Then the OS : WinXP //Then CPU features such as SSE cout<<"OpenGL version = "<<glGetString(GL_VERSION)<<endl; //This is the new way for getting the GL version. //It returns integers. Much better than the old glGetString(GL_VERSION). glGetIntegerv(GL_MAJOR_VERSION, &OpenGLVersion[0]); glGetIntegerv(GL_MINOR_VERSION, &OpenGLVersion[1]); cout<<"OpenGL major version = "<<OpenGLVersion[0]<<endl; cout<<"OpenGL minor version = "<<OpenGLVersion[1]<<endl<<endl; //The old method to get the extension list is obsolete. //You must use glGetIntegerv and glGetStringi glGetIntegerv(GL_NUM_EXTENSIONS, &NumberOfExtensions); //We don't need any extensions. Useless code. for(i=0; i<NumberOfExtensions; i++) { const GLubyte *ccc=glGetStringi(GL_EXTENSIONS, i); } InitGLStates(); if(LoadShader("Shader1.vert", "Shader1.frag", false, false, true, ShaderProgram[0], VertexShader[0], FragmentShader[0])==-1) { exit(1); } else { ProjectionModelviewMatrix_Loc[0]=glGetUniformLocation(ShaderProgram[0], "ProjectionModelviewMatrix"); } if(LoadShader("Shader2.vert", "Shader2.frag", true, true, false, ShaderProgram[1], VertexShader[1], FragmentShader[1])==-1) { exit(1); } else { ProjectionModelviewMatrix_Loc[1]=glGetUniformLocation(ShaderProgram[1], "ProjectionModelviewMatrix"); } CreateGeometry(); glutDisplayFunc(display); glutReshapeFunc(reshape); glutMainLoop(); return 0; }
Shader1.vert : notice that we have 1 uniform called ProjectionModelviewMatrix. We set this to identity in our C++ code.
The color has the smooth qualifier which will instruct the GPU to do bilinear interpolation across the polygon.
InVertex and InColor are attributes which are setup with a call to glBindAttribLocation. After that, you must link the shader and check for success (glLinkProgram and glGetProgramiv(shaderProgram, GL_LINK_STATUS, (GLint *)&IsLinked))
//[VERTEX SHADER] #version 330 in vec3 InVertex; in vec4 InColor; smooth out vec4 Color; uniform mat4 ProjectionModelviewMatrix; void main() { gl_Position = ProjectionModelviewMatrix * vec4(InVertex, 1.0); Color = InColor; }
Shader1.frag : here we have the input Color. We just write it out to FragColor. FragColor is our own output variable which GL automatically bind to output 0, therefore we don't need to set it up from our C++ side of the code.
//[FRAGMENT SHADER] #version 330 smooth in vec4 Color; out vec4 FragColor; void main() { FragColor = Color; }
Shader2.vert
//[VERTEX SHADER] #version 330 in vec3 InVertex; in vec3 InNormal; smooth out vec3 LightVector0; smooth out vec3 EyeNormal; uniform mat4 ProjectionModelviewMatrix; void main() { gl_Position = ProjectionModelviewMatrix * vec4(InVertex, 1.0); LightVector0 = vec3(1.0, 1.0, 1.0); EyeNormal = InNormal; }
Shader2.frag
//[FRAGMENT SHADER] #version 330 smooth in vec3 LightVector0; smooth in vec3 EyeNormal; out vec4 FragColor; void main() { vec3 eyeNormal; vec3 lightVector; float dotProduct; eyeNormal = normalize(EyeNormal); lightVector = normalize(LightVector0); dotProduct = dot(eyeNormal, lightVector); FragColor = vec4(dotProduct); }