Water reflections and clip planes...

I am currently meddling with some water reflection code. It is all implemented but it was missing a few key aspects. One of the main problems when building a DYNAMIC reflection map for water is that there shouldn't be anything under the water when you generate the reflection map. But lets go one step backwards and look at the general process.

  • Take regular view camera and mirror it about reflection plane
  • Render scene (or the parts you want) into a render target. This will serve as your reflection map
  • Flip the camera back to its regular state
  • Render the scene regularly. When rendering your reflective material (such as the water), just use projective texturing and the texture you have generated in the first pass.

This all looks sweet and innocent, but what happens if you have geometry under water? This geometry will get rendered into the reflection map, leading to ghosts which shouldn't be in the reflection in the first place. I mean it makes sense, what you see is the light reflected off the water's surface, nothing from underneath. The logical conclusion is that when building the reflection map, you must only render what is above the surface of the water. In some apps, you can get away with an easy fix either because there is nothing under the water or that you have an easy way to turn off this geometry (because you know it is under water).

However, in my case. I just can't do that!! I have some terrain and scenery which can poke underground leading to some nasty visual glitches. So i need another solution. Using DirectX, I could simply use the UserClipPlanes which are supported on most of today's cards. There is a little issue there however... Depending on whether you are using the fixed pipeline of the programmable pipeline, you have to specify the clip plane either in world space or clip space. If you use one of the other this is pretty straight forward. But what if you use a combination (as some of us still do)? This mean i could have to dynamically change the plane for each material rendered based on whether it is fixed or programmable. Doable but yucky!!

But is there another way of achieving the same effect? Well there is! With a little math magic, it is possible to take a regular projection matrix and skew its near clip plane so that it is not perpendicular to the camera view direction but instead matches the plane of the reflection surface, which is exactly what we want! Before I explain in more details, there is a few caveats i should mention:

  • Using a skewed projection matrix like this will change the scale of your zbuffer. Think of it as taking the view frustum and shearing it to match the plane of your reflection. This should not cause issues if you render the whole scene with such a matrix but since the scale of the Z's change, you can't just go back and forth on the matrix when rendering since it will most likely lead to messed up Z-Buffering.
  • Because you are shearing the view frustum, you will start to get bizarre rendering at shallow angles. I mean, what happens when your water is perpendicular to your view direction? The view frustum will become flat and your reflection map will begin to warp. On the bright side, when you approach this case, this also means that your will see little of the reflecting surface since it will be perpendicular to your view anyways. So this will generally not cause any problems.

The code to accomplish this is below (note that this is taken from the NVIDIA sample):


D3DXMATRIXA16 matClipProj;
D3DXMATRIXA16 WorldToProjection;
WorldToProjection = matView * matProj;

// m_clip_plane is plane definition (world space)
D3DXPlaneNormalize(&clip_plane, &clip_plane);
D3DXMatrixInverse(&WorldToProjection, NULL, &WorldToProjection);
D3DXMatrixTranspose(&WorldToProjection, &WorldToProjection);

D3DXVECTOR4 clipPlane(clip_plane.a, clip_plane.b, clip_plane.c, clip_plane.d);
D3DXVECTOR4 projClipPlane;

// transform clip plane into projection space
D3DXVec4Transform(&projClipPlane, &clipPlane, &WorldToProjection);
D3DXMatrixIdentity(&matClipProj);
if (projClipPlane.w == 0) // or less than a really small value
{
// plane is perpendicular to the near plane
m_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj);
return;
}

if (projClipPlane.w > 0)
{
// flip plane to point away from eye
D3DXVECTOR4 clipPlane(-clip_plane.a, -clip_plane.b, -clip_plane.c, -clip_plane.d);
// transform clip plane into projection space
D3DXVec4Transform(&projClipPlane, &clipPlane, &WorldToProjection);
}

// put projection space clip plane in Z column
matClipProj(0, 2) = projClipPlane.x;
matClipProj(1, 2) = projClipPlane.y;
matClipProj(2, 2) = projClipPlane.z;
matClipProj(3, 2) = projClipPlane.w;

// multiply into projection matrix
D3DXMATRIXA16 projClipMatrix = matProj * matClipProj;
m_pd3dDevice->SetTransform( D3DTS_PROJECTION, &projClipMatrix);


In easy terms, that this code does is take the clip plane (which is supplied in world space) and then convert it into clip space. Once this is accomplished, the plane is taken into a matrix (as the Z column) and then applied to our projection matrix. This should have the effect of skewing the near clip plane to match our supplied clip plane.

Does it work? Sure it does, I've used it in other applications in the past. But i am still implementing it for our water reflection. So stay tuned as i might have some updates as i progress :)