CS155 Computer Graphics
Reflection Lab
OpenGL does not have built-in support for reflections but there is technique to produce them.
Download the zip file reflections.zip, unzip, open the visual studio solution, compile and run the program. You'll see a red floor, a blue cube, and a white background. (You can move the camera around to see under the floor.)
We can create a "reflected" cube by drawing a version of our original cube that is scaled by (1,-1,1). Add the appropriate code to display after the floor-drawing code; be sure to push/pop the modelview matrix before the scale/after drawing the reflected cube. The results aren't impressive so far, but you should see a second cube on the other side of the floor.
In order to see the reflected cube in the floor we need to use blending. OpenGL supports blending when rendering an object; rather than simply overwriting a pixel in the frame buffer you can combince the new value with the current value.
To blend the cube with the floor do the following:
glEnable(GL_BLEND);After drawing the reflected cube we need to disable blending using the following code.
glDisable(GL_BLEND);
cubeDiffuse[3]=0.5; cubeAmbient[3]=0.5; glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, cubeDiffuse); glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, cubeAmbient);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);When a pixel for the reflected cube is drawn to the frame buffer its value is averaged with what is already there.
There is, however, an easy fix; we can change the drawing order. After we draw the original cube, we can draw the reflected cube with depth buffering and no blending. This will elimate the problems arising from having depth buffering off. After both the original and reflected cubes are drawn, we can draw the floor with depth buffering off and blending on. We only want to blend with the reflected cube but this can be handled by using an appropriate blending function. Refactor your code accordingly but use the following blending function:
glBlendFund(GL_ONE_MINUS_DEST_ALPHA,GL_DEST_ALPHA);Note that our clear color has an alpha of 0 so the floor is not blended with the background! And the original cube has an alpha of 1 so the floor is not blended with any of those pixels.
Recompile and run the program. You should now see the "reflected" cube on the floor.
The stencil buffer is another buffer supported by OpenGL and GLUT that is useful for "tagging" pixels in a scene. When drawing a pixel of the original or reflected cube we need to know whether the pixel location corresponds to background, top of the floor, or bottom of the floor.
// initialize stencil clear value glClearStencil(0.0);In display, add the stencil buffer to the clear command.
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
// draw top of floor to stencil buffer // cull back faces glEnable(GL_CULL_FACE); glCullFace(GL_BACK);
// Don't change frame buffer or depth depth. glDisable(GL_DEPTH_TEST); glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
// enable stencil test glEnable(GL_STENCIL_TEST);
// define stencil test glStencilFunc(GL_GREATER, 1, 3);The first argument is the test to apply and the second argument is the constant. In this case the stencil test will pass if the constant, 1, is greater than the value in the stencil buffer. Since we initalize the stencil buffer to 0, this test will always pass. The third argument is a mask; we can use different bits of the stencil buffer in different ways; for now we only need the two low order bit so a mask of 011 = 3 is perfect.
// define stencil operations glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);This tells OpenGL that in cases (a) and (b), the stencil buffer should keep its current value. In case (c), it should be replaced by the constant defined in the glStencilFun call; in our case a 1. The only case that can arise in this example is the final one because depth testing is disabled and the stencil test is designed to always pass while drawing top of floor pixels. The first two parameters of glStencilOp could be changed without effect.
drawFloor();As the floor is being rendered, the pipeline discovers pixels that would be "turned on" for the top floor. For each such pixel the stencil test is performed. Since the stencil buffer is initialized to 0 and our stencil test specifies a constant of 1, the stencil test passes. Since the depth test is disabled we are in case (c) above. The stencil operation says to replace the value in the stencil buffer for that pixel with a 1. Since the depth test and frame buffer are turned off, they are not changed. Therefore, the only thing that happens during this call to drawFloor is that the pixels corresponding to the top of the floor in the stencil buffer are set to 1. Note, if our current view is from below, no changes are made to the stencil buffer.
glCullFace(GL_FRONT); glStencilFunc(GL_GREATER, 2, 3); drawFloor();
glEnable(GL_DEPTH_TEST); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);As we draw the cube, we only want to draw pixels where the corresponding value in the stencil buffer is 0 or 1. The current setting for the glStencilFunc is exactly what we need so we don't have to reset it. We don't, however, want to change the stencil buffer so add:
glStencilOP(GL_KEEP,GL_KEEP,GL_KEEP);
glDisable(GL_STENCIL_TEST);