3. GLSL Colouring

GLSL Colouring with gl_Color

In this tutorial, we will learn how to use gl_Color in GLSL shaders to replicate OpenGL’s fixed-function pipeline behaviour. This approach allows you to pass colours from your application (using functions like glColor3f and glColor4f) into your shaders, enabling custom effects while maintaining compatibility with older OpenGL conventions.

Using gl_Color here gives us a nice stepping stone from the fixed-function tutorials into shader-based rendering while keeping the familiar glColor* calls for the moment.

Why Use gl_Color?

In the fixed-function pipeline, colours were set directly in OpenGL using functions like glColor3f or glColor4f. With shaders, this functionality must be replicated manually. GLSL provides the built-in variable gl_Color for this purpose:

  • Vertex Shader: gl_Color contains the current legacy vertex colour supplied by calls such as glColor4f.
  • Fragment Shader: After the vertex shader writes gl_FrontColor, the rasteriser interpolates that value and the compatibility fragment shader receives it as gl_Color.

This tutorial demonstrates how to set up a shader program that leverages gl_Color to control the colour of rendered objects dynamically.

Main Program

Here is the main.cpp file that initialises the OpenGL context, loads the shaders, and renders a rotating cube. The key line to note is glColor4f, which sets the cube’s colour.

#include "shader.h"

// Shader instance
Shader shader;
GLfloat angle = 0.0; // Rotation angle

void init(void) {
    glEnable(GL_DEPTH_TEST);  // Enable depth testing
    glDepthFunc(GL_LESS);     // Specify depth function

    shader.init("shader.vert", "shader.frag"); // Load shaders
}

void cube(void) {
    // Rotate the cube on all axes
    glRotatef(angle, 1.0, 0.0, 0.0);
    glRotatef(angle, 0.0, 1.0, 0.0);
    glRotatef(angle, 0.0, 0.0, 1.0);

    // Set the colour of the cube to red
    glColor4f(1.0, 0.0, 0.0, 1.0);

    // Render a solid cube
    glutSolidCube(2);
}

void display(void) {
    glClearColor(0.0, 0.0, 0.0, 1.0);               // Set background to black
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear buffers
    glLoadIdentity();                               // Reset transformation
    gluLookAt(0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); // Set camera

    shader.bind();  // Activate shader
    cube();         // Draw the cube
    shader.unbind(); // Deactivate shader

    glutSwapBuffers(); // Swap buffers for smooth rendering
    angle += 0.1f;     // Increment rotation angle
}

void reshape(int w, int h) {
    if (h == 0) h = 1;
    glViewport(0, 0, (GLsizei)w, (GLsizei)h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60, (GLfloat)w / (GLfloat)h, 1.0, 100.0);
    glMatrixMode(GL_MODELVIEW);
}

int main(int argc, char **argv) {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
    glutInitWindowSize(500, 500);
    glutCreateWindow("GLSL Coloring Example");

    glewInit();    // Initialise GLEW
    init();        // Initialise OpenGL state

    glutDisplayFunc(display); // Set display callback
    glutIdleFunc(display);    // Set idle callback
    glutReshapeFunc(reshape); // Set reshape callback

    glutMainLoop(); // Enter the main loop
    return 0;
}

Vertex Shader

The vertex shader passes the colour from the application to the fragment shader using the built-in variable gl_FrontColor:

void main() {
    // Pass the colour to the fragment shader
    gl_FrontColor = gl_Color;

    // Transform the vertex position
    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}

Explanation:

  • gl_FrontColor: The legacy vertex-shader output for the front-face primary colour.
  • gl_Color: In the vertex shader, receives the current colour set by glColor3f/glColor4f.
  • gl_ModelViewProjectionMatrix: The legacy combined model-view and projection transform.

Fragment Shader

The fragment shader uses the interpolated colour from the vertex shader to set the pixel colour:

void main() {
    // Set the pixel colour
    gl_FragColor = gl_Color;
}

Explanation:

  • gl_FragColor: Sets the fragment shader output colour in this legacy GLSL version.
  • gl_Color: In the compatibility fragment shader, this is the interpolated primary colour produced by the vertex stage.

Why Do We Use gl_FrontColor And gl_FragColor?

gl_FrontColor is an output from the vertex shader. OpenGL interpolates it across the primitive and exposes the result to the compatibility fragment shader as gl_Color. The fragment shader then writes its final result to gl_FragColor.

That may sound like we are passing the colour through a few hoops just to end up with the same red cube, and we are! But it shows the exact path the data takes, which becomes much more useful once we start modifying the colour in the shaders.

What If The Shader Fails?

The old version of this tutorial implied that a failed shader would automatically fall back to the fixed-function pipeline. OpenGL does not do that for us. We should check compile/link status (as in the previous tutorial) and then decide in our own application whether to use another program, deliberately switch back to a compatibility fixed-function path, or stop rendering that object.

Source & downloads

Questions or corrections? If something doesn’t work or you spot something I’ve got wrong, leave a comment below. These tutorials have been around a long time, so useful corrections are always welcome!

  • March 25, 2010
  • 9