Basic OpenGL 2.0 Tutorial
Dec 20, 2012
10 minute read

I’ve been working on my own 3d game engine in C called PortableGLUE and have had to learn OpenGL from scratch. During that process I searched far and wide on the internet for information on just getting started. I found a lot of resources that covered the fixed pipeline that existed before OpenGL version 2 but these were not insightful for setting up a modern version 2.0 program. Also, when I looked through books on the topic, all of the ones I encountered included their own graphics engine library and then explained OpenGL in terms of that library. I didn’t find a clear, simple, basic example of how to render a simple object. Now that I’ve gotten far in my own library, I decided that now would be a good time to create that type of tutorial myself.

This tutorial will skip the fixed pipeline completely and introduce you to the bare minimum needed to get going in modern OpenGL with programmable shaders in Linux and ANSI C (tested under Fedora 18 beta). This tutorial will also not explain OpenGL in depth and is meant to be used as an adjunct to other material. Using the code here as a starting point.

Only GCC, OpenGL and GLFW 2.x to create the application window are required. No additional libraries are used. Compiling this in Mac OS X should be just as simple. Compiling in Windows will be trickier; mingw will be needed as well as GLEW. For iOS and OpenGL ES 2.0+, this code will also work for the basics, but your objective-c host code will need to create the OpenGL canvas and a a few small modifications to the shaders need to be made. Eventually I’ll write a separate blog post to describe this process on other operating systems.

The code will be broken down into four listings presented in their entirety. You can get an archive of all the code listed here. A script is included in each listing folder to compile the code with GCC, but basically it looks like this:

gcc -g -Wall -o main-01 main-01.c -L/usr/local/lib -lGL -lglfw

That command compiles and links the program all in one shot. The additional link directory of /lusr/local/lib is used because I compiled my own GLFW 2.7 release.

Here’s the first listing:

/* Listing 1: Basic GLFW setup */

#include <GL/glfw.h>
#include <stdlib.h>


void cleanup_on_exit(void) 
{
	glfwTerminate();
}	

int main(void)
{
	const int window_height = 480;
	const int window_width = 640;

	GLfloat bg_color = 0.0f;
	int running = 1;
 
  	/* Initialize GLFW */
	atexit(cleanup_on_exit);
  	if (! glfwInit()) {
      		exit(EXIT_FAILURE);
  	}

  	/* Open an OpenGL window */
  	if (! glfwOpenWindow(window_width, window_height, 0, 0, 0, 0, 0, 0, GLFW_WINDOW)) {
  		exit(EXIT_FAILURE);
	}

	/* Create a viewport */
	glViewport(0, 0, window_width, window_height);

	/* Loop over and over while running */
	while (running) {
		/* OpenGL rendering goes here */
		glClearColor(bg_color, bg_color, bg_color, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		/* Do some drawing here ... */

		/* Swap out the front and back rendering buffers */
		glfwSwapBuffers();

		/* Check to see if we should quit */
		running = !glfwGetKey(GLFW_KEY_ESC) && glfwGetWindowParam(GLFW_OPENED);

	}

	return 0;
}
  • creates an exit handler to terminate GLFW
  • creates a GLFW window that is 640x480
  • sets the viewport
  • creates a basic draw loop that clears the screen and flips the buffers
  • closes the window when the escape key is pressed

We create the initial shader support in listing 2:

/* Listing 2: Basic loading of shaders */

#define GL_GLEXT_PROTOTYPES
#include <GL/glfw.h>
#include <GL/gl.h>
#include <GL/glext.h>
#include <stdlib.h>
#include <stdio.h>

/* Include the shaders as hard-coded strings */
const char* vertex_shader = 
"#version 120 \n"
"void main() \n"
"{ \n"
"	gl_Position = gl_Vertex; \n"
"} \n";

const char* fragment_shader = 
"#version 120 \n"
"void main (void) \n"
"{ \n"
"	gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); \n"
"} \n";



void cleanup_on_exit(void) 
{
	glfwTerminate();
}	

void print_shader_log(GLuint id)
{
	GLint log_length = 0;

	glGetShaderiv(id, GL_INFO_LOG_LENGTH, &log_length);
	if (log_length) {
		char* log = (char*) malloc(log_length);
		glGetShaderInfoLog(id, log_length, &log_length, log);
		fprintf(stderr, "Shader error: %s\n", log);
		free(log);
	}
}

void load_shader(GLenum t, const char* code, GLuint* id)
{
	GLint compiled_status = 0;

	/* create the shader in OpenGL */
	*id = glCreateShader(t);
	glShaderSource(*id, 1, &code, NULL);
	glCompileShader(*id);

	/* make sure the shader is compiled. if it's not compiled,
	   delete the shader in OpenGL and reset the id. */
	glGetShaderiv(*id, GL_COMPILE_STATUS, &compiled_status);
	if (compiled_status != GL_TRUE) {
		fprintf(stderr, "Shader compilation status == failed.\n");
		print_shader_log(*id);

		glDeleteShader(*id);
		*id = 0;;
	}
}

int main(void)
{
	const int window_height = 480;
	const int window_width = 640;

	GLfloat bg_color = 0.0f;
	GLuint vert_shader_id;
	GLuint frag_shader_id;
	GLuint shader_program_id;
	GLint link_status = 0;
	int running = 1;
 
  	/* Initialize GLFW */
	atexit(cleanup_on_exit);
  	if (! glfwInit()) {
      		exit(EXIT_FAILURE);
  	}

  	/* Open an OpenGL window */
  	if (! glfwOpenWindow(window_width, window_height, 0, 0, 0, 0, 0, 0, GLFW_WINDOW)) {
  		exit(EXIT_FAILURE);
	}

	/* Create a viewport */
	glViewport(0, 0, window_width, window_height);

	/* Load the shaders */
	load_shader(GL_VERTEX_SHADER, vertex_shader, &vert_shader_id);
	load_shader(GL_FRAGMENT_SHADER, fragment_shader, &frag_shader_id);
	fprintf(stdout, "Vert shader id: %d ; Frag shader id: %d\n", vert_shader_id, frag_shader_id);

	/* Compile them into a shader program */
	shader_program_id = glCreateProgram();
	glAttachShader(shader_program_id, vert_shader_id);
	glAttachShader(shader_program_id, frag_shader_id);
	glLinkProgram(shader_program_id);
	glGetProgramiv(shader_program_id, GL_LINK_STATUS, &link_status);
	if (! link_status) {
		fprintf(stderr, "Unable to link shader program.\n");
		print_shader_log(shader_program_id);
 		exit(EXIT_FAILURE);
	}
	fprintf(stdout, "Shader successfully linked.\n");

	/* Loop over and over while running */
	while (running) {
		/* OpenGL rendering goes here */
		glClearColor(bg_color, bg_color, bg_color, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		/* Do some drawing here ... */

		/* Swap out the front and back rendering buffers */
		glfwSwapBuffers();

		/* Check to see if we should quit */
		running = !glfwGetKey(GLFW_KEY_ESC) && glfwGetWindowParam(GLFW_OPENED);

	}

	return 0;
}
  • includes shader code as string literals for simplicity
  • add a debug function to print the error log if the shader fails to compile
  • adds a function to load and compile the shader
  • the main function now compiles the vertex and fragment shaders and then attaches them to a shader program

Now that we have the technicalities of shader loading and compiling dealt with, lets draw a triangle in listing 3:

/* Listing 3: Basic triangle drawing */

#define GL_GLEXT_PROTOTYPES
#include <GL/glfw.h>
#include <GL/gl.h>
#include <GL/glext.h>
#include <stdlib.h>
#include <stdio.h>

/* Include the shaders as hard-coded strings */
const char* vertex_shader = 
"#version 120 \n"
"void main() \n"
"{ \n"
"	gl_Position = gl_Vertex; \n"
"} \n";

const char* fragment_shader = 
"#version 120 \n"
"void main (void) \n"
"{ \n"
"	gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); \n"
"} \n";



void cleanup_on_exit(void) 
{
	glfwTerminate();
}	

void print_shader_log(GLuint id)
{
	GLint log_length = 0;

	glGetShaderiv(id, GL_INFO_LOG_LENGTH, &log_length);
	if (log_length) {
		char* log = (char*) malloc(log_length);
		glGetShaderInfoLog(id, log_length, &log_length, log);
		fprintf(stderr, "Shader error: %s\n", log);
		free(log);
	}
}

void load_shader(GLenum t, const char* code, GLuint* id)
{
	GLint compiled_status = 0;

	/* create the shader in OpenGL */
	*id = glCreateShader(t);
	glShaderSource(*id, 1, &code, NULL);
	glCompileShader(*id);

	/* make sure the shader is compiled. if it's not compiled,
	   delete the shader in OpenGL and reset the id. */
	glGetShaderiv(*id, GL_COMPILE_STATUS, &compiled_status);
	if (compiled_status != GL_TRUE) {
		fprintf(stderr, "Shader compilation status == failed.\n");
		print_shader_log(*id);

		glDeleteShader(*id);
		*id = 0;;
	}
}

int main(void)
{
	const int window_height = 480;
	const int window_width = 640;

	GLfloat bg_color = 0.0f;
	GLuint vert_shader_id;
	GLuint frag_shader_id;
	GLuint shader_program_id;
	GLint link_status = 0;
	int running = 1;
 
  	/* Initialize GLFW */
	atexit(cleanup_on_exit);
  	if (! glfwInit()) {
      		exit(EXIT_FAILURE);
  	}

  	/* Open an OpenGL window */
  	if (! glfwOpenWindow(window_width, window_height, 0, 0, 0, 0, 0, 0, GLFW_WINDOW)) {
  		exit(EXIT_FAILURE);
	}

	/* Create a viewport */
	glViewport(0, 0, window_width, window_height);

	/* Load the shaders */
	load_shader(GL_VERTEX_SHADER, vertex_shader, &vert_shader_id);
	load_shader(GL_FRAGMENT_SHADER, fragment_shader, &frag_shader_id);
	fprintf(stdout, "Vert shader id: %d ; Frag shader id: %d\n", vert_shader_id, frag_shader_id);

	/* Compile them into a shader program */
	shader_program_id = glCreateProgram();
	glAttachShader(shader_program_id, vert_shader_id);
	glAttachShader(shader_program_id, frag_shader_id);
	glLinkProgram(shader_program_id);
	glGetProgramiv(shader_program_id, GL_LINK_STATUS, &link_status);
	if (! link_status) {
		fprintf(stderr, "Unable to link shader program.\n");
		print_shader_log(shader_program_id);
 		exit(EXIT_FAILURE);
	}
	fprintf(stdout, "Shader successfully linked.\n");

	/* Shaders can now be safely deleted */
	glDeleteShader(vert_shader_id);
	glDeleteShader(frag_shader_id);

	/* Setup the triangle data */
	GLuint triangle_vertex_vbo;
	GLfloat triangle_verts[9] = { 
		-0.5f, 0.0f, 0.0f,
		0.5f, 0.0f, 0.0f,
		0.0f, 0.5f, 0.0f 
	};

	glEnableClientState(GL_VERTEX_ARRAY);
	glGenBuffers(1, &triangle_vertex_vbo);
	glBindBuffer(GL_ARRAY_BUFFER, triangle_vertex_vbo);
	glBufferData(GL_ARRAY_BUFFER, 9 * sizeof(GLfloat), triangle_verts, GL_STATIC_DRAW);
	glVertexPointer(3, GL_FLOAT, 0, 0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);


	/* Loop over and over while running */
	while (running) {
		/* OpenGL rendering goes here */
		glClearColor(bg_color, bg_color, bg_color, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		glUseProgram(shader_program_id);

		glBindBuffer(GL_ARRAY_BUFFER, triangle_vertex_vbo);
		glDrawArrays(GL_TRIANGLES, 0, 3);
		glBindBuffer(GL_ARRAY_BUFFER, 0);
		
		/* Swap out the front and back rendering buffers */
		glfwSwapBuffers();

		/* Check to see if we should quit */
		running = !glfwGetKey(GLFW_KEY_ESC) && glfwGetWindowParam(GLFW_OPENED);
	}

	/* Cleanup the gl data */
	glDeleteProgram(shader_program_id);
	glDeleteBuffers(1, &triangle_vertex_vbo);

	return 0;
}
  • adds some miscellaneous clean-up to delete buffers, shaders and the shader program
  • buffers an array of floats into a newly created VBO
  • assigns this data in the VBO to gl_Vertex in the vertex shader
  • the draw loop binds the VBO and draws the vertexes as a triangle

Using gl_Vertex is not the cleanest way of supplying the position of the vertex to the vertex shader in OpenGL 2.0+. Listing 4 replaces the use of gl_Vertex with a custom attribute:

/* Listing 4: Using a custom shader attribute */

#define GL_GLEXT_PROTOTYPES
#include <GL/glfw.h>
#include <GL/gl.h>
#include <GL/glext.h>
#include <stdlib.h>
#include <stdio.h>

/* Include the shaders as hard-coded strings */
const char* vertex_shader = 
"#version 120 \n"
"attribute vec4 in_vert_pos; \n"
"void main() \n"
"{ \n"
"	gl_Position = in_vert_pos; \n"
"} \n";

const char* fragment_shader = 
"#version 120 \n"
"void main (void) \n"
"{ \n"
"	gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); \n"
"} \n";



void cleanup_on_exit(void) 
{
	glfwTerminate();
}	

void print_shader_log(GLuint id)
{
	GLint log_length = 0;

	glGetShaderiv(id, GL_INFO_LOG_LENGTH, &log_length);
	if (log_length) {
		char* log = (char*) malloc(log_length);
		glGetShaderInfoLog(id, log_length, &log_length, log);
		fprintf(stderr, "Shader error: %s\n", log);
		free(log);
	}
}

void load_shader(GLenum t, const char* code, GLuint* id)
{
	GLint compiled_status = 0;

	/* create the shader in OpenGL */
	*id = glCreateShader(t);
	glShaderSource(*id, 1, &code, NULL);
	glCompileShader(*id);

	/* make sure the shader is compiled. if it's not compiled,
	   delete the shader in OpenGL and reset the id. */
	glGetShaderiv(*id, GL_COMPILE_STATUS, &compiled_status);
	if (compiled_status != GL_TRUE) {
		fprintf(stderr, "Shader compilation status == failed.\n");
		print_shader_log(*id);

		glDeleteShader(*id);
		*id = 0;;
	}
}

int main(void)
{
	const int window_height = 480;
	const int window_width = 640;
	const int in_vert_pos = 1;

	GLfloat bg_color = 0.0f;
	GLuint vert_shader_id;
	GLuint frag_shader_id;
	GLuint shader_program_id;
	GLint link_status = 0;
	int running = 1;
 
  	/* Initialize GLFW */
	atexit(cleanup_on_exit);
  	if (! glfwInit()) {
      		exit(EXIT_FAILURE);
  	}

  	/* Open an OpenGL window */
  	if (! glfwOpenWindow(window_width, window_height, 0, 0, 0, 0, 0, 0, GLFW_WINDOW)) {
  		exit(EXIT_FAILURE);
	}

	/* Create a viewport */
	glViewport(0, 0, window_width, window_height);

	/* Load the shaders */
	load_shader(GL_VERTEX_SHADER, vertex_shader, &vert_shader_id);
	load_shader(GL_FRAGMENT_SHADER, fragment_shader, &frag_shader_id);
	fprintf(stdout, "Vert shader id: %d ; Frag shader id: %d\n", vert_shader_id, frag_shader_id);

	/* Compile them into a shader program */
	shader_program_id = glCreateProgram();
	glAttachShader(shader_program_id, vert_shader_id);
	glAttachShader(shader_program_id, frag_shader_id);
	glBindAttribLocation(shader_program_id, in_vert_pos, "in_vert_pos");
	glLinkProgram(shader_program_id);
	glGetProgramiv(shader_program_id, GL_LINK_STATUS, &link_status);
	if (! link_status) {
		fprintf(stderr, "Unable to link shader program.\n");
		print_shader_log(shader_program_id);
 		exit(EXIT_FAILURE);
	}
	fprintf(stdout, "Shader successfully linked.\n");

	/* Shaders can now be safely deleted */
	glDeleteShader(vert_shader_id);
	glDeleteShader(frag_shader_id);

	/* Setup the triangle data */
	GLuint triangle_vertex_vbo;
	GLfloat triangle_verts[9] = { 
		-0.5f, 0.0f, 0.0f,
		 0.5f, 0.0f, 0.0f,
		 0.0f, 0.5f, 0.0f 
	};

	glEnableClientState(GL_VERTEX_ARRAY);
	glGenBuffers(1, &triangle_vertex_vbo);
	glBindBuffer(GL_ARRAY_BUFFER, triangle_vertex_vbo);
	glBufferData(GL_ARRAY_BUFFER, 9 * sizeof(GLfloat), triangle_verts, GL_STATIC_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER, 0);


	/* Loop over and over while running */
	while (running) {
		/* OpenGL rendering goes here */
		glClearColor(bg_color, bg_color, bg_color, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		glUseProgram(shader_program_id);

		glBindBuffer(GL_ARRAY_BUFFER, triangle_vertex_vbo);
		glEnableVertexAttribArray(in_vert_pos);
		glVertexAttribPointer(in_vert_pos, 3, GL_FLOAT, GL_FALSE, 0, 0);
		glDrawArrays(GL_TRIANGLES, 0, 3);
		glBindBuffer(GL_ARRAY_BUFFER, 0);
		
		/* Swap out the front and back rendering buffers */
		glfwSwapBuffers();

		/* Check to see if we should quit */
		running = !glfwGetKey(GLFW_KEY_ESC) && glfwGetWindowParam(GLFW_OPENED);
	}

	/* Cleanup the gl data */
	glDeleteProgram(shader_program_id);
	glDeleteBuffers(1, &triangle_vertex_vbo);

	return 0;
}
  • added in_vert_pos to the vertex shader
  • just before linking the program, bind this attribute to an index location
  • in the render loop, enable the attribute and point it to the data in the VBO created earlier

There you have it! A modern triangle in OpenGL.