[BY TIMOTHY BOGDALA]

basic OpenGL 2.0 tutorial

Published: December 20, 2012

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;
}

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;
}

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;
}

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;
}

There you have it! A modern triangle in OpenGL.

<- OLDER ENTRY .|. NEWER ENTRY ->