28. OpenGL Basic Reflection (Version 2.0)

Introduction

Reflections are a visually striking effect that can greatly enhance the realism of your OpenGL scenes. In this tutorial, we’ll explore how to create basic reflections using the stencil buffer. This approach allows us to restrict reflections to a specific plane, such as a water surface or a polished floor.

The stencil buffer ensures that reflections are rendered only on the defined plane, while the rest of the scene remains unaffected. This method is particularly effective for static reflections, and it builds upon concepts introduced in the shadows tutorial.

What is the Stencil Buffer?

The stencil buffer acts as a mask, defining which parts of the scene should be affected by certain rendering operations. For reflections, the stencil buffer:

  • Defines the plane where reflections are drawn.
  • Clips any part of the reflection outside the specified plane.
  • Ensures reflections do not overwrite other parts of the scene.

Setting Up the Stencil Buffer

To use the stencil buffer, we modify the display mode when initialising GLUT:

glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_STENCIL); // Add stencil buffer

We also clear the stencil buffer alongside the colour and depth buffers:

glClearStencil(0); // Reset stencil buffer
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

Defining the Reflection Plane

The reflection plane is defined using the stencil buffer. First, disable colour and depth masking:

glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); // Disable color updates
glDepthMask(GL_FALSE);                              // Disable depth updates
glEnable(GL_STENCIL_TEST);                          // Enable stencil testing

Next, set the stencil buffer to replace data on the plane:

glStencilFunc(GL_ALWAYS, 1, 0xFFFFFFFF); // Stencil test always passes
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE); // Replace stencil data

We then draw the plane using the `bench()` function:

bench(); // Define the reflection plane

Finally, re-enable colour and depth masking:

glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // Enable color updates
glDepthMask(GL_TRUE);                           // Enable depth updates
glStencilFunc(GL_EQUAL, 1, 0xFFFFFFFF);         // Pass only where stencil equals 1
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);         // Keep stencil buffer data

Rendering the Reflection

The reflection is rendered by flipping the object vertically, translating it to the reflection plane, and applying the necessary transformations:

glDisable(GL_DEPTH_TEST); // Disable depth testing for reflection
glPushMatrix();
glScalef(1.0f, -1.0f, 1.0f); // Flip reflection vertically
glTranslatef(0, 2, 0);        // Translate to the reflection plane
glRotatef(angle, 0, 1, 0);    // Apply rotation
square();                     // Draw the reflection
glPopMatrix();
glEnable(GL_DEPTH_TEST);      // Re-enable depth testing
glDisable(GL_STENCIL_TEST);   // Disable stencil testing

Rendering the Scene

To blend the reflection with the surface, we use alpha blending for transparency:

glEnable(GL_BLEND); // Enable blending
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Set blending function
bench(); // Render the reflective surface
glDisable(GL_BLEND); // Disable blending

Finally, render the rest of the scene, including the original object:

glRotatef(angle, 0, 1, 0); // Apply rotation
square();                 // Draw the original object

Tutorial Code

Here’s the complete implementation of the basic reflections tutorial:

#include <GL/gl.h>
#include <GL/glut.h>
#include <cstdio>
#include <cstdlib>

float angle = 0.0f;
GLuint texture[2] = {0, 0};

GLuint loadtextures(const char *filename, int width, int height) {
    FILE *file = std::fopen(filename, "rb");
    if (file == NULL) return 0;

    const size_t byteCount = static_cast<size_t>(width) * height * 3;
    unsigned char *data = static_cast<unsigned char *>(std::malloc(byteCount));
    if (data == NULL) {
        std::fclose(file);
        return 0;
    }

    if (std::fread(data, 1, byteCount, file) != byteCount) {
        std::free(data);
        std::fclose(file);
        return 0;
    }
    std::fclose(file);

    GLuint id = 0;
    glGenTextures(1, &id);
    glBindTexture(GL_TEXTURE_2D, id);
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    gluBuild2DMipmaps(GL_TEXTURE_2D, 3, width, height, GL_RGB, GL_UNSIGNED_BYTE, data);
    std::free(data);
    return id;
}

void square(void) {
    glPushMatrix();
    glBindTexture(GL_TEXTURE_2D, texture[0]);
    glTranslatef(0.0f, 2.5f, 0.0f);
    glScalef(2.0f, 2.0f, 2.0f);
    glBegin(GL_QUADS);
    glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 0.0f);
    glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 0.0f);
    glTexCoord2f(0.0f, 1.0f); glVertex3f(1.0f, 1.0f, 0.0f);
    glTexCoord2f(0.0f, 0.0f); glVertex3f(1.0f, -1.0f, 0.0f);
    glEnd();
    glPopMatrix();
}

void bench(void) {
    glPushMatrix();
    glColor4f(1.0f, 1.0f, 1.0f, 0.7f);
    glBindTexture(GL_TEXTURE_2D, texture[1]);
    glTranslatef(0.0f, -2.5f, 0.0f);
    glScalef(4.0f, 2.0f, 4.0f);
    glBegin(GL_QUADS);
    glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
    glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -0.5f);
    glTexCoord2f(0.0f, 1.0f); glVertex3f(1.0f, 1.0f, -0.5f);
    glTexCoord2f(0.0f, 0.0f); glVertex3f(1.0f, -1.0f, 1.0f);
    glEnd();
    glPopMatrix();
}

void display(void) {
    glClearStencil(0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    glLoadIdentity();
    glTranslatef(0.0f, 0.0f, -10.0f);

    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
    glDepthMask(GL_FALSE);
    glEnable(GL_STENCIL_TEST);
    glStencilFunc(GL_ALWAYS, 1, 0xFFFFFFFF);
    glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
    bench();

    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    glDepthMask(GL_TRUE);
    glStencilFunc(GL_EQUAL, 1, 0xFFFFFFFF);
    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

    glDisable(GL_DEPTH_TEST);
    glPushMatrix();
    glScalef(1.0f, -1.0f, 1.0f);
    glTranslatef(0.0f, 2.0f, 0.0f);
    glRotatef(angle, 0.0f, 1.0f, 0.0f);
    square();
    glPopMatrix();
    glEnable(GL_DEPTH_TEST);
    glDisable(GL_STENCIL_TEST);

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    bench();
    glDisable(GL_BLEND);

    glRotatef(angle, 0.0f, 1.0f, 0.0f);
    square();
    glutSwapBuffers();
    angle += 1.0f;
}

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

void init(void) {
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_TEXTURE_2D);
    texture[0] = loadtextures("object.raw", 256, 256);
    texture[1] = loadtextures("floor.raw", 256, 256);
}

int main(int argc, char **argv) {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
    glutInitWindowSize(500, 500);
    glutCreateWindow("Basic Reflection");
    init();
    glutDisplayFunc(display);
    glutIdleFunc(display);
    glutReshapeFunc(reshape);
    glutMainLoop();
    return 0;
}

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
  • 2