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 might have to move the camera a bit to see 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; 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.
Next we want to blend the "reflected" cube with the floor. Or do we want to blend the floor with the "reflected" cube? In the first case we'll have to worry about the order the sides of the cube are rendered (as we saw in the last tutorial). Let's try it the other way.
Recompile and run the program. You should now see the "reflected" cube on the floor. There are a couple of problems. First, the floor is now semi- transparent -- we can see the white background through it. Second, if you look under the floor you'll still see the other cube. We can solve both of these problems using the stencil buffer. We'll implement the approach we discussed in lecture.
The stencil buffer is another buffer supported by OpenGL and GLUT that is useful for "tagging" pixels in a scene. We need to clear the stencil buffer just like we do the frame and depth buffers. Add the following to init.
// 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);
// Don't update color or depth. glDisable(GL_DEPTH_TEST); glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);The glColorMask command allows us to turn off writing to, respectively, the red, green, blue, and alpha channels of the frame buffer. By default, these are all set to GL_TRUE.
Next we need to enable stencil testing:
// enable stencil buffering glEnable(GL_STENCIL_TEST);There are two functions that control the behavior with respect to the stencil buffer. Add the following:
// define stencil test glStencilFunc(GL_GREATER , 1, 3); // define stencil operations glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);We'll come back to these functions and their effects in a moment.
As discussed in lecture, we need to distinguish pixels that correspond to the top of the floor. Because we have to be able to handle various views -- from above and below -- we need to tell OpenGL to only draw the top of the floor into the stencil buffer. We can do this with back-face culling.
// cull back faces glEnable(GL_CULL_FACE); glCullFace(GL_BACK);
With back-face culling enabled, OpenGL ignores any polygons that are facing away from the camera. Recall that the front face is determined by the order in which the vertices are defined; in our case the front faces of the floor polygons are facing up.
Next we have to draw the floor.
// draw the floor drawFloor()OpenGL processes each polygon of the floor in turn. For each, it first determines whether it is front or back-facing. If back-facing (i.e. the view is from the bottom), OpenGL ignores the polygons. If, on the other hand, the top of the floor is visible (i.e. the front faces), OpenGL projects the polygon vertices into screen space then uses its scan line algorithm to determine the pixels that are "in" the polygon. For each "in" pixel, OpenGL performs various tests and, depending on their outcome and the OpenGL state, writes to its various buffers.
The pixel tests include the depth test (if enabled) and stencil test (if enabled). In our case only the stencil test is performed. The actual test is specified by glStencilFunc(passFunc,refVal,mask) command. When a pixel is tested, the reference value, is compared to the current value for that pixel in the stencil buffer. (The third argument, mask allows us to look at specific bits in the stencil buffer but we don't need that capability; using a mask of 3 means we'll consider the two low order bits of the stencile buffer, which is all we'll need in this tutorial.) In our case, the stencil buffer is cleared to 0 so our stencil test compares 1 to 0. The passFunc we have specified is GL_GREATER. Thus the stencil test will pass since 1>0.
After performing the pixel tests, OpenGL checks its state to determine whether to write to the frame, depth, and stencil buffers.
In our case, if the top of the floor is visible, then for all pixels "in" the floor, the stencil test will pass. Since the depth test is disabled, it too passes. So the only outcome we really care about is the third one; nevertheless we have to specify operations for all three cases. We use GL_REPLACE. This causes the value in the stencil buffer to be overwritten (replaced) by refVal.
To complete this section of code we need to reset OpenGL's state as follows:
// Re-enable update of color and depth. Disable culling. glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); glEnable(GL_DEPTH_TEST); glDisable(GL_STENCIL_TEST); glDisable(GL_CULL_FACE);The final step is to only draw the "reflected" cube for pixels that are tagged (i.e. stencil value is 1). Add the following just before drawing the "reflected" cube.
// draw reflected cube but only where top of floor glEnable(GL_STENCIL_TEST); glStencilFunc(GL_EQUAL, 1, 3); glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);Now our stencil test compares the reference value of 1 to the value in the stencil buffer. For tagged locations, this test will pass, for all other locations it will fail. Thus for a pixel of the "reflected" cube that passes the depth test (is not occluded by the other cube), the frame buffer is updated with its color and the depth buffer is updated with its depth. Becase glStencilOp specifies GL_KEEP, the values of the stencil buffer are kept; i.e. not overwritten.
Finally we need to disable stencil tests after drawing the "reflected" cube. Add the following:
glDisable(GL_STENCIL_TEST);Recompile the program and run it. The "reflected" cube under the floor should no longer be visible.