Date: Mon, 21 Jul 1997 21:50:52 -0800
To: Multiple recipients of list OPENGL-GAMEDEV-L <OPENGL-GAMEDEV-L@fatcity.com>
X-Comment: OpenGL Game Developers Mailing List
X-Sender: mjk@fangio.asd.sgi.com (Mark Kilgard)
From: mjk (Mark Kilgard)
Subject: OpenGL-rendered patchy planar reflections (reflecting puddles)

opengl-gamedev,

First-person adventure games often involve wandering around dungeons. Every dungeon I've ever seen has some kind of plumbing problem and the result is puddles of water on the floor in places.

Puddles of water on the floor can create a unique reflective surface. Puddles only create reflections in the puddled regions of the floor.

Reflections in puddles of water can add an extra eerie realism to first-person advantures. It is straightforward to render patchy planar reflections with OpenGL.

I haven't yet written up a demo of patchy planar reflections in OpenGL so I regret I don't have nice pictures and sample code. SIGGRAPH is coming up and that tends to keep me busy. I'll go ahead and describe the technique anyway. Angus Dorbie (developer of this technique) has nicely supplied a snapshot of patchy planar reflections from a demo he wrote:

Notice how the lights on the marble are reflected in a patchy way. Unfortunately, a static image really fails to capture the cool dynamics of patchy planar reflections.

First, make sure that you understand how to do normal planar reflections with OpenGL. If you need to, review:

  http://reality.sgi.com/mjk_asd/tips/Reflect.html
  http://reality.sgi.com/mjk_asd/tips/TimHall_Reflections.txt
  http://reality.sgi.com/mjk_asd/tips/TexShadowReflectLight.html 

Now, for a patchy reflection, you use the same basic technique with a few more additional tricks. First create an "alpha" texture map (GL_ALPHA) that will be textured onto the floor to indicate where water puddles are located. The texture should have an alpha of 1.0 in the non-puddled (non-reflective) region of the floor and 0.0 where the puddle fully reflects light (in the water). Actually, you might have your texture not go fully to 0.0 so that the water is only partially reflective. The edges of the puddles should vary smoothly between 1.0 and your fully watery reflection alpha value so the puddle boundaries are smooth.

Now, you render you render the reflactive polygonal planar surface to the stencil buffer. In the examples above, we disabled color buffer update with glColorMask(0,0,0,0), but for patchy reflections you want to write the alpha values from the puddle texture into the frame buffer. Do this:

  glDisable(GL_BLEND);
  glEnable(GL_STENCIL_TEST);
  glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
  glStencilFunc(GL_ALWAYS, 1, 0xffffffff);
  glDisable(GL_DEPTH_TEST);
  glColorMask(0, 0, 0, 1); /* Just update destination alpha. */
  glEnable(GL_TEXTURE_2D);
  drawFloorWithPuddleTexture(); 

OpenGL 1.1 supports a GL_ALPHA texture format that is perfect for our puddle texture. Use GL_ALPHA when creating the puddle texture (GL_LUMINANCE_ALPHA or GL_INTENSITY can also be used if GL_ALPHA isn't accelerated well by your hardware).

Note that the above _assumes_ that your frame buffer has "destination alpha" (also known as an alpha buffer). Most PC hardware (today!) doesn't support an alpha buffer though good graphics workstations generally do. The technique works better with destination alpha (for reasons described later), but you can adapt the technique to work on hardware without destination alpha very easily (also described later).

Now, render the objects you want reflected as reflected through the floor plane. Remember to do the following first:

  glColorMask(1,1,1,0); /* Do NOT update alpha now. */
  glEnable(GL_DEPTH_TEST);
  glStencilFunc(GL_EQUAL, 1, 0xffffffff); /* Draw if stencil==1 */
  glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
  drawReflectedObjects(); 

Now, you'll want to want to render the non-reflecting and partially-reflecting surface of the floor into the scene. The tricky part here is to use the destination alpha values on the floor pixels to decide how much of the opaque floor texture (say a repeating RGB brick pattern texture) to show versus the reflection. Where there is a puddle (dest alpha < 1.0), the reflection should show, but blended with thek brick texture; where there is no puddle, you should just see the brick texture clearly (no reflection). This is easy to accomplish with glBlendFunc; just do:

  glDisable(GL_STENCIL_TEST);
  glEnable(GL_TEXTURE_2D);
  glEnable(GL_BLEND);
  glBlendFunc(GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA);
  drawFloorWithBrickTexture(); 

That's all there is to it.

There are a few subtle points to make. When drawing the alpha, you can decide to either use GL_REPLACE or GL_MODULATE. GL_REPLACE can be cheaper, but GL_MODULATE lets you modulate the reflecting puddles by the degree that light is shining on them. By controling the alpha component for your lighting, you can introduce specular and/or diffuse effects into the reflective nature of the surfaces.

Now, what if you don't have destination alpha? Well, you can use an RGBA that combines the RGB brick and Alpha puddle texture as a single texture. Instead of the glBlendFunc above, you'd just use the more traditional:

  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 

There is an advantage to using destination alpha which is that your puddle texture and your brick texture can be applied at different resolutions. For example, the brick texture (like many floor textures) could be a high-frequency texture of a repeated pattern, while you'd probably not like the puddles to look so tightly repeated. Since the puddles probably aren't going to have high-frequency changes, the puddle textures can be low-res (kind of like lightmaps). This is a good example of multi-resolutional texturing approach.

Performance-wise, the extra alpha texturing during the stenciling step will often be "for free" on good hardware (the kind of hardware that probably also supports destination alpha).

By the way, if you lack destination alpha, you could still get the multi-resolutional advantage of the separate puddle and brick textures with a final added "puddle pass" but that certainly costs performance.

Also, if you were really clever, you could construct a time sequence of alpha puddle textures so that the puddles might actually indicate motion from dripping water from the ceiling or a creature walking through the puddles.

As the briefest aside, I'll note that other 3D APIs (notably Direct3D) lack the functionality to use the more sophisticated two-texture (puddle and floor) approach (not to mention the general lack of stencil needed for good constrained planar reflections). In the case of Direct3D (even with DirectX 5.0), there is no destination alpha or alpha component-only texture format (or stenciling).

Imagine how cool a game would be where you hear footsteps in shallow water and turn around to find a nasty demon right behind you including his ugly reflection in the rippling puddles on the hallway floor.

All the credit for this wonderfully convincing technique should go to Angus Dorbie.

- Mark (mjk@nvidia.com)