Upcoming Events
Southwest Gaming Expo
11/20 - 11/22 @ Dallas, TX

Workshop on Network and Systems Support for Games (NetGames 2009)
11/23 - 11/25 @ Paris, France

ICIDS 2009 Interactive Storytelling
12/9 - 12/11 @ Guimarães, Portugal

Global Game Jam
1/29 - 1/31  

More events...


Quick Stats
6312 people currently visiting GDNet.
2341 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!



Link to us

Link to us

  Intel sponsors gamedev.net search:   

Realistic Natural Effect Rendering: Water I


Planar mirrors for local reflections

The largest problem with cube environment mapping is the lack of local reflections. An interesting alternative is the planar mirror technique, also known under the slight misnomer "stencil reflection". Often used as general-purpose reflection algorithm on flat opaque geometry, such as for example shiny marble or glass, it is perfectly able to handle both local and distant reflections. Although the approach will only work on totally planar reflectors, a water surface is usually flat enough to be approximated by a single reflective plane. The results won't be physically accurate, but the visual appearance is very convincing. Planar mirrors are easy to implement, and existing engine functionality can often be reused. The effect is view dependent, and consists of two main passes that have to be executed once per frame.

On the first pass, the environment is geometrically reflected around the water plane by the use of a special reflection matrix. Assume a coordinate system with X and Y parallel to the ground, and positive Z upwards. Since an undisturbed water surface under the influence of gravity will always be parallel to the ground, the plane approximating the surface will always have a constant surface normal of [0, 0, 1]. The reflection operation is therefore reduced to a geometric scale of (-1) on the Z axis (essentially flipping it around), but additional care must be taken if the water plane does not cross the origin. Given the height h of the water surface from the ground, the final reflection matrix Mf is shown below:

 1   0   0   0
 0   1   0   0
 0   0  -1  2h
 0   0   0   1

The environment reflected through Mf is then rendered from the point of view of the original camera into a 2D reflection texture. Note that the common stencil based reflection technique cannot be applied to water reflections, the image needs to be available as a texture. Caveat: the above reflection matrix assumes vertex coordinates to be in world space. Where this is not the case, the local object transformation matrix (Mo) needs to be applied first. The typical concatenated view matrix used to render a reflected environment is as follows, where Mc is the current camera matrix, and Mreflview the final reflected view matrix:

Mreflview = Mc * Mf (* Mo)

Tip:

Since the geometric reflection is essentially a negative scale, the winding order of the primitives rendered with the reflection matrix enabled will be inverted. This can lead to visual anomalies if backface culling and/or two-sided lighting is enabled. The solution is to either flip the backface culling mode from front to back (or vice-versa), or to invert the internal winding order of the 3D API.

In the second pass, the reflection texture is applied to the water mesh using projective texturing. The result is a perfectly calm reflection of the environment onto the water surface. Although features such as waves or turbulences distort the reflection in reality, the current model doesn't take any of this into account yet, as it has no knowledge about the dynamic state of the surface. Fortunately, projective texturing allows the manipulation of the projective coordinates either per vertex or per pixel. This can be used to realistically distort the projected reflection image according to the current water surface state.

A word on projective texturing

Planar mirrors as described above require projective texturing in the second pass. Projective texture mapping [4] is a technique where a 2D texture is projected onto an object, rather than being directly attached to it (as it would be with conventional texture mapping). It operates analogous to a slide projector, where an image, or rather its texture coordinates are projected onto an object. From the concept, it does the exact opposite of a camera: instead of recording an image of the environment by means of an orthographic or perspective projection, projective texture mapping projects an image onto the environment, by using a matrix very similar to the one a camera uses. Both perform the same operations on a vertex, the difference lies in the way the resulting coordinates are processed. The camera model uses the transformed and projected vertex positions to rasterize a primitive onto the screen, while perspective texture mapping uses them to index a texture.

General projective texture mapping allows such a "slide projector" to be positioned virtually anywhere in the scene. But the way it is used by the planar reflection technique is a special and simplified case. In fact, the projector position and orientation is exactly equal to the camera position and orientation. Both share the same view and projection matrices, which make things a lot easier. Imagine the camera being a combined recording and projecting device, that records the (reflected) environment during the first pass to a texture, and projects it back onto the water surface during the second pass (although not reflected).

The math behind projective texture mapping is pretty straightforward, and is very similar to the way vertices are processed by the standard viewing pipeline. First, the vertex position is multiplied by the view and projection matrices, transforming it to clip space. At this point, a remapping operation unique to projective texturing is performed. A clip space coordinate c has a defined range of [-1 < c < +1], in both x and y directions (the z component is handled differently depending on the API, but it can safely be ignored in the context of projective reflections). But since the transformed vertex position will be used as a texture coordinate, it has to be remapped to the 0 to 1 range required to access a texture map. This is simply done by dividing each component of the clip space position by 2, and adding 0.5 to the result. This operation can be conveniently expressed by a 4x4 remapping matrix Mr:

 0.5  0    0    0.5
 0    0.5  0    0.5
 0    0    0.5  0.5
 0    0    0    1  

As mentioned above, in the case of planar mapping, the camera view matrix Mc and the camera projection matrix Mp are equivalent to the matrices used by the projective mapping step. When rendering the water surface in step 2 of the planar reflection algorithm, the projective texture coordinates used to apply the reflection texture are computed by transforming the world space water grid vertex positions by the following combined projective texture matrix Mprojtex:

Mprojtex = Mr * Mp * Mc

The resulting projective texture coordinates show a property that might seem unusual to someone used to conventional 2D [s, t] coordinate pairs: their homogeneous coordinate q is not automatically one, and can therefore not simply be ignored. This is due to the projection encoded in Mp and relates to the homogeneous w coordinate used in the standard viewing pipeline. In the same way x and y are divided by w ("homogeneous divide"), the projective texture coordinates s and t need to be divided by q. This is achieved by a special projective texture access opcode within the pixel shader, which internally uses a [s/q, t/q] pair to access the texture rather than the conventional [s, t] pair. Cg uses the tex2Dproj keyword for this purpose. Source 2 shows a basic vertex and pixel shader combo to perform projective texture mapping in the context of planar reflections.

void VP_projective( float4 inPos : POSITION,
                    out float4 outPos : POSITION,
                    out float4 outTexProj : TEXCOORD0,
                    uniform float4x4 Mvp,
                    uniform float4x4 Mprojtex )
{
   // transform vertex position by combined view projection matrix
   outPos = mul(Mvp, inPos);
   // transform vertex position by projective texture matrix and
   // copy the result into homogeneous texture coordinate set 0
   outTexProj = mul(Mprojtex, inPos);
}

void FP_projective( float4 inTexProj : TEXCOORD0, out float4
outCol : COLOR, uniform sampler2D ReflectMap )
{
   // projectively sample the 2D reflection texture
   outCol = tex2Dproj(ReflectMap, inTexProj);
}
Source 2: projective texture shader

Tip:

Mvp is the combined view projection matrix. Instead of explicitly multiplying a vertex by two distinct matrices (the camera view matrix, followed by the projection matrix), one can concatenate both matrices into the combined view-projection matrix for performance reasons. Mvp will directly transform a world space point into clip space, without going over camera space. Mathematically speaking,  Mvp = Mp * Mc. If a vertex is originally in local object space instead of world space, then it first needs to be transformed to world space. In this case, Mvp = Mp * Mc * Mo holds true. This latter combined matrix is often called the modelview-projection matrix, and will map a vertex from local object space into clip space.




Clipping planes


Contents
  Introduction
  Optical properties of real life water
  Planar mirrors for local reflections
  Clipping planes
  Putting it all together

  Printable version
  Discuss this article

The Series
  Water I