27. OpenGL Basic Shadows (Version 2.0)

Introduction

Shadows are a critical component of 3D rendering, adding depth and realism to a scene. In this tutorial, we’ll explore how to create basic shadows in OpenGL using the stencil buffer. The stencil buffer allows us to define where a shadow is rendered by restricting its placement to a specific plane.

This method works best for simple shadows that remain on a flat surface, such as shadows cast onto a floor or table.

What is the Stencil Buffer?

The stencil buffer is an additional buffer in OpenGL used for masking certain parts of the scene. It allows you to:

  • Select specific areas to render shadows.
  • Clip unwanted parts of the shadow outside the defined plane.
  • Combine shadows with transparency for realistic effects.

By leveraging the stencil buffer, we can define a “drawing plane” where shadows are rendered, while ensuring other parts of the scene remain unaffected.

Setting Up the Stencil Buffer

To use the stencil buffer, we need to modify the window initialization code:

  1. glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_STENCIL);  

This adds a stencil buffer to the window.

Next, we clear the stencil buffer at the beginning of the rendering process:

  1. glClearStencil(0);  
  2. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);  

This ensures the stencil buffer is reset before rendering each frame.

Defining the Shadow Plane

We define the plane where shadows will be rendered using a combination of masking and stencil functions:

  1. glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); // Disable color rendering  
  2. glDepthMask(GL_FALSE);                              // Disable depth updates  
  3. glEnable(GL_STENCIL_TEST);                          // Enable stencil testing  
  4. glStencilFunc(GL_ALWAYS, 1, 0xFFFFFFFF);            // Set stencil test to always pass  
  5. glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);    // Replace stencil buffer data  

The next step is to draw the plane. For this example, we use a custom `bench()` function to define the plane’s geometry:

  1. bench();  

After defining the plane, re-enable color and depth masking:

  1. glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // Enable color rendering  
  2. glDepthMask(GL_TRUE);                           // Enable depth updates  
  3. glStencilFunc(GL_EQUAL, 1, 0xFFFFFFFF);         // Only draw where stencil value equals 1  
  4. glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);         // Keep existing stencil buffer data  

Rendering the Shadow

To render the shadow, we use the following steps:

  1. Disable texturing and depth testing to ensure the shadow appears as a plain black shape.
  2. Flip the shadow vertically to simulate it being cast onto the plane.
  3. Translate and rotate the shadow to match the object’s position.

Here’s the code:

  1. glDisable(GL_TEXTURE_2D);  
  2. glDisable(GL_DEPTH_TEST);  
  3.   
  4. glPushMatrix();  
  5. glScalef(1.0f, -1.0f, 1.0f); // Flip vertically  
  6. glTranslatef(0, 2, 0);       // Move shadow to plane  
  7. glRotatef(angle, 0, 1, 0);   // Rotate shadow  
  8. glColor4f(0, 0, 0, 1);       // Set shadow color to black  
  9. square();                    // Draw shadow shape  
  10. glPopMatrix();  
  11.   
  12. glEnable(GL_DEPTH_TEST);  
  13. glEnable(GL_TEXTURE_2D);  
  14. glDisable(GL_STENCIL_TEST); // Disable stencil testing  

Rendering the Scene

After rendering the shadow, we draw the rest of the scene as usual. To enhance the realism of the shadow, use alpha blending:

  1. glEnable(GL_BLEND);  
  2. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);  
  3. bench(); // Draw the bench with transparency  
  4. glDisable(GL_BLEND);  

Tutorial Code

Here’s the full implementation of the basic shadows tutorial:

  1. #include <GL/gl.h>  
  2. #include <GL/glut.h>  
  3.   
  4. float angle = 0; // Rotation angle  
  5. GLuint texture[2]; // Texture IDs  
  6.   
  7. // Load textures (implement as needed)  
  8. GLuint loadtextures(const char *filename, float width, float height);  
  9.   
  10. // Draw a square  
  11. void square(void) {  
  12.     glPushMatrix();  
  13.     glBegin(GL_QUADS);  
  14.     glVertex3f(-1, -1, 0);  
  15.     glVertex3f(-1, 1, 0);  
  16.     glVertex3f(1, 1, 0);  
  17.     glVertex3f(1, -1, 0);  
  18.     glEnd();  
  19.     glPopMatrix();  
  20. }  
  21.   
  22. // Draw the bench (shadow plane)  
  23. void bench(void) {  
  24.     glPushMatrix();  
  25.     glColor4f(1, 1, 1, 0.7); // Semi-transparent bench  
  26.     glBegin(GL_QUADS);  
  27.     glVertex3f(-2, -0.5, 1);  
  28.     glVertex3f(-2, 0.5, -1);  
  29.     glVertex3f(2, 0.5, -1);  
  30.     glVertex3f(2, -0.5, 1);  
  31.     glEnd();  
  32.     glPopMatrix();  
  33. }  
  34.   
  35. // Display callback  
  36. void display(void) {  
  37.     glClearStencil(0);  
  38.     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);  
  39.     glLoadIdentity();  
  40.     glTranslatef(0, 0, -10);  
  41.   
  42.     // Stencil buffer setup  
  43.     glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);  
  44.     glDepthMask(GL_FALSE);  
  45.     glEnable(GL_STENCIL_TEST);  
  46.     glStencilFunc(GL_ALWAYS, 1, 0xFFFFFFFF);  
  47.     glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);  
  48.     bench(); // Define shadow plane  
  49.     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);  
  50.     glDepthMask(GL_TRUE);  
  51.     glStencilFunc(GL_EQUAL, 1, 0xFFFFFFFF);  
  52.     glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);  
  53.   
  54.     // Draw shadow  
  55.     glDisable(GL_TEXTURE_2D);  
  56.     glDisable(GL_DEPTH_TEST);  
  57.     glPushMatrix();  
  58.     glScalef(1.0f, -1.0f, 1.0f);  
  59.     glTranslatef(0, 2, 0);  
  60.     glRotatef(angle, 0, 1, 0);  
  61.     glColor4f(0, 0, 0, 1);  
  62.     square();  
  63.     glPopMatrix();  
  64.     glEnable(GL_DEPTH_TEST);  
  65.     glEnable(GL_TEXTURE_2D);  
  66.     glDisable(GL_STENCIL_TEST);  
  67.   
  68.     // Draw the scene  
  69.     glEnable(GL_BLEND);  
  70.     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);  
  71.     bench();  
  72.     glDisable(GL_BLEND);  
  73.     glRotatef(angle, 0, 1, 0);  
  74.     square();  
  75.     glutSwapBuffers();  
  76.     angle++;  
  77. }  
  78.   
  79. // Main function  
  80. int main(int argc, char **argv) {  
  81.     glutInit(&argc, argv);  
  82.     glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_STENCIL);  
  83.     glutInitWindowSize(500, 500);  
  84.     glutCreateWindow("Basic Shadows");  
  85.     glutDisplayFunc(display);  
  86.     glutIdleFunc(display);  
  87.     glutMainLoop();  
  88.     return 0;  
  89. }  

If you have any questions or run into issues, feel free to email me at swiftless@gmail.com. Happy coding!

  • March 25, 2010
  • 11