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:
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.
- OPENGL Web site