Lake Water With Natural Looking Egdes

Introduction

The first lake water demo shows a simple way to render water, but we can update that method to eliminate straight edges at terrain-water intersections.

The following images demonstrate the difference; the left images have the new bumpy water-terrain intersections, while the right images show the original straight edges. (Click image for high resolution.)

lakewaterxna_3_1-2009-11-09-17-41-47-27.jpg?w=350 lakewaterxna_3_1-2009-11-10-13-08-48-34.jpg?w=350
lakewaterxna_3_1-2009-11-09-17-42-12-44.jpg?w=350 lakewaterxna_3_1-2009-11-10-13-08-39-26.jpg?w=350

To achieve this we clear the buffers with a color that is less common in the water that the current green one. Using black (0,0,0) can help us identify the places on the reflection and refraction maps that are invalid: those points are not reflecting/refracting anything. (Of course, for example, white should be used for a night scene as that cannot appear on a real night color palette, while black can.) Here we can remove the clipping-pane elevation that was used to result less artifacts at edges - we are going to fade out those artifacts, no need for elevated clipping any more.
As we have marked the invalid areas on the reflection and refraction maps, we can simply fade out those areas by lowering their alpha values in the pixel shader. Unfortunately, this step can cause transparent stripes at the edges, as the wave height vector is big enough to cause distinct areas reflecting the same points. (The next image shows the new artifacts at the terrain-water intersections. Click for high resolution)

lakewaterxna_3_1-2009-11-08-22-10-21-39.jpg?w=500

We need to lower the wave height to solve this problem. That will result less distortion and faded water surface only near the terrain-water intersections.
Some more images can be seen HERE, but video demonstrations are more useful:

HD video demonstrating the result:

Notice the bumpy/wavy water-terrain intersections, that are not straight lines any more. The drawback of this method is the limited wave-height vector, meaning that the entire water surface is less distorted than the original version.

Summary of changes

  • Enable alpha testing for water rendering
  • Use black as clear color for reflection and refraction maps
  • Restore elevated clipping plane - we do not need the modification for refraction rendering any more
  • Fade out black areas in the pixel shader
  • Lower wave height vector to eliminate unrealistic transparent areas at the edges

Download

Click here to download
For requirements, see the Download page of the original lake water shader.

Code changes

Enable alpha testing

  public void Draw(Matrix p_ViewMatrix, Matrix p_ReflectionViewMatrix, Matrix p_ProjectionMatrix, int xEnableTextureBlending, float p_fElapsedTime)
        {
        m_Device.RenderState.AlphaBlendEnable = true;
        m_Device.RenderState.AlphaTestEnable = true;
        m_Device.RenderState.AlphaBlendOperation = BlendFunction.Add;
        m_Device.RenderState.AlphaFunction = CompareFunction.Greater;
 
        //Original draw method code comes here...
 
        m_Device.RenderState.AlphaBlendEnable = false;
        m_Device.RenderState.AlphaTestEnable = false;
}

Clearing with black color

 m_Device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0);

Color.Black defines the RGB(0,0,0) color, the one we require. This should be used for both the reflection and refraction map rendering methods.

Removing elevated clipping

We clip everything above the water plane for refraction rendering as nothing above that level can be refracted by the water - the should be not on the map either. We used a correction value to decrease the number of artifacts at edges, but we do not need that correction any more as we intend to fade those artifacts.

Vector4 planeCoefficients = new Vector4(planeNormalDirection, GetWaterHeight());

It was:
Vector4 planeCoefficients = new Vector4(planeNormalDirection, GetWaterHeight() + correction );

Fading artifacts in the pixel shader

// alpha is set to 1 except when the reflection map does not have a valid 
// value (color is completely black)
if ( reflectiveColor.r + reflectiveColor.b + reflectiveColor.g < 0.1f )
{
    reflectiveColor.a = 0.0f;
}
else
{
    reflectiveColor.a = 1.0f;
}
 
// Some code comes here...
 
// creating the combined color
float4 combinedColor = refractiveColor*(1-fresnelTerm)*refractiveColor.a*reflectiveColor.a + reflectiveColor*(fresnelTerm)*reflectiveColor.a*refractiveColor.a;

These are the main changes in the HLSL code: Alpha value is set and used.

Full HLSL code

//------- Constants --------
float4x4 xView;
float4x4 xProjection;
float4x4 xWorld;
float4x4 xReflectionView;
float4x4 xWindDirection;
float3 xCamPos;
float3 xLightDirection;
float xAmbient;
bool xEnableLighting;
 
// variables for the BUMP MAP
float xWaveLength;
float xWaveHeight;
 
 float xTime;
 float xWindForce;
 float xDrawMode;
 
 //fresnel  intput variables
 int fresnelMode;
 
 //specular input variables
 float specPerturb;
 float specPower;
 
 //dullblend factor
 float xdullBlendFactor;
 
  //dullblend factor
 int xEnableTextureBlending;
 
//------- Texture Samplers --------
Texture xReflectionMap;
 
sampler ReflectionSampler = sampler_state { texture = <xReflectionMap> ; magfilter = LINEAR; minfilter = LINEAR; mipfilter=LINEAR; AddressU = mirror; AddressV = mirror;};
 
Texture xRefractionMap;
 
sampler RefractionSampler = sampler_state { texture = <xRefractionMap> ; magfilter = LINEAR; minfilter = LINEAR; mipfilter=LINEAR; AddressU = mirror; AddressV = mirror;};
 
/////// BUMP MAP
Texture xWaterBumpMap;
 
sampler WaterBumpMapSampler = sampler_state { texture = <xWaterBumpMap>; magfilter = LINEAR; minfilter = LINEAR; mipfilter=LINEAR; AddressU = mirror; AddressV = mirror;};
 
//------- Technique: Water --------
 
struct WaterVertexToPixel
{
    float4 Position                 : POSITION;    
    float4 ReflectionMapSamplingPos    : TEXCOORD1;
    float2 BumpMapSamplingPos        : TEXCOORD2;
    float4 RefractionMapSamplingPos : TEXCOORD3;
    float4 Position3D                : TEXCOORD4;
};
 
struct WaterPixelToFrame
{
    float4 Color : COLOR0;
};
 
WaterVertexToPixel WaterVS(float4 inPos : POSITION, float2 inTex: TEXCOORD)
{
    WaterVertexToPixel Output = (WaterVertexToPixel)0;
    float4x4 preViewProjection = mul (xView, xProjection);
    float4x4 preWorldViewProjection = mul (xWorld, preViewProjection);
    float4x4 preReflectionViewProjection = mul (xReflectionView, xProjection);
    float4x4 preWorldReflectionViewProjection = mul (xWorld, preReflectionViewProjection);
 
    Output.Position = mul(inPos, preWorldViewProjection);
    Output.ReflectionMapSamplingPos = mul(inPos, preWorldReflectionViewProjection);
    Output.RefractionMapSamplingPos = mul(inPos, preWorldViewProjection);
    Output.Position3D = inPos;
 
     float4 absoluteTexCoords = float4(inTex, 0, 1);
     float4 rotatedTexCoords = mul(absoluteTexCoords, xWindDirection);
     float2 moveVector = float2(0, 1);
 
     // moving the water
     Output.BumpMapSamplingPos = rotatedTexCoords.xy/xWaveLength + xTime*xWindForce*moveVector.xy;
 
    return Output;    
}
 
WaterPixelToFrame WaterPS(WaterVertexToPixel PSIn)
{
    WaterPixelToFrame Output = (WaterPixelToFrame)0;
 
    float2 ProjectedTexCoords;
    ProjectedTexCoords.x = PSIn.ReflectionMapSamplingPos.x/PSIn.ReflectionMapSamplingPos.w/2.0f + 0.5f;
    ProjectedTexCoords.y = -PSIn.ReflectionMapSamplingPos.y/PSIn.ReflectionMapSamplingPos.w/2.0f + 0.5f;
 
    // sampling the bump map
    float4 bumpColor = tex2D(WaterBumpMapSampler, PSIn.BumpMapSamplingPos);
 
    // perturbating the color
    float2 perturbation = xWaveHeight*(bumpColor.rg - 0.5f);
 
    // the final texture coordinates
    float2 perturbatedTexCoords = ProjectedTexCoords + perturbation;
    float4 reflectiveColor = tex2D(ReflectionSampler, perturbatedTexCoords);
 
    // alpha is set to 1 except when the reflection map does not have a valid 
    // value (color is completely black)
    if ( reflectiveColor.r + reflectiveColor.b + reflectiveColor.g < 0.1f )
    {
        reflectiveColor.a = 0.0f;
    }
    else
    {
        reflectiveColor.a = 1.0f;
    }
 
    float2 ProjectedRefrTexCoords;
    ProjectedRefrTexCoords.x = PSIn.RefractionMapSamplingPos.x/PSIn.RefractionMapSamplingPos.w/2.0f + 0.5f;
    ProjectedRefrTexCoords.y = -PSIn.RefractionMapSamplingPos.y/PSIn.RefractionMapSamplingPos.w/2.0f + 0.5f;
    float2 perturbatedRefrTexCoords = ProjectedRefrTexCoords + perturbation;
    float4 refractiveColor = tex2D(RefractionSampler, perturbatedRefrTexCoords);
 
    // alpha is set to 1 except when the refraction map does not have a valid 
    // value (color is completely black)
    if ( refractiveColor.r + refractiveColor.b + refractiveColor.g < 0.1f )
    {
        refractiveColor.a = 0.0f;
    }
    else
    {
        refractiveColor.a = 1.0f;
    }
 
    float3 eyeVector = normalize(xCamPos - PSIn.Position3D);
    float3 normalVector = float3(0,0,1);
 
/////////////////////////////////////////////////
// FRESNEL TERM APPROXIMATION
/////////////////////////////////////////////////
    float fresnelTerm = (float)0;
 
    if ( fresnelMode == 1 )
    {
        fresnelTerm = 0.02+0.97f*pow((1-dot(eyeVector, normalVector)),5);
    } 
    else
    {
        fresnelTerm = 1-dot(eyeVector, normalVector)*1.3f;
    } 
 
    //Hardness factor - user input
    fresnelTerm = fresnelTerm * xDrawMode;
 
    //just to be sure that the value is between 0 and 1;
    fresnelTerm = fresnelTerm < 0? 0 : fresnelTerm;
    fresnelTerm = fresnelTerm > 1? 1 : fresnelTerm;
 
    // creating the combined color
    float4 combinedColor = refractiveColor*(1-fresnelTerm)*refractiveColor.a*reflectiveColor.a + reflectiveColor*(fresnelTerm)*reflectiveColor.a*refractiveColor.a;
 
    /////////////////////////////////////////////////
    // WATER COLORING
    /////////////////////////////////////////////////
        // Add some dull color to make the water darker then the reflected picture...
        Output.Color = dullBlendFactor * combinedColor * float4(0.95f,1.00f,1.05f,1.0f);
 
/////////////////////////////////////////////////
// Specular Highlights
/////////////////////////////////////////////////
 
  float4 speccolor;
 
    float3 lightSourceDir = normalize(float3(0.1f,0.6f,0.5f));
 
    float3 halfvec = normalize(eyeVector+lightSourceDir+float3(perturbation.x*specPerturb,perturbation.y*specPerturb,0));
 
    float3 temp = 0;
 
    temp.x = pow(dot(halfvec,normalVector),specPower);
 
    speccolor = float4(0.98,0.97,0.7,0.6);
 
    speccolor = speccolor*temp.x;
 
    speccolor = float4(speccolor.x*speccolor.w,speccolor.y*speccolor.w,speccolor.z*speccolor.w,0);
 
    Output.Color = Output.Color + speccolor;
 
    return Output;
}
 
technique Water
{
    pass Pass0
    {
        VertexShader = compile vs_2_0 WaterVS();
        PixelShader = compile ps_2_0 WaterPS();
    }
}
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License