Google
 

Thursday, June 28, 2007

Engine Design - Terrain (Bump/Normal Mapping)

Well, looks like my post on fire is on hold for now. So here is my bump/normal mapped terrain shaders and the modifications I made to my existing terrain object.

Changes to the Terrain Class
First thing I had to do was figure out how to add tangents to the terrain Vertex Element, so I put a post on the XNA Creators Club (no laughing at my game tag, I have been trying to change it but to no avail..) and Shawn Hargreaves replied with the solution. I think I may have applied it too literally as there is another post on there pointing to an article on ziggyware describing how to generate tangents and it is quite a lump of code! Also, I have tested my stuff with the tangent elements removed...and it still seems to work, odd. But what the hell I am new to all this and I got the bump mapping working and that is all I am bothered about. Though, if you can point me in the direction for creating the tangent data correctly then please post a comment :)

So, the code changes. I added a tangent vector3 to the Vertex Element.

       protected struct VertexMultitextured
       {
           public Vector3 Position;
           public Vector3 Normal;            
           public Vector4 TextureCoordinate;
           public Vector4 TexWeights;
           public Vector3 Tangent;

           public static int SizeInBytes = (3 + 3 + 3 + 4 + 4) * 4;
           public static VertexElement[] VertexElements = new VertexElement[]
            {
                new VertexElement( 0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0 ),
                new VertexElement( 0, sizeof(float) * 3, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal, 0 ),
                new VertexElement( 0, sizeof(float) * 6, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0 ),
                new VertexElement( 0, sizeof(float) * 10, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 1 ),
                new VertexElement( 0, sizeof(float) * 14, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Tangent, 0 ),
            };
       }



Once this was added I had to calcualte the tangent data per vertex. So in my BuildTerrain method of the class I added a line to the loop that calulates the vertex normal. (second loop in the method)

           for (int x = 1; x < myWidth - 1; x++)
           {
               for (int y = 1; y < myHeight - 1; y++)
               {
                   Vector3 normX = new Vector3((myVertices[x - 1 + y * myWidth].Position.Y - myVertices[x + 1 + y * myWidth].Position.Y) / 2, 0, 1);
                   Vector3 normY = new Vector3(0, 1, (myVertices[x + (y - 1) * myWidth].Position.Y - myVertices[x + (y + 1) * myWidth].Position.Y) / 2);
                   myVertices[x + y * myWidth].Normal = normX + normY;
                   myVertices[x + y * myWidth].Normal.Normalize();

                   myVertices[x + y * myWidth].Tangent = myVertices[x - 1 + y * myWidth].Position - myVertices[x + 1 + y * myWidth].Position;                    
               }
           }



Now that was all set up I needed to get the normals passed to the class and store them. So I have added these fields and properties.

   private bool bumpOn;
   private string[] myBumpMapAssets;

   public bool BumpOn
   {
      get { return bumpOn; }
      set { bumpOn = value; }
   }



Now I altered the constructor to handle the new texture feed.

       public RCTerrain(string[] textureAssets,string[] bumpAssets,string heightAsset,string name):base(name)
       {          
           myHeightAsset = heightAsset;

           myAssets = new string[textureAssets.Length];
           for(int ass=0;ass<textureAssets.Length;ass++)
               myAssets[ass] = textureAssets[ass];

           myBumpMapAssets = new string[bumpAssets.Length];
           for (int ass = 0; ass < bumpAssets.Length; ass++)
               myBumpMapAssets[ass] = bumpAssets[ass];

           bumpOn = true;
       }



And added this loop to the LoadGraphicsContent method

           bumpMaps = new Texture2D[myBumpMapAssets.Length];
           for (int ass = 0; ass < myBumpMapAssets.Length; ass++)
               bumpMaps[ass] = myLoader.Load<Texture2D>(myBumpMapAssets[ass]);



Will all that set up I now altered the Render method to use the new textures

           if (effect.Parameters["bumpOn"] != null)
               effect.Parameters["bumpOn"].SetValue(bumpOn);

           if (effect.Parameters["tileSizeMod"] != null)
               effect.Parameters["tileSizeMod"].SetValue(1f);

           if (effect.Parameters["BumpMap0"] != null)
               effect.Parameters["BumpMap0"].SetValue(bumpMaps[0]);
           if (effect.Parameters["BumpMap1"] != null)
               effect.Parameters["BumpMap1"].SetValue(bumpMaps[1]);
           if (effect.Parameters["BumpMap2"] != null)
               effect.Parameters["BumpMap2"].SetValue(bumpMaps[2]);
           if (effect.Parameters["BumpMap3"] != null)
               effect.Parameters["BumpMap3"].SetValue(bumpMaps[3]);



Shaders
Now to the shaders, the first shader was Reimers origional terrain shader the only change I have really made is to add the bump/normal mapping. The second shader is mine, I have put a few more comments in mine so it should be a bit more readable.

Reimers Shader modified by me

//////////////////////////////////////////////////////////////////
//                                                                //
//    This terrain shader was origionaly written by                //
//  Reimer (http://www.riemers.net/)                            //
//  I have modified it so it now had a bump map effect            //
//                                                                //
//  26/06/2007 C.Humphrey (http://randomchaosuk.blogspot.com/)    //
//                                                                //
//////////////////////////////////////////////////////////////////

struct MultiTexVertexToPixel
{
   float4 Position         : POSITION;    
   float4 Color            : COLOR0;
   float3 Normal            : TEXCOORD0;
   float4 TextureCoords    : TEXCOORD1;
   float4 LightDirection    : TEXCOORD2;
   float4 TextureWeights    : TEXCOORD3;
   float    Depth            : TEXCOORD4;
   float shade : TEXCOORD5;
   float3 lView : TEXCOORD6;
};


struct MultiTexPixelToFrame
{
   float4 Color : COLOR0;
};

//------- Constants --------
float4x4 World : World;
float4x4 wvp : WorldViewProjection;
float3 LightPosition : LightDirection;
float Ambient : Ambient;
float3 EyePosition : CAMERAPOSITION;

Texture SandTexture;
sampler SandTextureSampler = sampler_state
{
   texture = <SandTexture> ;
   magfilter = LINEAR;
   minfilter = LINEAR;
   mipfilter=LINEAR;
   AddressU = mirror;
   AddressV = mirror;
};

Texture GrassTexture;
sampler GrassTextureSampler = sampler_state
{
   texture = <GrassTexture> ;
   magfilter = LINEAR;
   minfilter = LINEAR;
   mipfilter=LINEAR;
   AddressU = mirror;
   AddressV = mirror;
};

Texture RockTexture;
sampler RockTextureSampler = sampler_state
{
   texture = <RockTexture> ;
   magfilter = LINEAR;
   minfilter = LINEAR;
   mipfilter=LINEAR;
   AddressU = mirror;
   AddressV = mirror;
};

Texture SnowTexture;
sampler SnowTextureSampler = sampler_state
{
   texture = <SnowTexture>;
   magfilter = LINEAR;
   minfilter = LINEAR;
   mipfilter=LINEAR;
   AddressU = mirror;
   AddressV = mirror;
};

texture BumpMap0;
sampler BumpMap0Sampler = sampler_state
{
   Texture = <BumpMap0>;
   MinFilter = Linear;
   MagFilter = Linear;
   MipFilter = Linear;
   AddressU = mirror;
   AddressV = mirror;
};
texture BumpMap1;
sampler BumpMap1Sampler = sampler_state
{
   Texture = <BumpMap1>;
   MinFilter = Linear;
   MagFilter = Linear;
   MipFilter = Linear;
   AddressU = mirror;
   AddressV = mirror;
};

texture BumpMap2;
sampler BumpMap2Sampler = sampler_state
{
   Texture = <BumpMap2>;
   MinFilter = Linear;
   MagFilter = Linear;
   MipFilter = Linear;
   AddressU = mirror;
   AddressV = mirror;
};

texture BumpMap3;
sampler BumpMap3Sampler = sampler_state
{
   Texture = <BumpMap3>;
   MinFilter = Linear;
   MagFilter = Linear;
   MipFilter = Linear;
   AddressU = mirror;
   AddressV = mirror;
};

MultiTexVertexToPixel MultiTexturedVS( float4 inPos : POSITION, float3 inNormal: NORMAL, float4 inTexCoords: TEXCOORD0, float4 inTexWeights: TEXCOORD1, float3 Tangent : TANGENT)
{
   MultiTexVertexToPixel Output = (MultiTexVertexToPixel)0;
   
   Output.Position = mul(inPos, wvp);
   Output.Normal = mul(normalize(inNormal), World);
   Output.TextureCoords = inTexCoords;
   Output.LightDirection.xyz = LightPosition;
   Output.LightDirection.w = 1;
   Output.TextureWeights = inTexWeights;
   Output.Depth = Output.Position.y;
   Output.shade = saturate(saturate(dot(Output.Normal, normalize(Output.LightDirection))) + Ambient);    
   
   float3x3 worldToTangentSpace;
   worldToTangentSpace[0] = mul(Tangent,World);
   worldToTangentSpace[1] = mul(cross(Tangent,inNormal),World);
   worldToTangentSpace[2] = mul(inNormal,World);
   
   Output.lView = mul(worldToTangentSpace,EyePosition - Output.Position);    
   
   return Output;    
}

MultiTexPixelToFrame MultiTexturedPS(MultiTexVertexToPixel PSIn)
{
   MultiTexPixelToFrame Output = (MultiTexPixelToFrame)0;
 
   float blendDistance = 30;
   float blendWidth = 50;
   float blendFactor = clamp((PSIn.Depth-blendDistance)/blendWidth, 0, 1);

   float4 farColor = 0;
   farColor = tex2D(SandTextureSampler, PSIn.TextureCoords)*PSIn.TextureWeights.x;
   farColor += tex2D(GrassTextureSampler, PSIn.TextureCoords)*PSIn.TextureWeights.y;
   farColor += tex2D(RockTextureSampler, PSIn.TextureCoords)*PSIn.TextureWeights.z;
   farColor += tex2D(SnowTextureSampler, PSIn.TextureCoords)*PSIn.TextureWeights.w;

   float4 nearColor;
   float2 nearTextureCoords = PSIn.TextureCoords*3;
   nearColor = tex2D(SandTextureSampler, nearTextureCoords)*PSIn.TextureWeights.x;
   nearColor += tex2D(GrassTextureSampler, nearTextureCoords)*PSIn.TextureWeights.y;
   nearColor += tex2D(RockTextureSampler, nearTextureCoords)*PSIn.TextureWeights.z;
   nearColor += tex2D(SnowTextureSampler, nearTextureCoords)*PSIn.TextureWeights.w;
 
   float lightingFactor = saturate(saturate(dot(PSIn.Normal, normalize(PSIn.LightDirection))) + Ambient);      
   
   float4 LightDir = normalize(PSIn.LightDirection);
   float3 ViewDir = normalize(PSIn.lView);

   float3 Normal;
   Normal = tex2D(BumpMap0Sampler,nearTextureCoords)*PSIn.TextureWeights.x;
   Normal += tex2D(BumpMap1Sampler,nearTextureCoords)*PSIn.TextureWeights.y;
   Normal += tex2D(BumpMap2Sampler,nearTextureCoords)*PSIn.TextureWeights.z;
   Normal += tex2D(BumpMap3Sampler,nearTextureCoords)*PSIn.TextureWeights.w;
   
   Normal = 2 * Normal - 1.0;    
   float4 Color = nearColor + farColor;
   
   float Diffuse = saturate(dot(Normal,LightDir));    
   float Reflect = normalize(2 * Diffuse * Normal - LightDir);    
   float Specular = min(pow(saturate(dot(Reflect,ViewDir)),3),Color.w);
   
   Output.Color = (0.2 * ((farColor*blendFactor + nearColor*(1-blendFactor))*4) * (Diffuse + Specular)) * (PSIn.shade*3);    
   
   return Output;
}

technique MultiTextured
{
   pass Pass0
   {
       VertexShader = compile vs_2_0 MultiTexturedVS();
       PixelShader = compile ps_2_0 MultiTexturedPS();
   }    
}



My Shader

//////////////////////////////////////////////////////////////////////////////
//                                                                            //
//    NemoKradBumpTerrain.fx Terrain shader by C.Humphrey 02/05/2007          //
//                                                                            //
//    This shader is based on terrain shaders by Riemer and Frank Luna.       //
//  Riemer: http://www.riemers.net                                            //
//  Frank Luna: http://www.moon-labs.com                                    //
//                                                                            //
//    http://randomchaos.co.uk                                                //
//    http://randomchaosuk.blogspot.com                                        //
//                                                                            //
// 25/06/2007 - Adde bumpmap functionlaity                                    //
//                                                                            //
//////////////////////////////////////////////////////////////////////////////

float4x4 wvp : WorldViewProjection;
float4x4 world : World;
float3 LightPosition : LightDirection;
float3 EyePosition : CAMERAPOSITION;

// Do we want to use bump map feature?
bool bumpOn = true;

// Texture size moifier 1 = no change.
float tileSizeMod = 1;

// Terrain Textures.
texture  LayerMap0;
texture  LayerMap1;
texture  LayerMap2;
texture  LayerMap3;

// Terrain Normals for above texture.
texture BumpMap0;
texture BumpMap1;
texture BumpMap2;
texture BumpMap3;

// Normal samplers
sampler BumpMap0Sampler = sampler_state
{
   Texture = <BumpMap0>;
   MinFilter = Linear;
   MagFilter = Linear;
   MipFilter = Linear;
   AddressU = mirror;
   AddressV = mirror;
};

sampler BumpMap1Sampler = sampler_state
{
   Texture = <BumpMap1>;
   MinFilter = Linear;
   MagFilter = Linear;
   MipFilter = Linear;
   AddressU = mirror;
   AddressV = mirror;
};

sampler BumpMap2Sampler = sampler_state
{
   Texture = <BumpMap2>;
   MinFilter = Linear;
   MagFilter = Linear;
   MipFilter = Linear;
   AddressU = mirror;
   AddressV = mirror;
};

sampler BumpMap3Sampler = sampler_state
{
   Texture = <BumpMap3>;
   MinFilter = Linear;
   MagFilter = Linear;
   MipFilter = Linear;
   AddressU = mirror;
   AddressV = mirror;
};

// Texture Samplers
sampler LayerMap0Sampler = sampler_state
{
   Texture   = <LayerMap0>;
   MinFilter = LINEAR;
   MagFilter = LINEAR;
   MipFilter = LINEAR;
   AddressU  = WRAP;
   AddressV  = WRAP;
};

sampler LayerMap1Sampler = sampler_state
{
   Texture   = <LayerMap1>;
   MinFilter = LINEAR;
   MagFilter = LINEAR;
   MipFilter = LINEAR;
   AddressU  = WRAP;
   AddressV  = WRAP;
};

sampler LayerMap2Sampler = sampler_state
{
   Texture   = <LayerMap2>;
   MinFilter = LINEAR;
   MagFilter = LINEAR;
   MipFilter = LINEAR;
   AddressU  = WRAP;
   AddressV  = WRAP;
};
sampler LayerMap3Sampler = sampler_state
{
   Texture   = <LayerMap3>;
   MinFilter = LINEAR;
   MagFilter = LINEAR;
   MipFilter = LINEAR;
   AddressU  = WRAP;
   AddressV  = WRAP;
};

// Vertex Shader input structure.
struct VS_INPUT
{
   float4 posL         : POSITION0;
   float3 normalL      : NORMAL0;
   float4 tiledTexC    : TEXCOORD0;
   float4 TextureWeights : TEXCOORD1;
   float3 Tangent : TANGENT;
};

// Vertex Shader output structure/Pixel Shaer input structure
struct VS_OUTPUT
{
   float4 posH         : POSITION0;
   float  shade        : TEXCOORD0;
   float4 tiledTexC    : TEXCOORD1;  
   float4 TextureWeights : TEXCOORD2;    
   float4 Light : TEXCOORD3;
   float3 lView : TEXCOORD4;
};

// Vetex Shader
VS_OUTPUT BumpVS(VS_INPUT input)
{
   // Clean the output structure.
   VS_OUTPUT Out = (VS_OUTPUT)0;
       
   // Calculate tangent space.
   float3x3 worldToTangentSpace;
   worldToTangentSpace[0] = mul(input.Tangent,world);
   worldToTangentSpace[1] = mul(cross(input.Tangent,input.normalL),world);
   worldToTangentSpace[2] = mul(input.normalL,world);    
   
   // Get world pos for texture and normal.
   float4 PosWorld = mul(input.posL,world);    
   
   // Get light position.
   Out.Light.xyz = LightPosition;
   Out.Light.w = 1;
   
   // Set position for pixel shader
   Out.posH  = mul(input.posL, wvp);        
   
   // Set lighting.
   Out.shade = saturate(saturate(dot(input.normalL, normalize(LightPosition))));    
   
   // Set view direction for normals.
   Out.lView = mul(worldToTangentSpace,EyePosition - Out.posH);    
   
   // Set tile TexCoord.
   Out.tiledTexC = input.tiledTexC * tileSizeMod;
       
   // Set Texture weight.
   Out.TextureWeights = input.TextureWeights;    
   
   return Out;
}
// Output to screen.
struct PixelToFrame
{
   float4 Color : COLOR0;
};

// Pixel shader.
PixelToFrame BumpPS(VS_OUTPUT input) : COLOR
{
   // Clean output structure.
   PixelToFrame Out = (PixelToFrame)0;    
   
   // Get pixel color.
   float4 Col = tex2D(LayerMap0Sampler, input.tiledTexC)*input.TextureWeights.x;
   Col += tex2D(LayerMap1Sampler, input.tiledTexC)*input.TextureWeights.y;
   Col += tex2D(LayerMap2Sampler, input.tiledTexC)*input.TextureWeights.z;
   Col += tex2D(LayerMap3Sampler, input.tiledTexC)*input.TextureWeights.w;
   
   // Set light directon amd view direction.
   float4 LightDir = normalize(input.Light);
   float3 ViewDir = normalize(input.lView);
   
   // Get prominent normal.    
   float2 nearTextureCoords = input.tiledTexC*3;
   float3 Normal;
   Normal = tex2D(BumpMap0Sampler,nearTextureCoords)*input.TextureWeights.x;
   Normal += tex2D(BumpMap1Sampler,nearTextureCoords)*input.TextureWeights.y;
   Normal += tex2D(BumpMap2Sampler,nearTextureCoords)*input.TextureWeights.z;
   Normal += tex2D(BumpMap3Sampler,nearTextureCoords)*input.TextureWeights.w;    
   Normal = 2 * Normal - 1.0;    
       
   // Set diffuse, reflection and specular effect for Normal.
   float Diffuse = saturate(dot(Normal,LightDir));    
   float Reflect = normalize(2 * Diffuse * Normal - LightDir);
   float Specular = min(pow(saturate(dot(Reflect,ViewDir)),3),Col.w);    
   
   float4 final;
   
   // Do color calculation depending if bump feature is on or off.
   if(bumpOn)
       final = (0.2 * Col * (Diffuse + Specular)) * (input.shade * 12);
   else
       final = Col * input.shade;

   Out.Color = final;
   
   return Out;
}

technique Terrain_MultiTex
{
   pass P0
   {
       vertexShader = compile vs_2_0 BumpVS();
       pixelShader  = compile ps_2_0 BumpPS();
   }    
}



Screen Shots
Right here are a number of screen shots from different angles so you can see how the two shaders render, I have also put up my FPS counter so you can see the performance.
In each set I do my best to get the same camera position. Oh and I don't want you to think that Reimers shader is the worst of the two, my shader is quicker because it does the job in a much cruder way. I guess you have to go for speed or beuty, unless your machine is supper fast.

Riemers with Bump/Normal mapping Pic 1


My shader Pic 1


Riemers with Bump/Normal mapping Pic2


My shader Pic 2


Riemers with Bump/Normal mapping Pic 3


My shader Pic 3


Riemers with Bump/Normal mapping Pic 4


My shader Pic 4


Terrain Up Close
Here are images of the terrain up close. I think you will agree using Remiers terrain render is better looking, well especialy now it has bump mapping :)

Riemers with Bump/Normal mapping Sand


My shader Sand


Riemers with Bump/Normal mapping Grass


My shader Grass


Riemers with Bump/Normal mapping Stone


My shader Stone


Riemers with Bump/Normal mapping Snow


My shader Snow



Textures and Normals
Here are the textures used in my examples, they are not the origional DDS files as this blog wont host them, all you have to do is go to Reimers turorial, and you can down load the textures there, all you have to do then is use PhotoShop and NVIDIAs Normal tool to generate the normal. If I have time I will put a zip up on my server and link to it from here.

Sand & Normal


Grass & Normal


Stone & Normal


Snow & Normal


By all means please, if you optomize these shaders ot even the terrain class I would love to know what you did. I am planning on doing terrain culling in the future and if I ever get time to get my head into it ROAM terrain.

PHEW! That has to be my biggest post!

Tuesday, June 26, 2007

New Engine Component - Terrain With Bump Map Effect

I know, I should be writing up the Fire class and shader, but I have been playing around with bump maps and normals and thought it would be a good idea to apply this to the terrain. I managed to do it using the shader from Reimers tutorial and modified it to render bump maps per terrain layer. Any way enough chat, here are some examples..

Looking down from a mountain..


Looking down the bank..
(different soil texture)


Looking back up the mountain..
(different soil texture)


First run before I started to normal my textures


This was taken to show how this shader hits performance..


My shader, crude, but quicker..


My shader with my water..


I will post the shader once I have a nice clean shader, lots of stuff in there at the moment so will tidy up before posting it. I will also try and put this feature into my own terrain shader and see if it runs any faster....

Thursday, June 14, 2007

Engine Design - Water (Ocean)

Now, once I had a terrain object I thought it would be great to get a water model to go with it. I thought I would just use the terrain class and modify it so that it was flat. I then needed a shader to do my water effect, and being new to all this 3D stuff and HLSL, did not have the skills to write my own, so I had a mooch on line and found NVIDIA's Ocean shader, with a few tweaks I got it working with my engine.

I am going to give you the whole class at the end along with the modified shader. What I will do now is show the effects of various parameter setting's.

Basic Water
Here is a basic implementation of the class

As you can see, in the picture above, I am reflecting the surrounding skybox and the bumpmap is also applied. The wave amplitude and frequency are set to 0 so you cant see any waves in this example.

           RCWater water = new RCWater("Content/Textures/SkyBox/HazyMind/cubeMap", "Content/Textures/Terrain/waves2", "water");            
           water.SetShader("WAT");
           water.Width = 128;
           water.Height = 128;
           water.WaveFrequency = .0f;
           water.WaveAmplitude = 0f;
           water.DeepWaterColor = Color.Navy.ToVector4();
           water.ShallowWaterColor = Color.DarkSeaGreen.ToVector4();
           water.ReflectionColor = Color.DarkGray.ToVector4();
           water.ReflectionAmount = 0.7f;            
           water.Position = new Vector3(0, -1, 0);            
           water.AnimationSpeed = .05f;            
           game.Scene.AddObject(water, "water");



BumpMap
This is the bumpmap I am using to ripple the surface of the water


If I remove the bump map from the render this is the effect

Kinda takes the depth out of the water doesn't it.

You can manage the bump height with the BumpMapHeight parameter

    water.BumpMapHeight = 0.1f;


The initial picture above uses the default BumpMapHeight of 0.1 the best range is from 0 to 2, so here are both those extremes

With a BumpMapHeight of 0, the water renders like still water, no ripples at all.


With a BumpMapHeight of 2, the water renders like a raging river


Waves

OK, I will now set the BumpMapHeight back to default and start creating some waves.

As you can see this is a nice wave effect, this was done by setting the wave amplitude to 1 and the frequency to .1


    water.WaveFrequency = .1f;
    water.WaveAmplitude = 1f;



Lets see what this looks like now with the maximum recommended BumpMapHeight of 2

Now that just makes me feel a little sea sick...

Water Color
All the examples so far have been setting the DeepWaterColor to Navy, ShallowWaterColor to DarkSeaGreen and the ReflectionColor to DarkGrey, but as you guessed you can alter these colors too.

So with the following parameter settings

    water.DeepWaterColor = Color.Red.ToVector4();
    water.ShallowWaterColor = Color.Gold.ToVector4();
    water.ReflectionColor = Color.Black.ToVector4();


You get this effect (keeping the frequency and bump height map as before)


Just to clarify the DeepWaterColor and the ShallowWaterColor parameters. In the next image I have set the parameters like this

    water.WaveFrequency = .0f;
    water.WaveAmplitude = 0f;
    water.DeepWaterColor = Color.Gold.ToVector4();
    water.ShallowWaterColor = Color.Red.ToVector4();
    water.ReflectionColor = Color.DarkGray.ToVector4();



Giving this result


You can see the water closest to the camera is Gold colored where the water in the distance is Red, this gives that deep ocean effect.

"HALO" sparkle effect
In the origional shader you could either have this effect on or off, this is now switchable via the sparkle parameter.


    water.Sparkle = true;



Sparkle On


Sparkle Off


Transparent Water
So what if you want to be able to see the fish swimming in your Ocean? Well you can do this by simply setting the AlphaBlendColor parameter like this

    water.DeepWaterColor = Color.Navy.ToVector4();
    water.ShallowWaterColor = Color.DarkSeaGreen.ToVector4();
    water.ReflectionColor = Color.DarkGray.ToVector4();
    water.AlphaBlendColor = Color.White;



So here is the ocean rendered without the AlphaBlendColor parameter being set and then with it set as in the code snippet above.




You still get all the lovely reflection and bump effects, they are just now rendered against a transparent mesh.

Synopsis
This is a very configurable shader and coupled with my RCWater class is easily accessible. As you can see in the images above it can be used in a variety of ways and I have only really touched on the possible parameter variations. However, this shader is GPU heavy, but then I am running it on my laptop which is not exactly designed for high performance 3D rendering.



RCWater

   public class RCWater : RCObject, IRCRenderable, IRCLoadable
   {
       public struct VertexMultitextured
       {
           public Vector3 Position;
           public Vector3 Normal;
           public Vector4 TextureCoordinate;
           public Vector4 TexWeights;

           public static int SizeInBytes = (3 + 3 + 4 + 4) * 4;
           public static VertexElement[] VertexElements = new VertexElement[]
            {
                new VertexElement( 0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0 ),
                new VertexElement( 0, sizeof(float) * 3, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal, 0 ),
                new VertexElement( 0, sizeof(float) * 6, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0 ),
                new VertexElement( 0, sizeof(float) * 10, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 1 ),
            };
       }

       private VertexBuffer vb;
       private IndexBuffer ib;
       VertexMultitextured[] myVertices;        
       private int myHeight = 128;
       private int myWidth = 128;

       private Vector3 basePosition;

       public new Vector3 Position
       {
           get { return basePosition; }
           set { basePosition = value; }
       }

       /// <summary>
       /// Default 128
       /// </summary>
       public int Height
       {
           get { return myHeight; }
           set { myHeight = value; }
       }
       /// <summary>
       /// Default 128
       /// </summary>
       public int Width
       {
           get { return myWidth; }
           set { myWidth = value; }
       }

       private string myEnvironmentAsset;
       private string myBumpMapAsset;
       private TextureCube myEnvironment;
       private Texture2D my2DEnvironment;
       private Texture2D myHeightMap;
       private Texture2D myBumpMap;
       private Vector4 myDeepWater = new Vector4(0,0,1,1);
       private Vector4 myShallowWater = new Vector4(0,5,5,1);
       private Vector4 myReflection = new Vector4(1,1,1,1);

       private float myBumpHeight = 0.10f;        
       private float myHDRMult = 3.0f;
       private float myReflectionAmt = 1.0f;
       private float myWaterColorAmount = 1.0f;
       private float myWaveAmplitude = 1.0f;
       private float myWaveFrequency = 0.1f;

       private bool sparkle;

       /// <summary>
       /// Use HALO sparkle effect
       /// </summary>
       public bool Sparkle
       {
           get { return sparkle; }
           set { sparkle = value; }
       }

       /// <summary>
       /// Min 0, Max 2.0
       /// </summary>
       public float BumpMapHeight
       {
           get { return myBumpHeight; }
           set { myBumpHeight = value; }
       }
       /// <summary>
       /// Min 0, Max 100
       /// </summary>
       public float HDRMultiplier
       {
           get { return myHDRMult; }
           set { myHDRMult = value; }
       }
       /// <summary>
       /// Min 0, Max 2.0
       /// </summary>
       public float ReflectionAmount
       {
           get { return myReflectionAmt; }
           set { myReflectionAmt = value; }
       }
       /// <summary>
       /// Min 0, Max 2.0
       /// </summary>
       public float WaterColorAmount
       {
           get { return myWaterColorAmount; }
           set { myWaterColorAmount = value; }
       }
       /// <summary>
       /// Min 0, Max 10.0
       /// </summary>
       public float WaveAmplitude
       {
           get { return myWaveAmplitude; }
           set { myWaveAmplitude = value; }
       }
       /// <summary>
       /// Min 0, Max 1.0
       /// </summary>
       public float WaveFrequency
       {
           get { return myWaveFrequency; }
           set { myWaveFrequency = value; }
       }

       private float tick = 0.0f;
       private float animSpeed = 0.0f;

       private bool alphaCheck;
       private Color myAlphaBlendColor;

       /// <summary>
       /// Set to get transparent water.
       /// </summary>
       public Color AlphaBlendColor
       {
           get { return myAlphaBlendColor; }
           set
           {
               myAlphaBlendColor = value;
               if (value == Color.Black)
                   alphaCheck = false;
               else
                   alphaCheck = true;
           }
       }
       
       /// <summary>
       /// Min 0, Max 0.1
       /// </summary>
       public float AnimationSpeed
       {
           get { return animSpeed; }
           set { animSpeed = value; }
       }

       /// <summary>
       /// Color of deep water
       /// </summary>
       public Vector4 DeepWaterColor
       {
           get { return myDeepWater; }
           set { myDeepWater = value; }
       }
       /// <summary>
       /// Color of shallow water
       /// </summary>
       public Vector4 ShallowWaterColor
       {
           get { return myShallowWater; }
           set { myShallowWater = value; }
       }
       /// <summary>
       /// Color of reflection
       /// </summary>
       public Vector4 ReflectionColor
       {
           get { return myReflection; }
           set { myReflection = value; }
       }
       
       public RCWater(string environmentMap, string bumpMap, string name) : base(name)
       {
           myEnvironmentAsset = environmentMap;
           myBumpMapAsset = bumpMap;            
           myWidth = 128;
           myHeight = 128;            
       }

       public void LoadGraphicsContent(GraphicsDevice myDevice, ContentManager myLoader)
       {
           // Textures
           try
           {
               myEnvironment = myLoader.Load<TextureCube>(myEnvironmentAsset);
           }
           catch
           {
               my2DEnvironment = myLoader.Load<Texture2D>(myEnvironmentAsset);
           }
           myBumpMap = myLoader.Load<Texture2D>(myBumpMapAsset);

           myPosition = new Vector3(basePosition.X - (myWidth / 2), basePosition.Y, basePosition.Z - (myHeight / 2));
         
           // Vertices
           myVertices = new VertexMultitextured[myWidth * myHeight];            

           for (int x = 0; x < myWidth; x++)
               for (int y = 0; y < myHeight; y++)
               {                    
                   myVertices[x + y * myWidth].Position = new Vector3(y, 0, x);
                   myVertices[x + y * myWidth].Normal = new Vector3(0, -1, 0);
                   myVertices[x + y * myWidth].TextureCoordinate.X = (float)x / 30.0f;
                   myVertices[x + y * myWidth].TextureCoordinate.Y = (float)y / 30.0f;
               }
           
           vb = new VertexBuffer(myDevice, VertexMultitextured.SizeInBytes * myWidth * myHeight, ResourceUsage.WriteOnly, ResourceManagementMode.Automatic);
           vb.SetData(myVertices);

           //Index
           short[] terrainIndices = new short[(myWidth - 1) * (myHeight - 1) * 6];
           for (short x = 0; x < myWidth - 1; x++)
           {
               for (short y = 0; y < myHeight - 1; y++)
               {
                   terrainIndices[(x + y * (myWidth - 1)) * 6] = (short)((x + 1) + (y + 1) * myWidth);
                   terrainIndices[(x + y * (myWidth - 1)) * 6 + 1] = (short)((x + 1) + y * myWidth);
                   terrainIndices[(x + y * (myWidth - 1)) * 6 + 2] = (short)(x + y * myWidth);

                   terrainIndices[(x + y * (myWidth - 1)) * 6 + 3] = (short)((x + 1) + (y + 1) * myWidth);
                   terrainIndices[(x + y * (myWidth - 1)) * 6 + 4] = (short)(x + y * myWidth);
                   terrainIndices[(x + y * (myWidth - 1)) * 6 + 5] = (short)(x + (y + 1) * myWidth);
               }
           }

           ib = new IndexBuffer(myDevice, typeof(short), (myWidth - 1) * (myHeight - 1) * 6, ResourceUsage.WriteOnly, ResourceManagementMode.Automatic);
           ib.SetData(terrainIndices);
       }

       public void Render(GraphicsDevice myDevice)
       {
           Effect effect = RCShaderManager.GetShader(myShader).Effect;
           effect.Parameters["normalMap"].SetValue(myBumpMap);
           effect.Parameters["sparkle"].SetValue(sparkle);
           if(myEnvironment != null)
               effect.Parameters["cubeMap"].SetValue(myEnvironment);
           else
               effect.Parameters["cubeMap"].SetValue(my2DEnvironment);                
           effect.Parameters["deepColor"].SetValue(myDeepWater);
           effect.Parameters["shallowColor"].SetValue(myShallowWater);
           effect.Parameters["reflectionColor"].SetValue(myReflection);
           effect.Parameters["time"].SetValue(tick += animSpeed);

           effect.Parameters["bumpHeight"].SetValue(myBumpHeight);
           effect.Parameters["hdrMultiplier"].SetValue(myHDRMult);
           effect.Parameters["reflectionAmount"].SetValue(myReflectionAmt);
           effect.Parameters["waterAmount"].SetValue(myWaterColorAmount);
           effect.Parameters["waveAmp"].SetValue(myWaveAmplitude);
           effect.Parameters["waveFreq"].SetValue(myWaveFrequency);

           bool alphaTest = myDevice.RenderState.AlphaTestEnable;
           bool alphaBlend = myDevice.RenderState.AlphaBlendEnable;
           CompareFunction alphaFunc = myDevice.RenderState.AlphaFunction;
           Blend sourceBlend = myDevice.RenderState.SourceBlend;
           Blend destinationBlend = myDevice.RenderState.DestinationBlend;
           Color blendFator = myDevice.RenderState.BlendFactor;

           if (alphaCheck)
           {
               if (myDevice.RenderState.AlphaTestEnable != true)
                   myDevice.RenderState.AlphaTestEnable = true;
               if (myDevice.RenderState.AlphaBlendEnable != true)
                   myDevice.RenderState.AlphaBlendEnable = true;
               if (myDevice.RenderState.AlphaFunction != CompareFunction.NotEqual)
                   myDevice.RenderState.AlphaFunction = CompareFunction.NotEqual;
                               
               if(myDevice.RenderState.SourceBlend != Blend.BlendFactor)
                   myDevice.RenderState.SourceBlend = Blend.BlendFactor;
               if(myDevice.RenderState.DestinationBlend != Blend.One)
                   myDevice.RenderState.DestinationBlend = Blend.One;
               if (myDevice.RenderState.BlendFactor != myAlphaBlendColor)
                   myDevice.RenderState.BlendFactor = myAlphaBlendColor;      
               
           }            

           myDevice.Vertices[0].SetSource(vb, 0, VertexMultitextured.SizeInBytes);
           myDevice.Indices = ib;
           myDevice.VertexDeclaration = new VertexDeclaration(myDevice, VertexMultitextured.VertexElements);
           myDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, myWidth * myHeight, 0, (myWidth - 1) * (myHeight - 1) * 2);

           if (alphaCheck)
           {
               if (myDevice.RenderState.AlphaTestEnable != alphaTest)
                   myDevice.RenderState.AlphaTestEnable = alphaTest;
               if (myDevice.RenderState.AlphaBlendEnable != alphaBlend)
                   myDevice.RenderState.AlphaBlendEnable = alphaBlend;
               if (myDevice.RenderState.AlphaFunction != alphaFunc)
                   myDevice.RenderState.AlphaFunction = alphaFunc;

               if (myDevice.RenderState.SourceBlend != sourceBlend)
                   myDevice.RenderState.SourceBlend = sourceBlend;
               if (myDevice.RenderState.DestinationBlend != destinationBlend)
                   myDevice.RenderState.DestinationBlend = destinationBlend;
               if (myDevice.RenderState.BlendFactor != blendFator)
                   myDevice.RenderState.BlendFactor = blendFator;
           }            
       }
   }



Ocean.fx

/*********************************************************************NVMH3****
Path: $Id: //sw/devtools/ShaderLibrary/1.0/HLSL/Ocean.fx#1 $
File:  ocean.fx

Copyright NVIDIA Corporation 2003-2007
TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THIS SOFTWARE IS PROVIDED
*AS IS* AND NVIDIA AND ITS SUPPLIERS DISCLAIM ALL WARRANTIES, EITHER EXPRESS
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE.  IN NO EVENT SHALL NVIDIA OR ITS SUPPLIERS
BE LIABLE FOR ANY SPECIAL, INCIDENTAL, INDIRECT, OR CONSEQUENTIAL DAMAGES
WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS,
BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR ANY OTHER PECUNIARY LOSS)
ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF NVIDIA HAS
BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.


% Simple ocean shader with animated bump map and geometric waves
% Based partly on "Effective Water Simulation From Physical Models", GPU Gems

keywords: material animation environment bumpmap

Sparkle Mod: C.Humphrey
Altered the shader so that the "HALO" sparkle effect could be switched on and off
rather than having either or.

******************************************************************************/

float Script : STANDARDSGLOBAL <
   string UIWidget = "none";
   string ScriptClass = "object";
   string ScriptOrder = "standard";
   string ScriptOutput = "color";
   string Script = "Technique=Main;";
> = 0.8;


///////// UNTWEAKABLES //////////////////////

float4x4 world : World < string UIWidget = "none";>;                   // World or Model matrix
float4x4 wvp : WorldViewProjection < string UIWidget = "none";>;    // Model*View*Projection
float4x4 worldView : WorldView < string UIWidget = "none";>;
float4x4 viewI : ViewInverse < string UIWidget = "none";>;

bool sparkle;

float time : Time < string UIWidget = "none"; >;

//////////////// TEXTURES ///////////////////

texture normalMap : Normal <
   string UIName = "Normal Map";
   string ResourceName = "waves2.dds";
   string ResourceType = "2D";
>;

texture cubeMap : Environment <
   string UIName = "Environment Cube Map";
   string ResourceName = "CloudyHillsCubemap2.dds";
   string ResourceType = "Cube";
>;

sampler2D normalMapSamplerHALO = sampler_state
{
   Texture = <normalMap>;
   // this is a trick from Halo - use point sampling for sparkles
   MagFilter = Linear;    
   MinFilter = Point;
   MipFilter = None;
};
sampler2D normalMapSampler = sampler_state
{
   Texture = <normalMap>;
   MagFilter = Linear;    
   MinFilter = Linear;
   MipFilter = Linear;
};

samplerCUBE envMapSampler = sampler_state
{
   Texture = <cubeMap>;
   MinFilter = Linear;
   MagFilter = Linear;
   MipFilter = Linear;
   AddressU = Clamp;
   AddressV = Clamp;
};

///////// TWEAKABLE PARAMETERS //////////////////

float bumpHeight <
   string UIWidget = "slider";
   float UIMin = 0.0; float UIMax = 2.0; float UIStep = 0.01;
   string UIName = "Bump Height";
> = 0.1;

float2 textureScale <
   string UIName = "Texture Scale";
> = { 8.0, 4.0 };

float2 bumpSpeed <
   string UIName = "Bumpmap Translation Speed";
> = { -0.05, 0.0 };

float fresnelBias <
   string UIName = "Fresnel Bias";
   string UIWidget = "slider";
   float UIMin = 0.0; float UIMax = 1.0; float UIStep = 0.01;
> = 0.1;

float fresnelPower <
   string UIName = "Fresnel Exponent";
   string UIWidget = "slider";
   float UIMin = 1.0; float UIMax = 10.0; float UIStep = 0.01;
> = 4.0;

float hdrMultiplier <
   string UIName = "HDR Multiplier";
   string UIWidget = "slider";
   float UIMin = 0.0; float UIMax = 100.0; float UIStep = 0.01;
> = 3.0;

float4 deepColor : Diffuse <
   string UIName = "Deep Water";
> = {0.0f, 0.0f, 0.1f, 1.0f};

float4 shallowColor : Diffuse <
   string UIName = "Shallow Water";
> = {0.0f, 0.5f, 0.5f, 1.0f};

float4 reflectionColor : Specular <
   string UIName = "Reflection";
> = {1.0f, 1.0f, 1.0f, 1.0f};

// these are redundant, but makes the ui easier:
float reflectionAmount <
   string UIName = "Reflection Amount";
   string UIWidget = "slider";    
   float UIMin = 0.0; float UIMax = 2.0; float UIStep = 0.01;    
> = 1.0f;

float waterAmount <
   string UIName = "Water Color Amount";
   string UIWidget = "slider";    
   float UIMin = 0.0; float UIMax = 2.0; float UIStep = 0.01;    
> = 1.0f;

float waveAmp <
   string UIName = "Wave Amplitude";
   string UIWidget = "slider";
   float UIMin = 0.0; float UIMax = 10.0; float UIStep = 0.1;
> = 1.0;

float waveFreq <
   string UIName = "Wave frequency";
   string UIWidget = "slider";
   float UIMin = 0.0; float UIMax = 1.0; float UIStep = 0.001;
> = 0.1;

//////////// CONNECTOR STRUCTS //////////////////

struct AppData {
   float4 Position : POSITION;   // in object space
   float2 TexCoord : TEXCOORD0;
   float3 Tangent  : TEXCOORD1;
   float3 Binormal : TEXCOORD2;
   float3 Normal   : NORMAL;
};

struct VertexOutput {
   float4 Position  : POSITION;  // in clip space
   float2 TexCoord  : TEXCOORD0;
   float3 TexCoord1 : TEXCOORD1; // first row of the 3x3 transform from tangent to cube space
   float3 TexCoord2 : TEXCOORD2; // second row of the 3x3 transform from tangent to cube space
   float3 TexCoord3 : TEXCOORD3; // third row of the 3x3 transform from tangent to cube space

   float2 bumpCoord0 : TEXCOORD4;
   float2 bumpCoord1 : TEXCOORD5;
   float2 bumpCoord2 : TEXCOORD6;
   
   float3 eyeVector  : TEXCOORD7;
};

// wave functions ///////////////////////

struct Wave {
 float freq;  // 2*PI / wavelength
 float amp;   // amplitude
 float phase; // speed * 2*PI / wavelength
 float2 dir;
};

#define NWAVES 2
Wave wave[NWAVES] = {
   { 1.0, 1.0, 0.5, float2(-1, 0) },
   { 2.0, 0.5, 1.3, float2(-0.7, 0.7) }    
};

float evaluateWave(Wave w, float2 pos, float t)
{
 return w.amp * sin( dot(w.dir, pos)*w.freq + t*w.phase);
}

// derivative of wave function
float evaluateWaveDeriv(Wave w, float2 pos, float t)
{
 return w.freq*w.amp * cos( dot(w.dir, pos)*w.freq + t*w.phase);
}

// sharp wave functions
float evaluateWaveSharp(Wave w, float2 pos, float t, float k)
{
 return w.amp * pow(sin( dot(w.dir, pos)*w.freq + t*w.phase)* 0.5 + 0.5 , k);
}

float evaluateWaveDerivSharp(Wave w, float2 pos, float t, float k)
{
 return k*w.freq*w.amp * pow(sin( dot(w.dir, pos)*w.freq + t*w.phase)* 0.5 + 0.5 , k - 1) * cos( dot(w.dir, pos)*w.freq + t*w.phase);
}

///////// SHADER FUNCTIONS ///////////////

VertexOutput BumpReflectWaveVS(AppData IN,
                     uniform float4x4 WorldViewProj,
                     uniform float4x4 World,
                     uniform float4x4 ViewIT,
                     uniform float BumpScale,
                     uniform float2 textureScale,
                     uniform float2 bumpSpeed,
                     uniform float time,
                     uniform float waveFreq,
                     uniform float waveAmp
                     )
{
   VertexOutput OUT;

   wave[0].freq = waveFreq;
   wave[0].amp = waveAmp;

   wave[1].freq = waveFreq*2.0;
   wave[1].amp = waveAmp*0.5;

   float4 P = IN.Position;

   // sum waves    
   P.y = 0.0;
   float ddx = 0.0, ddy = 0.0;
   for(int i=0; i<NWAVES; i++) {
       P.y += evaluateWave(wave[i], P.xz, time);
       float deriv = evaluateWaveDeriv(wave[i], P.xz, time);
       ddx += deriv * wave[i].dir.x;
       ddy += deriv * wave[i].dir.y;
   }

   // compute tangent basis
   float3 B = float3(1, ddx, 0);
   float3 T = float3(0, ddy, 1);
   float3 N = float3(-ddx, 1, -ddy);

   OUT.Position = mul(P, WorldViewProj);
   
   // pass texture coordinates for fetching the normal map
   OUT.TexCoord.xy = IN.TexCoord*textureScale;

   time = fmod(time, 100.0);
   OUT.bumpCoord0.xy = IN.TexCoord*textureScale + time*bumpSpeed;
   OUT.bumpCoord1.xy = IN.TexCoord*textureScale*2.0 + time*bumpSpeed*4.0;
   OUT.bumpCoord2.xy = IN.TexCoord*textureScale*4.0 + time*bumpSpeed*8.0;

   // compute the 3x3 tranform from tangent space to object space
   float3x3 objToTangentSpace;
   // first rows are the tangent and binormal scaled by the bump scale
   objToTangentSpace[0] = BumpScale * normalize(T);
   objToTangentSpace[1] = BumpScale * normalize(B);
   objToTangentSpace[2] = normalize(N);

   OUT.TexCoord1.xyz = mul(objToTangentSpace, World[0].xyz);
   OUT.TexCoord2.xyz = mul(objToTangentSpace, World[1].xyz);
   OUT.TexCoord3.xyz = mul(objToTangentSpace, World[2].xyz);

   // compute the eye vector (going from shaded point to eye) in cube space
   float4 worldPos = mul(P, World);
   OUT.eyeVector = ViewIT[3] - worldPos; // view inv. transpose contains eye position in world space in last row
   return OUT;
}


// Pixel Shaders

float4 BumpReflectMain(VertexOutput IN,                      
                      uniform samplerCUBE EnvironmentMap) : COLOR
{
   // fetch the bump normal from the normal map
   float4 N;
   
   if(!sparkle)
       N = tex2D(normalMapSamplerHALO, IN.TexCoord.xy)*2.0 - 1.0;
   else
       N = tex2D(normalMapSampler, IN.TexCoord.xy)*2.0 - 1.0;
       
   
   float3x3 m; // tangent to world matrix
   m[0] = IN.TexCoord1;
   m[1] = IN.TexCoord2;
   m[2] = IN.TexCoord3;
   float3 Nw = mul(m, N.xyz);
   
//    float3 E = float3(IN.TexCoord1.w, IN.TexCoord2.w, IN.TexCoord3.w);
   float3 E = IN.eyeVector;
   float3 R = reflect(-E, Nw);

   return texCUBE(EnvironmentMap, R);
}

float4 OceanMain(VertexOutput IN,                
                uniform samplerCUBE EnvironmentMap,
                uniform half4 deepColor,
                uniform half4 shallowColor,
                uniform half4 reflectionColor,
                uniform half4 reflectionAmount,
                uniform half4 waterAmount,
                uniform half fresnelPower,
                uniform half fresnelBias,
                uniform half hdrMultiplier
                ) : COLOR
{
   // sum normal maps
   half4 t0;
   half4 t1;
   half4 t2;
   
   if(!sparkle)
   {
       t0 = tex2D(normalMapSampler, IN.bumpCoord0.xy)*2.0-1.0;
       t1 = tex2D(normalMapSampler, IN.bumpCoord1.xy)*2.0-1.0;
       t2 = tex2D(normalMapSampler, IN.bumpCoord2.xy)*2.0-1.0;
   }
   else
   {
       t0 = tex2D(normalMapSamplerHALO, IN.bumpCoord0.xy)*2.0-1.0;
       t1 = tex2D(normalMapSamplerHALO, IN.bumpCoord1.xy)*2.0-1.0;
       t2 = tex2D(normalMapSamplerHALO, IN.bumpCoord2.xy)*2.0-1.0;
   }
   half3 N = t0.xyz + t1.xyz + t2.xyz;
//    half3 N = t1.xyz;

   half3x3 m; // tangent to world matrix
   m[0] = IN.TexCoord1;
   m[1] = IN.TexCoord2;
   m[2] = IN.TexCoord3;
   half3 Nw = mul(m, N.xyz);
   Nw = normalize(Nw);

   // reflection
   float3 E = normalize(IN.eyeVector);
   half3 R = reflect(-E, Nw);

   half4 reflection = texCUBE(EnvironmentMap, R);
   // hdr effect (multiplier in alpha channel)
   reflection.rgb *= (1.0 + reflection.a*hdrMultiplier);

   // fresnel - could use 1D tex lookup for this
   half facing = 1.0 - max(dot(E, Nw), 0);
   half fresnel = fresnelBias + (1.0-fresnelBias)*pow(facing, fresnelPower);

   half4 waterColor = lerp(deepColor, shallowColor, facing);

   return waterColor*waterAmount + reflection*reflectionColor*reflectionAmount*fresnel;
//    return waterColor;
//    return fresnel;
//    return reflection;
}

//////////////////// TECHNIQUE ////////////////

technique Main <
   string Script = "Pass=p0;";
> {
   pass p0 <
       string Script = "Draw=geometry;";
   > {
       VertexShader = compile vs_1_1 BumpReflectWaveVS(wvp, world, viewI,
                                                       bumpHeight, textureScale, bumpSpeed, time,
                                                       waveFreq, waveAmp);
       
       Zenable = true;
       ZWriteEnable = true;
//        CullMode = None;

//        PixelShader = compile ps_2_0 BumpReflectMain(envMapSampler);
       PixelShader = compile ps_2_0 OceanMain(envMapSampler,
                                              deepColor, shallowColor, reflectionColor, reflectionAmount, waterAmount,
                                              fresnelPower, fresnelBias, hdrMultiplier);
   }
}

///////////////////////////////// eof ///


So my next post will be on my Fire class and it's shader.