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 initializing GLUT:

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

We also clear the stencil buffer alongside the color 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 color 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 color 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>

float angle = 0; // Rotation angle
GLuint texture[2]; // Texture IDs

// Function to load textures
GLuint loadtextures(const char *filename, float width, float height) {
    GLuint texture;
    unsigned char *data;
    FILE *file = fopen(filename, "rb");
    if (file == NULL) return 0;
    data = (unsigned char *)malloc(width * height * 3);
    fread(data, width * height * 3, 1, file);
    fclose(file);

    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    gluBuild2DMipmaps(GL_TEXTURE_2D, 3, width, height, GL_RGB, GL_UNSIGNED_BYTE, data);

    free(data);
    return texture;
}

// Function to draw a square
void square(void) {
    glPushMatrix();
    glBindTexture(GL_TEXTURE_2D, texture[0]);
    glTranslatef(0, 2.5, 0);
    glScalef(2, 2, 2);
    glBegin(GL_QUADS);
    glTexCoord2f(1, 0); glVertex3f(-1, -1, 0);
    glTexCoord2f(1, 1); glVertex3f(-1, 1, 0);
    glTexCoord2f(0, 1); glVertex3f(1, 1, 0);
    glTexCoord2f(0, 0); glVertex3f(1, -1, 0);
    glEnd();
    glPopMatrix();
}

// Function to draw the bench
void bench(void) {
    glPushMatrix();
    glColor4f(1, 1, 1, 0.7); // Semi-transparent surface
    glBindTexture(GL_TEXTURE_2D, texture[1]);
    glTranslatef(0, -2.5, 0);
    glScalef(4, 2, 4);
    glBegin(GL_QUADS);
    glTexCoord2f(1, 0); glVertex3f(-1, -1, 1);
    glTexCoord2f(1, 1); glVertex3f(-1, 1, -0.5);
    glTexCoord2f(0, 1); glVertex3f(1, 1, -0.5);
    glTexCoord2f(0, 0); glVertex3f(1, -1, 1);
    glEnd();
    glPopMatrix();
}

// Display callback
void display(void) {
    glClearStencil(0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    glLoadIdentity();
    glTranslatef(0, 0, -10);

    // Stencil buffer setup
    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);

    // Render reflection
    glDisable(GL_DEPTH_TEST);
    glPushMatrix();
    glScalef(1.0f, -1.0f, 1.0f);
    glTranslatef(0, 2, 0);
    glRotatef(angle, 0, 1, 0);
    square();
    glPopMatrix();
    glEnable(GL_DEPTH_TEST);
    glDisable(GL_STENCIL_TEST);

    // Render scene
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    bench();
    glDisable(GL_BLEND);

    glRotatef(angle, 0, 1, 0);
    square();

    glutSwapBuffers();
    angle++;
}

// Initialization function
void init(void) {
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_TEXTURE_2D);
    text
  • March 25, 2010
  • 2