Google
 

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.

4 comments:

  1. Dude, you're a code killer... That's too perfect, I can't believe it with my own eyes in my own engine!
    Thanks for everything!!!!!

    ReplyDelete
  2. Nice mate, if you are able to post the whole file, it would be really great.. Cheers

    ReplyDelete
  3. Hi eko1892,

    Are you after the full project and sample? If it is then check out the new blog, I have not used this one in ages and will be rolling it all over to C++ when I get time.

    You can find the new blog here: http://www.xna-uk.net/blogs/randomchaos

    If you are just after the water class and shader, they are at the bottom of this post :)

    ReplyDelete

Note: Only a member of this blog may post a comment.