Tutorial 23 :
Reflection / Stencil Buffer

Tutorial Advanced Download Section

Until now, we have used the color buffer and depth buffer.
In this tutorial, we will use another buffer : the Stencil Buffer.

Please read Lesson 05 for advanced informations about buffers.
 

The reflection effect is a two pass effect.

In the first pass, you draw in the stencil buffer the area in which the reflection should be visible. This will create a mask for drawing the reflection.
In the second pass, you draw the reflection in the color buffer only where the mask is. The reflection is the symetry of the object (to reflect) in reference to the reflection area (area that was used in first pas for creating the mask). Then, draw the reflection area by blending it with the reflection. Finally, draw the object reflected.


Reflection

The number of bit planes for stencil buffer is specified using a GLCapabilities object, before the creation of the GLCanvas :

Set stencil bit

    GLCapabilities glCapabilities = new GLCapabilities();
    glCapabilities.setStencilBits(1);
    GLDrawableFactory.getFactory().createGLCanvas(glCapabilities)

In the initialization, we specify the clearing value of the stencil buffer. When this buffer will be cleared, it will be filled by 0 :
    gl.glClearStencil(0)
 

To create the mask, we need to draw in the stencil buffer the reflection area (here it is the ground).

To draw only in the stencil buffer, we disable writting in the color buffer (off screen) and in depth buffer (draw all pixels).
Then, we activate and set up the stencil test. The stencil function to always pass, value written will be 1(second parameter of glStencilTest). The operation done, if stencil & depth test passes, is writting in stencil buffer.
And we draw the reflected area (the ground).

This means, for each pixels of the ground, stencil test will mark '1' in stencil buffer if depth test passes (third parameter of glStencilOp). We will have a buffer in which where the ground is value is 1, elsewhere it is 0.

The stencil buffer for the above picture will look like (white='1', black='0'):


Stencil buffer

Finally, we activate writting in color and depth buffer for the following.
 

First pass - Create the mask

    //Disable color and depth buffers
    gl.glColorMask(false, false, false, false);             //Disable writting in color buffer
    gl.glDepthMask(false);                                  //Disable writting in depth buffer

    gl.glEnable(GL.GL_STENCIL_TEST);                        //Enable Stencil test
    gl.glStencilFunc(GL.GL_ALWAYS, 1, 1);                   //Test always success, value written 1
    gl.glStencilOp(GL.GL_KEEP, GL.GL_KEEP, GL.GL_REPLACE);  //Stencil & Depth test passes => replace existing value

    //Draw ground
    ...

    gl.glDepthMask(true);
    gl.glColorMask(true, true, true, true); //Enable drawing of r, g, b & a

 

Here we draw the reflection in the mask area, defined in the stencil buffer.

We start with stencil test. The stencil function tests if value in the stencil buffer is 1, to draw only where stencil buffer contains a '1'. When the test succeed, we do nothing (keep current value).

Then, we draw the object to be reflected symmetrically to the drawing area. For this, we mirror y and draw the object.

Finally, we disable stencil test.
 

Draw reflection

    gl.glStencilFunc(GL.GL_EQUAL, 1, 1);                   //Draw only where stencil buffer is 1
    gl.glStencilOp(GL.GL_KEEP, GL.GL_KEEP, GL.GL_KEEP);    //Stencil buffer read only

    gl.glPushMatrix();
     gl.glScalef(1.0f, -1.0f, 1.0f);                        //Mirror Y
     gl.glEnable(GL.GL_LIGHTING);
     gl.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, lightPos);
      drawSpring(gl, glu);
     gl.glDisable(GL.GL_LIGHTING);
    gl.glPopMatrix();

    gl.glDisable(GL.GL_STENCIL_TEST);                        //Disable Stencil test

 

Now, we draw the reflection area in color buffer.
We uses blending to mix the reflection with the ground (keep only 20% of the reflection).
 

Draw reflection area

    gl.glEnable(GL.GL_TEXTURE_2D);
    gl.glBindTexture(GL.GL_TEXTURE_2D, texture);
    gl.glEnable(GL.GL_BLEND);
    gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE_MINUS_SRC_ALPHA);
    gl.glColor4f(1.0f, 1.0f, 1.0f, 0.8f);
    Drawer.drawGroundCenter(gl, 20, 20, 10);
    gl.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
    gl.glDisable(GL.GL_BLEND);
    gl.glDisable(GL.GL_TEXTURE_2D);

 

To finish, we draw the reflected object.
 

Draw object reflected

    gl.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, lightPos);
    gl.glEnable(GL.GL_LIGHTING);
    drawSpring(gl, glu);
    gl.glDisable(GL.GL_LIGHTING);

 

T/A/C : True/Average/Computed normals
 True     : true normal for at each vertex, using the mathematical model of the geometry.
 Average  : average normal for each vertex, average calculated using next and previous vertex.
 Computed : normals is the normal calculated for each faces (bad result).

Average mode is equivalent to true mode for medium and high number of vertex, but has low results for low number of vertex.
 

Remember to download the GraphicEngine-1.1.2 to run this tutorial !


If you've got any remarks on this tutorial, please let me know to improve it.
Thanks for your feedback.
 

Previous Tutorial

Back

Next turorial

Copyright © 2004-2012 Jérôme Jouvie - All rights reserved. http://jerome.jouvie.free.fr/