Tutorial 29: Water

This tutorial will cover how to implement water in DirectX 10 using HLSL and C++. The code in this tutorial is based and builds upon the previous tutorials.

There are many different types of water implementations, each of which has advantages and disadvantages. The type of water we will cover in this tutorial is reflective and refractive water. It is one of the best looking techniques but it does have edge problems if the wave height is too large. Therefore reflective and refractive water works best for smaller water bodies that have very small wave heights such as rivers, ponds, and pools. You can extend this technique to lakes and oceans provided they are semi-still.

To start the tutorial I will build a simple scene to facilitate the reflective and refractive water technique. The scene will have a flat ground and a marble water bath to hold the water. It will also have a stone pillar so there is an object for the water to reflect. The basic scene looks like the following:

Then we create a flat water quad made out of two triangles and translate it so it sits inside the bath object. We won't give the water object a texture but instead we will use a reflection of the scene as the water texture. To build the reflection texture we use the reflection technique from the reflection tutorial and create a reflection render to texture. We render the reflection of the scene from the height of the water based on our camera position/angle and it produces a water that reflects everything above it:

Now that we have reflective water we can improve the technique by adding refractive water. Refraction is basically the inverse of reflection. In reflection we render everything above the water line, but in refraction we render everything below the water line. Since this is just a simple modification of the reflection technique we can quickly produce a refraction to a render to texture in the same way and then map that texture to the water plane. Also note that you should not render the ground and pillar when creating the refraction since the only object that can be seen beneath the water will be the marble bath. The following is the refraction texture highlighted in yellow and I have purposely not rendered the bath in this image so you can clearly see what the refraction should look like:

Now that we have a reflection texture and a refraction texture mapped to the water quad we will combine the two textures using a linear interpolation to produce the reflective and refractive water effect:

To enhance this reflective and refractive water effect we will add a normal map to simulate ripples in the water. The bump mapping tutorial explained the use of normal maps to create bumps and in this tutorial will we use the normal maps to create water ripples in a similar fashion. However in bump mapping we used the light direction with the bump normal to determine the per pixel lighting. But with water we use the normal to distort the texture sampling location the same way a wave distorts what we see beneath it in water. We will also translate the normal map along the Y axis in this tutorial to move the ripples and simulate moving water.

As a side note the way I created a normal map for this tutorial was through the use of Photoshop and the Nvidia texture tool (available on Nvidia's website). In Photoshop create a new image (I created a 256x256 image). Then with just the default black and white colors I did a Filter->Render->Clouds which gives a perlin noise style cloud formation. Then I did a Filter->Distort->Ocean Ripple to give a water ripple look to the cloud image. Finally I did the Filter->NVIDIA Tools->Normal Map Filter and set the scale to 10 or so. It then produces the final image which I save into a DDS format using the DirectX Texture Tool:

One final thing to note is that some graphics engines only update the reflection and refraction texture once every 15-30 frames or so to gain some speed by removing the expensive render to textures every single frame.


Framework

The frame work was updated to include the new RefractionShaderClass and WaterShaderClass.


Water.fx

////////////////////////////////////////////////////////////////////////////////
// Filename: water.fx
////////////////////////////////////////////////////////////////////////////////


/////////////
// GLOBALS //
/////////////
matrix worldMatrix;
matrix viewMatrix;
matrix projectionMatrix;

Just like the reflection tutorial the water shader will require a reflection matrix.

matrix reflectionMatrix;

The water shader will need three textures. A reflection texture for the scene reflection. A refraction texture for the refraction of the scene. And finally a normal map texture for simulating water ripples.

Texture2D reflectionTexture;
Texture2D refractionTexture;
Texture2D normalTexture;

The water translation variable will be used for simulating water motion by translating the texture sampling coordinates each frame.

float waterTranslation;

The reflectRefractScale variable is used for controlling the size of the water ripples in relation to the normal map. Some normal maps will be slightly different in how drastic the normals rise and fall. Having a variable to control this becomes very useful.

float reflectRefractScale;


///////////////////
// SAMPLE STATES //
///////////////////
SamplerState SampleType
{
    Filter = MIN_MAG_MIP_LINEAR;
    AddressU = Wrap;
    AddressV = Wrap;
};


//////////////
// TYPEDEFS //
//////////////
struct VertexInputType
{
    float4 position : POSITION;
    float2 tex : TEXCOORD0;
};

The PixelInputType has two extra texture coordinate inputs for the reflection texture coordinates and the refraction texture coordinates.

struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
    float4 reflectionPosition : TEXCOORD1;
    float4 refractionPosition : TEXCOORD2;
};


////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType WaterVertexShader(VertexInputType input)
{
    PixelInputType output;
    matrix reflectProjectWorld;
    matrix viewProjectWorld;
	
    
    // Change the position vector to be 4 units for proper matrix calculations.
    input.position.w = 1.0f;

    // Calculate the position of the vertex against the world, view, and projection matrices.
    output.position = mul(input.position, worldMatrix);
    output.position = mul(output.position, viewMatrix);
    output.position = mul(output.position, projectionMatrix);
    
    // Store the texture coordinates for the pixel shader.
    output.tex = input.tex;

Create the reflection projection world matrix just like the reflection tutorial and calculate the reflection coordinates from it.

    // Create the reflection projection world matrix.
    reflectProjectWorld = mul(reflectionMatrix, projectionMatrix);
    reflectProjectWorld = mul(worldMatrix, reflectProjectWorld);

    // Calculate the input position against the reflectProjectWorld matrix.
    output.reflectionPosition = mul(input.position, reflectProjectWorld);

Refraction coordinates are calculated in the same way as the reflection coordinates except that we use a view projection world matrix for them.

    // Create the view projection world matrix for refraction.
    viewProjectWorld = mul(viewMatrix, projectionMatrix);
    viewProjectWorld = mul(worldMatrix, viewProjectWorld);
   
    // Calculate the input position against the viewProjectWorld matrix.
    output.refractionPosition = mul(input.position, viewProjectWorld);

    return output;
}



////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 WaterPixelShader(PixelInputType input) : SV_Target
{
    float2 reflectTexCoord;
    float2 refractTexCoord;
    float4 normalMap;
    float3 normal;
    float4 reflectionColor;
    float4 refractionColor;
    float4 color;

Just like the translate shader tutorial we use a translation variable updated each frame to move the water normal map texture along the Y axis to simulate motion.

    // Move the position the water normal is sampled from to simulate moving water.	
    input.tex.y += waterTranslation;

Convert both the reflection and refraction coordinates into texture coordinates in the -1 to +1 range.

    // Calculate the projected reflection texture coordinates.
    reflectTexCoord.x = input.reflectionPosition.x / input.reflectionPosition.w / 2.0f + 0.5f;
    reflectTexCoord.y = -input.reflectionPosition.y / input.reflectionPosition.w / 2.0f + 0.5f;
	
    // Calculate the projected refraction texture coordinates.
    refractTexCoord.x = input.refractionPosition.x / input.refractionPosition.w / 2.0f + 0.5f;
    refractTexCoord.y = -input.refractionPosition.y / input.refractionPosition.w / 2.0f + 0.5f;

Sample the normal for this pixel from the normal map and expand the range to be in the -1 to +1 range.

    // Sample the normal from the normal map texture.
    normalMap = normalTexture.Sample(SampleType, input.tex);

    // Expand the range of the normal from (0,1) to (-1,+1).
    normal = (normalMap.xyz * 2.0f) - 1.0f;

Now distort the reflection and refraction coordinates by the normal map value. This creates the rippling effect by using the normal transitioning from -1 to +1 to distort our view just as water waves distort light. The normal map value is multiplied by the reflectRefractScale to make the ripples less pronounced and more natural looking.

    // Re-position the texture coordinate sampling position by the normal map value to simulate the rippling wave effect.
    reflectTexCoord = reflectTexCoord + (normal.xy * reflectRefractScale);
    refractTexCoord = refractTexCoord + (normal.xy * reflectRefractScale);

Now sample the reflection and refraction pixel based on the updated texture sampling coordinates.

    // Sample the texture pixels from the textures using the updated texture coordinates.
    reflectionColor = reflectionTexture.Sample(SampleType, reflectTexCoord);
    refractionColor = refractionTexture.Sample(SampleType, refractTexCoord);

Finally combine the reflection and refraction pixel using a linear interpolation.

    // Combine the reflection and refraction results for the final color.
    color = lerp(reflectionColor, refractionColor, 0.6f);

    return color;
}


////////////////////////////////////////////////////////////////////////////////
// Technique
////////////////////////////////////////////////////////////////////////////////
technique10 WaterTechnique
{
    pass pass0
    {
        SetVertexShader(CompileShader(vs_4_0, WaterVertexShader()));
        SetPixelShader(CompileShader(ps_4_0, WaterPixelShader()));
        SetGeometryShader(NULL);
    }
}

Watershaderclass.h

The WaterShaderClass handles shading water models using the water.fx HLSL shader.

////////////////////////////////////////////////////////////////////////////////
// Filename: watershaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _WATERSHADERCLASS_H_
#define _WATERSHADERCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d10.h>
#include <d3dx10.h>
#include <fstream>
using namespace std;


////////////////////////////////////////////////////////////////////////////////
// Class name: WaterShaderClass
////////////////////////////////////////////////////////////////////////////////
class WaterShaderClass
{
public:
	WaterShaderClass();
	WaterShaderClass(const WaterShaderClass&);
	~WaterShaderClass();

	bool Initialize(ID3D10Device*, HWND);
	void Shutdown();
	void Render(ID3D10Device*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D10ShaderResourceView*, 
		    ID3D10ShaderResourceView*, ID3D10ShaderResourceView*, float, float);

private:
	bool InitializeShader(ID3D10Device*, HWND, WCHAR*);
	void ShutdownShader();
	void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*);

	void SetShaderParameters(D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D10ShaderResourceView*, 
				 ID3D10ShaderResourceView*, ID3D10ShaderResourceView*, float, float);
	void RenderShader(ID3D10Device*, int);

private:
	ID3D10Effect* m_effect;
	ID3D10EffectTechnique* m_technique;
	ID3D10InputLayout* m_layout;

	ID3D10EffectMatrixVariable* m_worldMatrixPtr;
	ID3D10EffectMatrixVariable* m_viewMatrixPtr;
	ID3D10EffectMatrixVariable* m_projectionMatrixPtr;

We have new pointers to all the new variables that were inside the water.fx HLSL shader file.

	ID3D10EffectMatrixVariable* m_reflectionMatrixPtr;
	ID3D10EffectShaderResourceVariable* m_reflectionTexturePtr;
	ID3D10EffectShaderResourceVariable* m_refractionTexturePtr;
	ID3D10EffectShaderResourceVariable* m_normalTexturePtr;
	ID3D10EffectScalarVariable* m_translationPtr;
	ID3D10EffectScalarVariable* m_reflectRefractScalePtr;
};

#endif

Watershaderclass.cpp

////////////////////////////////////////////////////////////////////////////////
// Filename: watershaderclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "watershaderclass.h"


WaterShaderClass::WaterShaderClass()
{
	m_effect = 0;
	m_technique = 0;
	m_layout = 0;

	m_worldMatrixPtr = 0;
	m_viewMatrixPtr = 0;
	m_projectionMatrixPtr = 0;

Initialize the new pointers inside the class constructor.

	m_reflectionMatrixPtr = 0;
	m_reflectionTexturePtr = 0;
	m_refractionTexturePtr = 0;
	m_normalTexturePtr = 0;
	m_translationPtr = 0;
	m_reflectRefractScalePtr = 0;
}


WaterShaderClass::WaterShaderClass(const WaterShaderClass& other)
{
}


WaterShaderClass::~WaterShaderClass()
{
}


bool WaterShaderClass::Initialize(ID3D10Device* device, HWND hwnd)
{
	bool result;

Load the new water.fx HLSL shader file.

	// Initialize the shader that will be used to draw the triangles.
	result = InitializeShader(device, hwnd, L"../Engine/water.fx");
	if(!result)
	{
		return false;
	}

	return true;
}


void WaterShaderClass::Shutdown()
{
	// Shutdown the shader effect.
	ShutdownShader();

	return;
}

The Render function takes in all the new water parameters that will be set inside the shader before rendering the water model. This includes the reflection matrix, the reflection texture, the refraction texture, the normal map texture, the water translation, and the reflect refract scale.

void WaterShaderClass::Render(ID3D10Device* device, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, 
			      D3DXMATRIX projectionMatrix, D3DXMATRIX reflectionMatrix, 
			      ID3D10ShaderResourceView* reflectionTexture, ID3D10ShaderResourceView* refractionTexture,
			      ID3D10ShaderResourceView* normalTexture, float waterTranslation, float reflectRefractScale)
{
	// Set the shader parameters that it will use for rendering.
	SetShaderParameters(worldMatrix, viewMatrix, projectionMatrix, reflectionMatrix, reflectionTexture, refractionTexture, 
			    normalTexture, waterTranslation, reflectRefractScale);

	// Now render the prepared buffers with the shader.
	RenderShader(device, indexCount);

	return;
}


bool WaterShaderClass::InitializeShader(ID3D10Device* device, HWND hwnd, WCHAR* filename)
{
	HRESULT result;
	ID3D10Blob* errorMessage;
	D3D10_INPUT_ELEMENT_DESC polygonLayout[2];
	unsigned int numElements;
	D3D10_PASS_DESC passDesc;


	// Initialize the error message.
	errorMessage = 0;

	// Load the shader in from the file.
	result = D3DX10CreateEffectFromFile(filename, NULL, NULL, "fx_4_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, 
					    device, NULL, NULL, &m_effect, &errorMessage, NULL);
	if(FAILED(result))
	{
		// If the shader failed to compile it should have writen something to the error message.
		if(errorMessage)
		{
			OutputShaderErrorMessage(errorMessage, hwnd, filename);
		}
		// If there was  nothing in the error message then it simply could not find the shader file itself.
		else
		{
			MessageBox(hwnd, filename, L"Missing Shader File", MB_OK);
		}

		return false;
	}

The name of the technique is set to WaterTechnique.

	// Get a pointer to the technique inside the shader.
	m_technique = m_effect->GetTechniqueByName("WaterTechnique");
	if(!m_technique)
	{
		return false;
	}

	// Now setup the layout of the data that goes into the shader.
	// This setup needs to match the VertexType stucture in the ModelClass and in the shader.
	polygonLayout[0].SemanticName = "POSITION";
	polygonLayout[0].SemanticIndex = 0;
	polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT;
	polygonLayout[0].InputSlot = 0;
	polygonLayout[0].AlignedByteOffset = 0;
	polygonLayout[0].InputSlotClass = D3D10_INPUT_PER_VERTEX_DATA;
	polygonLayout[0].InstanceDataStepRate = 0;

	polygonLayout[1].SemanticName = "TEXCOORD";
	polygonLayout[1].SemanticIndex = 0;
	polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT;
	polygonLayout[1].InputSlot = 0;
	polygonLayout[1].AlignedByteOffset = D3D10_APPEND_ALIGNED_ELEMENT;
	polygonLayout[1].InputSlotClass = D3D10_INPUT_PER_VERTEX_DATA;
	polygonLayout[1].InstanceDataStepRate = 0;

	// Get a count of the elements in the layout.
	numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);

	// Get the description of the first pass described in the shader technique.
	m_technique->GetPassByIndex(0)->GetDesc(&passDesc);

	// Create the input layout.
	result = device->CreateInputLayout(polygonLayout, numElements, passDesc.pIAInputSignature, passDesc.IAInputSignatureSize, 
					   &m_layout);
	if(FAILED(result))
	{
		return false;
	}

	// Get pointers to the three matrices inside the shader so we can update them from this class.
	m_worldMatrixPtr = m_effect->GetVariableByName("worldMatrix")->AsMatrix();
	m_viewMatrixPtr = m_effect->GetVariableByName("viewMatrix")->AsMatrix();
	m_projectionMatrixPtr = m_effect->GetVariableByName("projectionMatrix")->AsMatrix();

We setup pointer interfaces to all the new variables inside the water shader.

	// Get a pointer to the reflection matrix resource inside the shader.
	m_reflectionMatrixPtr = m_effect->GetVariableByName("reflectionMatrix")->AsMatrix();

	// Get a pointer to the reflection texture resource inside the shader.
	m_reflectionTexturePtr = m_effect->GetVariableByName("reflectionTexture")->AsShaderResource();

	// Get a pointer to the refraction texture resource inside the shader.
	m_refractionTexturePtr = m_effect->GetVariableByName("refractionTexture")->AsShaderResource();

	// Get a pointer to the normal map texture resource inside the shader.
	m_normalTexturePtr = m_effect->GetVariableByName("normalTexture")->AsShaderResource();

	// Get a pointer to the water translation variable inside the shader.
	m_translationPtr = m_effect->GetVariableByName("waterTranslation")->AsScalar();

	// Get a pointer to the reflection refraction scale variable inside the shader.
	m_reflectRefractScalePtr = m_effect->GetVariableByName("reflectRefractScale")->AsScalar();

	return true;
}


void WaterShaderClass::ShutdownShader()
{

In the ShutdownShader function we release all the new pointers to variables inside the water shader.

	// Release the reflection refraction scale pointer.
	m_reflectRefractScalePtr = 0;

	// Release the water translation pointer.
	m_translationPtr = 0;

	// Release the normal map texture pointer.
	m_normalTexturePtr = 0;

	// Release the reflection/refraction pointers.
	m_refractionTexturePtr = 0;
	m_reflectionTexturePtr = 0;
	m_reflectionMatrixPtr = 0;

	// Release the pointers to the matrices inside the shader.
	m_worldMatrixPtr = 0;
	m_viewMatrixPtr = 0;
	m_projectionMatrixPtr = 0;

	// Release the pointer to the shader layout.
	if(m_layout)
	{
		m_layout->Release();
		m_layout = 0;
	}

	// Release the pointer to the shader technique.
	m_technique = 0;

	// Release the pointer to the shader.
	if(m_effect)
	{
		m_effect->Release();
		m_effect = 0;
	}

	return;
}


void WaterShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename)
{
	char* compileErrors;
	unsigned long bufferSize, i;
	ofstream fout;


	// Get a pointer to the error message text buffer.
	compileErrors = (char*)(errorMessage->GetBufferPointer());

	// Get the length of the message.
	bufferSize = errorMessage->GetBufferSize();

	// Open a file to write the error message to.
	fout.open("shader-error.txt");

	// Write out the error message.
	for(i=0; i<bufferSize; i++)
	{
		fout << compileErrors[i];
	}

	// Close the file.
	fout.close();

	// Release the error message.
	errorMessage->Release();
	errorMessage = 0;

	// Pop a message up on the screen to notify the user to check the text file for compile errors.
	MessageBox(hwnd, L"Error compiling shader.  Check shader-error.txt for message.", shaderFilename, MB_OK);

	return;
}

SetShaderParameters function sets all the new water shader parameters inside the shader before rendering the water model.

void WaterShaderClass::SetShaderParameters(D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, 
					   D3DXMATRIX reflectionMatrix, ID3D10ShaderResourceView* reflectionTexture,
					   ID3D10ShaderResourceView* refractionTexture, ID3D10ShaderResourceView* normalTexture,
					   float waterTranslation, float reflectRefractScale)
{
	// Set the world matrix variable inside the shader.
	m_worldMatrixPtr->SetMatrix((float*)&worldMatrix);

	// Set the view matrix variable inside the shader.
	m_viewMatrixPtr->SetMatrix((float*)&viewMatrix);

	// Set the projection matrix variable inside the shader.
	m_projectionMatrixPtr->SetMatrix((float*)&projectionMatrix);

	// Set the reflection matrix variable inside the shader.
	m_reflectionMatrixPtr->SetMatrix((float*)&reflectionMatrix);

	// Bind the reflection texture.
	m_reflectionTexturePtr->SetResource(reflectionTexture);

	// Bind the refraction texture.
	m_refractionTexturePtr->SetResource(refractionTexture);

	// Bind the normal map texture.
	m_normalTexturePtr->SetResource(normalTexture);

	// Set the water translation variable inside the shader.
	m_translationPtr->SetFloat(waterTranslation);

	// Set the reflection refraction scale variable inside the shader.
	m_reflectRefractScalePtr->SetFloat(reflectRefractScale);

	return;
}

The RenderShader function draws the water model using the water technique in the HLSL file.

void WaterShaderClass::RenderShader(ID3D10Device* device, int indexCount)
{
	D3D10_TECHNIQUE_DESC techniqueDesc;
	unsigned int i;
	

	// Set the input layout.
	device->IASetInputLayout(m_layout);

	// Get the description structure of the technique from inside the shader so it can be used for rendering.
	m_technique->GetDesc(&techniqueDesc);

	// Go through each pass in the technique (should be just one currently) and render the triangles.
	for(i=0; i<techniqueDesc.Passes; ++i)
	{
		m_technique->GetPassByIndex(i)->Apply(0);
		device->DrawIndexed(indexCount, 0, 0);
	}

	return;
}

Refraction.fx

The refraction shader is just the light shader re-written with a clip plane. As the refraction renders the scene normally but only renders what is underneath the water we use the clip plane with the light shader to achieve this effect.

////////////////////////////////////////////////////////////////////////////////
// Filename: refraction.fx
////////////////////////////////////////////////////////////////////////////////


/////////////
// GLOBALS //
/////////////
matrix worldMatrix;
matrix viewMatrix;
matrix projectionMatrix;
Texture2D shaderTexture;
float4 ambientColor;
float4 diffuseColor;
float3 lightDirection;

We add the clip plane variable to the light shader to make it a refraction shader.

float4 clipPlane;


///////////////////
// SAMPLE STATES //
///////////////////
SamplerState SampleType
{
    Filter = MIN_MAG_MIP_LINEAR;
    AddressU = Wrap;
    AddressV = Wrap;
};


//////////////
// TYPEDEFS //
//////////////
struct VertexInputType
{
    float4 position : POSITION;
    float2 tex : TEXCOORD0;
    float3 normal : NORMAL;
};

The clip distance variable is added to the pixel input to achieve the clipping effect needed for the refraction shader.

struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
    float3 normal : NORMAL;
    float clip : SV_ClipDistance0;
};


////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType RefractionVertexShader(VertexInputType input)
{
    PixelInputType output;
    
    
    // Change the position vector to be 4 units for proper matrix calculations.
    input.position.w = 1.0f;

    // Calculate the position of the vertex against the world, view, and projection matrices.
    output.position = mul(input.position, worldMatrix);
    output.position = mul(output.position, viewMatrix);
    output.position = mul(output.position, projectionMatrix);
    
    // Store the texture coordinates for the pixel shader.
    output.tex = input.tex;
    
    // Calculate the normal vector against the world matrix only.
    output.normal = mul(input.normal, (float3x3)worldMatrix);
	
    // Normalize the normal vector.
    output.normal = normalize(output.normal);

Here we setup the clipping plane.

    // Set the clipping plane.
    output.clip = dot(mul(input.position, worldMatrix), clipPlane);

    return output;
}



////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 RefractionPixelShader(PixelInputType input) : SV_Target
{
    float4 textureColor;
    float3 lightDir;
    float lightIntensity;
    float4 color;
	

    // Sample the texture pixel at this location.
    textureColor = shaderTexture.Sample(SampleType, input.tex);

    // Set the default output color to the ambient light value for all pixels.
    color = ambientColor;
	
    // Invert the light direction for calculations.
    lightDir = -lightDirection;

    // Calculate the amount of light on this pixel.
    lightIntensity = saturate(dot(input.normal, lightDir));
	
    if(lightIntensity > 0.0f)
    {
        // Determine the final diffuse color based on the diffuse color and the amount of light intensity.
        color += (diffuseColor * lightIntensity);
    }

    // Saturate the final light color.
    color = saturate(color);
	
    // Multiply the texture pixel and the input color to get the final result.
    color = color * textureColor;
	
    return color;
}


////////////////////////////////////////////////////////////////////////////////
// Technique
////////////////////////////////////////////////////////////////////////////////
technique10 RefractionTechnique
{
    pass pass0
    {
        SetVertexShader(CompileShader(vs_4_0, RefractionVertexShader()));
        SetPixelShader(CompileShader(ps_4_0, RefractionPixelShader()));
        SetGeometryShader(NULL);
    }
}

Refractionshaderclass.h

The RefractionShaderClass is just the LightShaderClass with a clip plane variable.

////////////////////////////////////////////////////////////////////////////////
// Filename: refractionshaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _REFRACTIONSHADERCLASS_H_
#define _REFRACTIONSHADERCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d10.h>
#include <d3dx10.h>
#include <fstream>
using namespace std;


////////////////////////////////////////////////////////////////////////////////
// Class name: RefractionShaderClass
////////////////////////////////////////////////////////////////////////////////
class RefractionShaderClass
{
public:
	RefractionShaderClass();
	RefractionShaderClass(const RefractionShaderClass&);
	~RefractionShaderClass();

	bool Initialize(ID3D10Device*, HWND);
	void Shutdown();
	void Render(ID3D10Device*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D10ShaderResourceView*, D3DXVECTOR3, D3DXVECTOR4, 
		    D3DXVECTOR4, D3DXVECTOR4);

private:
	bool InitializeShader(ID3D10Device*, HWND, WCHAR*);
	void ShutdownShader();
	void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*);

	void SetShaderParameters(D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D10ShaderResourceView*, D3DXVECTOR3, D3DXVECTOR4, D3DXVECTOR4,
				 D3DXVECTOR4);
	void RenderShader(ID3D10Device*, int);

private:
	ID3D10Effect* m_effect;
	ID3D10EffectTechnique* m_technique;
	ID3D10InputLayout* m_layout;

	ID3D10EffectMatrixVariable* m_worldMatrixPtr;
	ID3D10EffectMatrixVariable* m_viewMatrixPtr;
	ID3D10EffectMatrixVariable* m_projectionMatrixPtr;
	ID3D10EffectShaderResourceVariable* m_texturePtr;

	ID3D10EffectVectorVariable* lightDirectionPtr;
	ID3D10EffectVectorVariable* ambientColorPtr;
	ID3D10EffectVectorVariable* diffuseColorPtr;

The clip plane pointer is added here.

	ID3D10EffectVectorVariable* clipPlanePtr;
};

#endif

Refractionshaderclass.cpp

////////////////////////////////////////////////////////////////////////////////
// Filename: refractionshaderclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "refractionshaderclass.h"


RefractionShaderClass::RefractionShaderClass()
{
	m_effect = 0;
	m_technique = 0;
	m_layout = 0;

	m_worldMatrixPtr = 0;
	m_viewMatrixPtr = 0;
	m_projectionMatrixPtr = 0;
	m_texturePtr = 0;

	lightDirectionPtr = 0;
	ambientColorPtr = 0;
	diffuseColorPtr = 0;

Initialize the clip plane pointer to null in the class constructor.

	clipPlanePtr = 0;
}


RefractionShaderClass::RefractionShaderClass(const RefractionShaderClass& other)
{
}


RefractionShaderClass::~RefractionShaderClass()
{
}


bool RefractionShaderClass::Initialize(ID3D10Device* device, HWND hwnd)
{
	bool result;

Load the refraction.fx HLSL file.

	// Initialize the shader that will be used to draw the triangle.
	result = InitializeShader(device, hwnd, L"../Engine/refraction.fx");
	if(!result)
	{
		return false;
	}

	return true;
}


void RefractionShaderClass::Shutdown()
{
	// Shutdown the shader effect.
	ShutdownShader();

	return;
}

The Render function now takes a clip plane input variable.

void RefractionShaderClass::Render(ID3D10Device* device, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix,
				   D3DXMATRIX projectionMatrix, ID3D10ShaderResourceView* texture, D3DXVECTOR3 lightDirection,
				   D3DXVECTOR4 ambientColor, D3DXVECTOR4 diffuseColor, D3DXVECTOR4 clipPlane)
{
	// Set the shader parameters that it will use for rendering.
	SetShaderParameters(worldMatrix, viewMatrix, projectionMatrix, texture, lightDirection, ambientColor, diffuseColor, clipPlane);

	// Now render the prepared buffers with the shader.
	RenderShader(device, indexCount);

	return;
}


bool RefractionShaderClass::InitializeShader(ID3D10Device* device, HWND hwnd, WCHAR* filename)
{
	HRESULT result;
	ID3D10Blob* errorMessage;
	D3D10_INPUT_ELEMENT_DESC polygonLayout[3];
	unsigned int numElements;
	D3D10_PASS_DESC passDesc;


	// Initialize the error message.
	errorMessage = 0;

	// Load the shader in from the file.
	result = D3DX10CreateEffectFromFile(filename, NULL, NULL, "fx_4_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, 
					    device, NULL, NULL, &m_effect, &errorMessage, NULL);
	if(FAILED(result))
	{
		// If the shader failed to compile it should have writen something to the error message.
		if(errorMessage)
		{
			OutputShaderErrorMessage(errorMessage, hwnd, filename);
		}
		// If there was  nothing in the error message then it simply could not find the shader file itself.
		else
		{
			MessageBox(hwnd, filename, L"Missing Shader File", MB_OK);
		}

		return false;
	}

Set the name of the technique to RefractionTechnique.

	// Get a pointer to the technique inside the shader.
	m_technique = m_effect->GetTechniqueByName("RefractionTechnique");
	if(!m_technique)
	{
		return false;
	}

	// Now setup the layout of the data that goes into the shader.
	// This setup needs to match the VertexType stucture in the ModelClass and in the shader.
	polygonLayout[0].SemanticName = "POSITION";
	polygonLayout[0].SemanticIndex = 0;
	polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT;
	polygonLayout[0].InputSlot = 0;
	polygonLayout[0].AlignedByteOffset = 0;
	polygonLayout[0].InputSlotClass = D3D10_INPUT_PER_VERTEX_DATA;
	polygonLayout[0].InstanceDataStepRate = 0;

	polygonLayout[1].SemanticName = "TEXCOORD";
	polygonLayout[1].SemanticIndex = 0;
	polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT;
	polygonLayout[1].InputSlot = 0;
	polygonLayout[1].AlignedByteOffset = D3D10_APPEND_ALIGNED_ELEMENT;
	polygonLayout[1].InputSlotClass = D3D10_INPUT_PER_VERTEX_DATA;
	polygonLayout[1].InstanceDataStepRate = 0;

	polygonLayout[2].SemanticName = "NORMAL";
	polygonLayout[2].SemanticIndex = 0;
	polygonLayout[2].Format = DXGI_FORMAT_R32G32B32_FLOAT;
	polygonLayout[2].InputSlot = 0;
	polygonLayout[2].AlignedByteOffset = D3D10_APPEND_ALIGNED_ELEMENT;
	polygonLayout[2].InputSlotClass = D3D10_INPUT_PER_VERTEX_DATA;
	polygonLayout[2].InstanceDataStepRate = 0;

	// Get a count of the elements in the layout.
	numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);

	// Get the description of the first pass described in the shader technique.
	m_technique->GetPassByIndex(0)->GetDesc(&passDesc);

	// Create the input layout.
	result = device->CreateInputLayout(polygonLayout, numElements, passDesc.pIAInputSignature, passDesc.IAInputSignatureSize, 
					   &m_layout);
	if(FAILED(result))
	{
		return false;
	}

	// Get pointers to the three matrices inside the shader so we can update them from this class.
	m_worldMatrixPtr = m_effect->GetVariableByName("worldMatrix")->AsMatrix();
	m_viewMatrixPtr = m_effect->GetVariableByName("viewMatrix")->AsMatrix();
	m_projectionMatrixPtr = m_effect->GetVariableByName("projectionMatrix")->AsMatrix();

	// Get pointer to the texture resource inside the shader.
	m_texturePtr = m_effect->GetVariableByName("shaderTexture")->AsShaderResource();

	// Get a pointer to the light direction and color variables inside the shader.
	lightDirectionPtr = m_effect->GetVariableByName("lightDirection")->AsVector();
	ambientColorPtr = m_effect->GetVariableByName("ambientColor")->AsVector();
	diffuseColorPtr = m_effect->GetVariableByName("diffuseColor")->AsVector();

Get a pointer to the clip plane inside the shader.

	// Get a pointer to the clip plane inside the shader.
	clipPlanePtr = m_effect->GetVariableByName("clipPlane")->AsVector();

	return true;
}


void RefractionShaderClass::ShutdownShader()
{

Release the new clip plane pointer inside the ShutdownShader function.

	// Release the clip plane pointer.
	clipPlanePtr = 0;

	// Release the light pointers.
	lightDirectionPtr = 0;
	ambientColorPtr = 0;
	diffuseColorPtr = 0;

	// Release the pointer to the texture in the shader file.
	m_texturePtr = 0;

	// Release the pointers to the matrices inside the shader.
	m_worldMatrixPtr = 0;
	m_viewMatrixPtr = 0;
	m_projectionMatrixPtr = 0;

	// Release the pointer to the shader layout.
	if(m_layout)
	{
		m_layout->Release();
		m_layout = 0;
	}

	// Release the pointer to the shader technique.
	m_technique = 0;

	// Release the pointer to the shader.
	if(m_effect)
	{
		m_effect->Release();
		m_effect = 0;
	}

	return;
}


void RefractionShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename)
{
	char* compileErrors;
	unsigned long bufferSize, i;
	ofstream fout;


	// Get a pointer to the error message text buffer.
	compileErrors = (char*)(errorMessage->GetBufferPointer());

	// Get the length of the message.
	bufferSize = errorMessage->GetBufferSize();

	// Open a file to write the error message to.
	fout.open("shader-error.txt");

	// Write out the error message.
	for(i=0; i<bufferSize; i++)
	{
		fout << compileErrors[i];
	}

	// Close the file.
	fout.close();

	// Release the error message.
	errorMessage->Release();
	errorMessage = 0;

	// Pop a message up on the screen to notify the user to check the text file for compile errors.
	MessageBox(hwnd, L"Error compiling shader.  Check shader-error.txt for message.", shaderFilename, MB_OK);

	return;
}


void RefractionShaderClass::SetShaderParameters(D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix,
						ID3D10ShaderResourceView* texture, D3DXVECTOR3 lightDirection, 
						D3DXVECTOR4 ambientColor, D3DXVECTOR4 diffuseColor, D3DXVECTOR4 clipPlane)
{
	// Set the world matrix variable inside the shader.
	m_worldMatrixPtr->SetMatrix((float*)&worldMatrix);

	// Set the view matrix variable inside the shader.
	m_viewMatrixPtr->SetMatrix((float*)&viewMatrix);

	// Set the projection matrix variable inside the shader.
	m_projectionMatrixPtr->SetMatrix((float*)&projectionMatrix);

	// Bind the texture.
	m_texturePtr->SetResource(texture);

	// Set the direction of the light.
	lightDirectionPtr->SetFloatVector((float*)&lightDirection);

	// Set the ambient color of the light.
	ambientColorPtr->SetFloatVector((float*)&ambientColor);

	// Set the diffuse color of the light.
	diffuseColorPtr->SetFloatVector((float*)&diffuseColor);

Set the clip plane inside the shader.

	// Set the clip plane inside the shader.
	clipPlanePtr->SetFloatVector((float*)&clipPlane);

	return;
}


void RefractionShaderClass::RenderShader(ID3D10Device* device, int indexCount)
{
	D3D10_TECHNIQUE_DESC techniqueDesc;
	unsigned int i;
	

	// Set the input layout.
	device->IASetInputLayout(m_layout);

	// Get the description structure of the technique from inside the shader so it can be used for rendering.
	m_technique->GetDesc(&techniqueDesc);

	// Go through each pass in the technique (should be just one currently) and render the triangles.
	for(i=0; i<techniqueDesc.Passes; ++i)
	{
		m_technique->GetPassByIndex(i)->Apply(0);
		device->DrawIndexed(indexCount, 0, 0);
	}

	return;
}

Graphicsclass.h

The GraphicsClass is where we are going to do the work of setting up and rendering the 3D scene and water.

////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_


/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = true;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d3dclass.h"
#include "cameraclass.h"
#include "modelclass.h"
#include "lightclass.h"
#include "rendertextureclass.h"
#include "lightshaderclass.h"
#include "refractionshaderclass.h"
#include "watershaderclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
	GraphicsClass();
	GraphicsClass(const GraphicsClass&);
	~GraphicsClass();

	bool Initialize(int, int, HWND);
	void Shutdown();
	bool Frame();
	void Render();

private:
	void RenderRefractionToTexture();
	void RenderReflectionToTexture();
	void RenderScene();

private:
	D3DClass* m_D3D;
	CameraClass* m_Camera;

We will need four different models for this tutorial. A model for the ground, a model for the wall, and model for the bath, and a model for the water.

	ModelClass *m_GroundModel, *m_WallModel, *m_BathModel, *m_WaterModel;
	LightClass* m_Light;

We will also need two render to texture objects for the reflection texture and the refraction texture.

	RenderTextureClass *m_RefractionTexture, *m_ReflectionTexture;

The light shader, refraction shader, and water shader are needed for this tutorial.

	LightShaderClass* m_LightShader;
	RefractionShaderClass* m_RefractionShader;
	WaterShaderClass* m_WaterShader;

We also need a couple variables to keep track of the water position and water height.

	float m_waterHeight, m_waterTranslation;
};

#endif

Graphicsclass.cpp

////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "graphicsclass.h"

All of the private pointers for the class are initialized to null inside the constructor.

GraphicsClass::GraphicsClass()
{
	m_D3D = 0;
	m_Camera = 0;
	m_GroundModel = 0;
	m_WallModel = 0;
	m_BathModel = 0;
	m_WaterModel = 0;
	m_Light = 0;
	m_RefractionTexture = 0;
	m_ReflectionTexture = 0;
	m_LightShader = 0;
	m_RefractionShader = 0;
	m_WaterShader = 0;
}


GraphicsClass::GraphicsClass(const GraphicsClass& other)
{
}


GraphicsClass::~GraphicsClass()
{
}


bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
	bool result;

		
	// Create the Direct3D object.
	m_D3D = new D3DClass;
	if(!m_D3D)
	{
		return false;
	}

	// Initialize the Direct3D object.
	result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK);
		return false;
	}

	// Create the camera object.
	m_Camera = new CameraClass;
	if(!m_Camera)
	{
		return false;
	}

Create and initialize the ground, wall, bath, and water model here.

	// Create the ground model object.
	m_GroundModel = new ModelClass;
	if(!m_GroundModel)
	{
		return false;
	}

	// Initialize the ground model object.
	result = m_GroundModel->Initialize(m_D3D->GetDevice(), L"../Engine/data/ground01.dds", "../Engine/data/ground.txt");
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the ground model object.", L"Error", MB_OK);
		return false;
	}

	// Create the wall model object.
	m_WallModel = new ModelClass;
	if(!m_WallModel)
	{
		return false;
	}

	// Initialize the wall model object.
	result = m_WallModel->Initialize(m_D3D->GetDevice(), L"../Engine/data/wall01.dds", "../Engine/data/wall.txt");
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the wall model object.", L"Error", MB_OK);
		return false;
	}

	// Create the bath model object.
	m_BathModel = new ModelClass;
	if(!m_BathModel)
	{
		return false;
	}

	// Initialize the bath model object.
	result = m_BathModel->Initialize(m_D3D->GetDevice(), L"../Engine/data/marble01.dds", "../Engine/data/bath.txt");
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the bath model object.", L"Error", MB_OK);
		return false;
	}

	// Create the water model object.
	m_WaterModel = new ModelClass;
	if(!m_WaterModel)
	{
		return false;
	}

	// Initialize the water model object.
	result = m_WaterModel->Initialize(m_D3D->GetDevice(), L"../Engine/data/water01.dds", "../Engine/data/water.txt");
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the water model object.", L"Error", MB_OK);
		return false;
	}

	// Create the light object.
	m_Light = new LightClass;
	if(!m_Light)
	{
		return false;
	}

	// Initialize the light object.
	m_Light->SetAmbientColor(0.15f, 0.15f, 0.15f, 1.0f);
	m_Light->SetDiffuseColor(1.0f, 1.0f, 1.0f, 1.0f);
	m_Light->SetDirection(0.0f, -1.0f, 0.5f);

Setup the two render to textures for the scene reflection and refraction.

	// Create the refraction render to texture object.
	m_RefractionTexture = new RenderTextureClass;
	if(!m_RefractionTexture)
	{
		return false;
	}

	// Initialize the refraction render to texture object.
	result = m_RefractionTexture->Initialize(m_D3D->GetDevice(), screenWidth, screenHeight);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the refraction render to texture object.", L"Error", MB_OK);
		return false;
	}

	// Create the reflection render to texture object.
	m_ReflectionTexture = new RenderTextureClass;
	if(!m_ReflectionTexture)
	{
		return false;
	}

	// Initialize the reflection render to texture object.
	result = m_ReflectionTexture->Initialize(m_D3D->GetDevice(), screenWidth, screenHeight);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the reflection render to texture object.", L"Error", MB_OK);
		return false;
	}

Setup the three shaders that we will be using.

	// Create the light shader object.
	m_LightShader = new LightShaderClass;
	if(!m_LightShader)
	{
		return false;
	}

	// Initialize the light shader object.
	result = m_LightShader->Initialize(m_D3D->GetDevice(), hwnd);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the light shader object.", L"Error", MB_OK);
		return false;
	}

	// Create the refraction shader object.
	m_RefractionShader = new RefractionShaderClass;
	if(!m_RefractionShader)
	{
		return false;
	}

	// Initialize the refraction shader object.
	result = m_RefractionShader->Initialize(m_D3D->GetDevice(), hwnd);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the refraction shader object.", L"Error", MB_OK);
		return false;
	}

	// Create the water shader object.
	m_WaterShader = new WaterShaderClass;
	if(!m_WaterShader)
	{
		return false;
	}

	// Initialize the water shader object.
	result = m_WaterShader->Initialize(m_D3D->GetDevice(), hwnd);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the water shader object.", L"Error", MB_OK);
		return false;
	}

Setup the height of the water plane and initialize the position of the water translation.

	// Set the height of the water.
	m_waterHeight = 2.75f;

	// Initialize the position of the water.
	m_waterTranslation = 0.0f;

	return true;
}


void GraphicsClass::Shutdown()
{
	// Release the water shader object.
	if(m_WaterShader)
	{
		m_WaterShader->Shutdown();
		delete m_WaterShader;
		m_WaterShader = 0;
	}

	// Release the refraction shader object.
	if(m_RefractionShader)
	{
		m_RefractionShader->Shutdown();
		delete m_RefractionShader;
		m_RefractionShader = 0;
	}

	// Release the light shader object.
	if(m_LightShader)
	{
		m_LightShader->Shutdown();
		delete m_LightShader;
		m_LightShader = 0;
	}

	// Release the reflection render to texture object.
	if(m_ReflectionTexture)
	{
		m_ReflectionTexture->Shutdown();
		delete m_ReflectionTexture;
		m_ReflectionTexture = 0;
	}

	// Release the refraction render to texture object.
	if(m_RefractionTexture)
	{
		m_RefractionTexture->Shutdown();
		delete m_RefractionTexture;
		m_RefractionTexture = 0;
	}

	// Release the light object.
	if(m_Light)
	{
		delete m_Light;
		m_Light = 0;
	}

	// Release the water model object.
	if(m_WaterModel)
	{
		m_WaterModel->Shutdown();
		delete m_WaterModel;
		m_WaterModel = 0;
	}

	// Release the bath model object.
	if(m_BathModel)
	{
		m_BathModel->Shutdown();
		delete m_BathModel;
		m_BathModel = 0;
	}

	// Release the wall model object.
	if(m_WallModel)
	{
		m_WallModel->Shutdown();
		delete m_WallModel;
		m_WallModel = 0;
	}

	// Release the ground model object.
	if(m_GroundModel)
	{
		m_GroundModel->Shutdown();
		delete m_GroundModel;
		m_GroundModel = 0;
	}

	// Release the camera object.
	if(m_Camera)
	{
		delete m_Camera;
		m_Camera = 0;
	}

	// Release the Direct3D object.
	if(m_D3D)
	{
		m_D3D->Shutdown();
		delete m_D3D;
		m_D3D = 0;
	}

	return;
}


bool GraphicsClass::Frame()
{

Each frame update the position of the water to simulation motion.

	// Update the position of the water to simulate motion.
	m_waterTranslation += 0.001f;
	if(m_waterTranslation > 1.0f)
	{
		m_waterTranslation -= 1.0f;
	}

	// Set the position and rotation of the camera.
	m_Camera->SetPosition(-10.0f, 6.0f, -10.0f);
	m_Camera->SetRotation(0.0f, 45.0f, 0.0f);

	return true;
}

First we render the refraction of the scene to a texture. Then we render the reflection of the scene to a texture. And then finally we render the scene normally and use the reflection and refraction texture to create the water effect. Remember if you want to reduce the cost of rendering to texture all the time you can instead only do so every 15-30 frames and reuse the previous textures.

void GraphicsClass::Render()
{
	// Render the refraction of the scene to a texture.
	RenderRefractionToTexture();

	// Render the reflection of the scene to a texture.
	RenderReflectionToTexture();

	// Render the scene as normal to the back buffer.
	RenderScene();

	return;
}

The RenderRefractionToTexture function renders the refraction of the scene to a render to texture. As the refraction of the scene is everything beneath the water and only the bath model is visible underneath the model we can be selective and just render the bath model.

void GraphicsClass::RenderRefractionToTexture()
{
	D3DXVECTOR4 clipPlane;
	D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix;

Now as expected we use a clip plane to clip everything above the water plane and only render what is beneath it. However you will notice that I am translating the clip plane up just slightly above the water height. The reason being is that this technique has edge issues and to reduce the visibility of the dark edges that appear once translated by the normal map sampling location we just force the sampling to occur in a range where there is more color data available (by moving the clip plane slightly up). When you run this program try just using the regular water height so you can see the artifacting that does occur normally.

	// Setup a clipping plane based on the height of the water to clip everything above it.
	clipPlane = D3DXVECTOR4(0.0f, -1.0f, 0.0f, m_waterHeight + 0.1f);

	// Set the render target to be the refraction render to texture.
	m_RefractionTexture->SetRenderTarget(m_D3D->GetDevice(), m_D3D->GetDepthStencilView());

	// Clear the refraction render to texture.
	m_RefractionTexture->ClearRenderTarget(m_D3D->GetDevice(), m_D3D->GetDepthStencilView(), 0.0f, 0.0f, 0.0f, 1.0f);

	// Generate the view matrix based on the camera's position.
	m_Camera->Render();

	// Get the world, view, and projection matrices from the camera and d3d objects.
	m_D3D->GetWorldMatrix(worldMatrix);
	m_Camera->GetViewMatrix(viewMatrix);
	m_D3D->GetProjectionMatrix(projectionMatrix);

	// Translate to where the bath model will be rendered.
	D3DXMatrixTranslation(&worldMatrix, 0.0f, 2.0f, 0.0f);

	// Put the bath model vertex and index buffers on the graphics pipeline to prepare them for drawing.
	m_BathModel->Render(m_D3D->GetDevice());

	// Render the bath model using the light shader.
	m_RefractionShader->Render(m_D3D->GetDevice(), m_BathModel->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix,
				   m_BathModel->GetTexture(), m_Light->GetDirection(), m_Light->GetAmbientColor(), 
				   m_Light->GetDiffuseColor(), clipPlane);

	// Reset the render target back to the original back buffer and not the render to texture anymore.
	m_D3D->SetBackBufferRenderTarget();

	return;
}

RenderReflectionToTexture renders the reflection of the scene to a render to texture. As the reflection of the scene is everything above the water we only need to render the wall model since it is the only thing that can reflect in the water.

void GraphicsClass::RenderReflectionToTexture()
{
	D3DXMATRIX reflectionViewMatrix, worldMatrix, projectionMatrix;


	// Set the render target to be the reflection render to texture.
	m_ReflectionTexture->SetRenderTarget(m_D3D->GetDevice(), m_D3D->GetDepthStencilView());

	// Clear the reflection render to texture.
	m_ReflectionTexture->ClearRenderTarget(m_D3D->GetDevice(), m_D3D->GetDepthStencilView(), 0.0f, 0.0f, 0.0f, 1.0f);

The reflection matrix is setup using the water height.

	// Use the camera to render the reflection and create a reflection view matrix.
	m_Camera->RenderReflection(m_waterHeight);

	// Get the camera reflection view matrix instead of the normal view matrix.
	reflectionViewMatrix = m_Camera->GetReflectionViewMatrix();

	// Get the world and projection matrices from the d3d object.
	m_D3D->GetWorldMatrix(worldMatrix);
	m_D3D->GetProjectionMatrix(projectionMatrix);

	// Translate to where the wall model will be rendered.
	D3DXMatrixTranslation(&worldMatrix, 0.0f, 6.0f, 8.0f);

	// Put the wall model vertex and index buffers on the graphics pipeline to prepare them for drawing.
	m_WallModel->Render(m_D3D->GetDevice());

	// Render the wall model using the light shader and the reflection view matrix.
	m_LightShader->Render(m_D3D->GetDevice(), m_WallModel->GetIndexCount(), worldMatrix, reflectionViewMatrix, projectionMatrix,
			      m_WallModel->GetTexture(), m_Light->GetDirection(), m_Light->GetAmbientColor(), 
			      m_Light->GetDiffuseColor());

	// Reset the render target back to the original back buffer and not the render to texture anymore.
	m_D3D->SetBackBufferRenderTarget();

	return;
}

The RenderScene function is where we render the scene to the back buffer and use the reflection and refraction render to textures to render the water.

void GraphicsClass::RenderScene()
{
	D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix, reflectionMatrix;


	// Clear the buffers to begin the scene.
	m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);

	// Generate the view matrix based on the camera's position.
	m_Camera->Render();

	// Get the world, view, and projection matrices from the camera and d3d objects.
	m_D3D->GetWorldMatrix(worldMatrix);
	m_Camera->GetViewMatrix(viewMatrix);
	m_D3D->GetProjectionMatrix(projectionMatrix);

Render the ground model first.

	// Translate to where the ground model will be rendered.
	D3DXMatrixTranslation(&worldMatrix, 0.0f, 1.0f, 0.0f);

	// Put the ground model vertex and index buffers on the graphics pipeline to prepare them for drawing.
	m_GroundModel->Render(m_D3D->GetDevice());

	// Render the ground model using the light shader.
	m_LightShader->Render(m_D3D->GetDevice(), m_GroundModel->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix,
			      m_GroundModel->GetTexture(), m_Light->GetDirection(), m_Light->GetAmbientColor(), 
			      m_Light->GetDiffuseColor());

	// Reset the world matrix.
	m_D3D->GetWorldMatrix(worldMatrix);

Render the wall model next.

	// Translate to where the wall model will be rendered.
	D3DXMatrixTranslation(&worldMatrix, 0.0f, 6.0f, 8.0f);

	// Put the wall model vertex and index buffers on the graphics pipeline to prepare them for drawing.
	m_WallModel->Render(m_D3D->GetDevice());

	// Render the wall model using the light shader.
	m_LightShader->Render(m_D3D->GetDevice(), m_WallModel->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix,
			      m_WallModel->GetTexture(), m_Light->GetDirection(), m_Light->GetAmbientColor(), 
			      m_Light->GetDiffuseColor());

	// Reset the world matrix.
	m_D3D->GetWorldMatrix(worldMatrix);

Render the bath model.

	// Translate to where the bath model will be rendered.
	D3DXMatrixTranslation(&worldMatrix, 0.0f, 2.0f, 0.0f);

	// Put the bath model vertex and index buffers on the graphics pipeline to prepare them for drawing.
	m_BathModel->Render(m_D3D->GetDevice());

	// Render the bath model using the light shader.
	m_LightShader->Render(m_D3D->GetDevice(), m_BathModel->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix,
			      m_BathModel->GetTexture(), m_Light->GetDirection(), m_Light->GetAmbientColor(), 
			      m_Light->GetDiffuseColor());
	
	// Reset the world matrix.
	m_D3D->GetWorldMatrix(worldMatrix);

Finally render the water model using the reflection matrix, render to textures, normal map, translation value, and the reflectRefractScale (0.01f).

	// Get the camera reflection view matrix.
	reflectionMatrix = m_Camera->GetReflectionViewMatrix();

	// Translate to where the water model will be rendered.
	D3DXMatrixTranslation(&worldMatrix, 0.0f, m_waterHeight, 0.0f); 

	// Put the water model vertex and index buffers on the graphics pipeline to prepare them for drawing.
	m_WaterModel->Render(m_D3D->GetDevice());

	// Render the water model using the water shader.
	m_WaterShader->Render(m_D3D->GetDevice(), m_WaterModel->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, 
			      reflectionMatrix, m_ReflectionTexture->GetShaderResourceView(), 
			      m_RefractionTexture->GetShaderResourceView(), m_WaterModel->GetTexture(), m_waterTranslation, 0.01f);
	
	// Present the rendered scene to the screen.
	m_D3D->EndScene();

	return;
}

Summary

The reflective and refractive water technique paired with translated normal maps provides a very realistic water effect that can be extended into many different related effects (glass, ice, etc.).


To Do Exercises

1. Recompile and run the program. You should get animated reflective/refractive water.

2. Modify how the reflection and refraction are coming by changing the 0.6f value in: color = lerp(reflectionColor, refractionColor, 0.6f)

3. Modify reflectRefractScale value (its the last value sent into the m_WaterShader->Render function).

4. Create your own normal map and see the difference it makes in the water effect.

5. Change the clip plane to be the exact height of the water in the RenderRefractionToTexture() function.


Source Code

Visual Studio 2008 Project: dx10tut29.zip

Source Only: dx10src29.zip

Executable Only: dx10exe29.zip

Back to Tutorial Index