Google
 
Showing posts with label Shaders. Show all posts
Showing posts with label Shaders. Show all posts

Wednesday, September 19, 2007

Generic XNA - Bump/Normal Mapping


Now I have had a hell of a game with this in the past and have now come across a few people who have had the same or similar issues that I have had. So I have decided to put up and example of how to do this. The example given on the Creators Club is great, but relies on you embedding your textures in the mesh you intend to bump. This is all fine, but if you have one model and you want to apply different textures to different instances of it then this method is limiting.

So I have decided to post up my code to do this. Now as I said before, I have had a right load of trouble with this right from the start, you can see my tales of woe on the HazyMind forum and on the XNA Creators Club forum, as usual a HUGE thanks goes out to Leaf from the XNA UK User Group

So my initial issue was that I didn't know that certain model formats do not come with tangent data in them, at least that is what I am told. So I started off trying to apply my shader to an X formated mesh and got some really odd results (see the HazyMind posts), after much time and with the release of Benjamin Nitschke's book on XNA I found out thanks to the guys on the HM forum that you have to pass the model through a custom content importer and generate your tangent data there.

So I added to my existing custom model content pipeline, but did not get the method of generating the Tangent data correctly. Now this gave rise to some really odd results, basically the models would have my old issue when I did not have my bumped terrain in the draw call, but one it was drawn it got the effect rendered correctly. This was because before the terrain was rendered there was just random data in the Tangent Chanel that was being passed to the shader, once it was drawn and by sheer luck the tangent data from the terrain object just happened to sit in the same memory location as the unassigned tangent data for the model, so giving the illusion of the bump effect. So when my applications were ran on other systems the bump effect just did not work.

Here is and example of a custom content pipleine class for the model to be bumped

using System;
using System;
using System.IO;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;
using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler;

namespace CustomContentPipeline
{    
   [ContentProcessor]    
   public class MyModelProcessor : ModelProcessor    
   {        
       double minX = double.MaxValue;
       double minY = double.MaxValue;
       double minZ = double.MaxValue;
       double maxX = double.MinValue;
       double maxY = double.MinValue;
       double maxZ = double.MinValue;
       
       List<BoundingBox> boxes = new List<BoundingBox>();
       List<List<Vector3>> MeshVerts = new List<List<Vector3>>();
       
       object[] ModelData = new object[2];
       
       // Bounding Box's
       private void CheckNode(NodeContent content)
       {
           foreach (NodeContent o in content.Children)
           {
               if (o is MeshContent)
               {
                   // Get VertData
                   GetAllVerticies((MeshContent)o);
                   BoundingBox bb = new BoundingBox();

                   minX = double.MaxValue;
                   minY = double.MaxValue;
                   minZ = double.MaxValue;
                   maxX = double.MinValue;
                   maxY = double.MinValue;
                   maxZ = double.MinValue;
                   
                   MeshContent mesh = (MeshContent)o;
                   foreach (Vector3 basev in mesh.Positions)
                   {
                       Vector3 v = basev;

                       if (v.X < minX)
                           minX = v.X;
                       
                       if (v.Y < minY)
                           minY = v.Y;
                       
                       if (v.Z < minZ)
                           minZ = v.Z;
                       
                       if (v.X > maxX)
                           maxX = v.X;
                       
                       if (v.Y > maxY)
                           maxY = v.Y;
                       
                       if (v.Z > maxZ)
                           maxZ = v.Z;
                   
                   }
                   
                   double lenX = maxX - minX;
                   double lenZ = maxZ - minZ;
                   double lenY = maxY - minY;

                   bb.Min = new Vector3((float)minX, (float)minY, (float)minZ);
                   bb.Max = new Vector3((float)maxX, (float)maxY, (float)maxZ);
                   boxes.Add(bb);
               }
               else
                   CheckNode(o);
           }
       }
       
       // Vertex positions
       private void GetAllVerticies(MeshContent mesh)
       {
           for (int g = 0; g < mesh.Geometry.Count; g++)
           {
               GeometryContent geometry = mesh.Geometry[g];
               
               List<Vector3> temp = new List<Vector3>();
               
               for (int ind = 0; ind < geometry.Indices.Count; ind++)
               {
                   // Transforms all of my verticies to local space.
                   Vector3 position = Vector3.Transform(geometry.Vertices.Positions[geometry.Indices[ind]], mesh.AbsoluteTransform);
                   temp.Add(position);
               }
               MeshVerts.Add(temp);
           }
       }        
       // Tangents.
       private void GenerateTangents(NodeContent input, ContentProcessorContext context)
       {
           MeshContent mesh = input as MeshContent;
           
           if (mesh != null)
           {
               MeshHelper.CalculateTangentFrames(mesh,
                   VertexChannelNames.TextureCoordinate(0),
                   VertexChannelNames.Tangent(0),
                   VertexChannelNames.Binormal(0));
           }
           
           foreach (NodeContent child in input.Children)
           {
               GenerateTangents(child, context);
           }
       }        
       // Normals
       private void GenerateNormals(NodeContent input, ContentProcessorContext context)
       {
           MeshContent mesh = input as MeshContent;
           
           if (mesh != null)
           {
               MeshHelper.CalculateNormals(mesh, true);
           }
           
           foreach (NodeContent child in input.Children)
           {
               GenerateNormals(child, context);
           }
       }
       
       public override ModelContent Process(NodeContent input, ContentProcessorContext context)
       {
           // Calculate Mesh Tangents.
           GenerateNormals(input, context);
           
           // Calculate Mesh Normals.
           GenerateTangents(input, context);
           
           // Setup bounding box data.
           CheckNode(input);
           
           ModelData[0] = boxes;
           ModelData[1] = MeshVerts;

           ModelContent basemodel = base.Process(input, context);
           basemodel.Tag = ModelData;
           return basemodel;
       }
   }
}



The relevant method here is my GenerateTangent method:

       // Tangents.
       private void GenerateTangents(NodeContent input, ContentProcessorContext context)
       {
           MeshContent mesh = input as MeshContent;
           
           if (mesh != null)
           {
               MeshHelper.CalculateTangentFrames(mesh,
                   VertexChannelNames.TextureCoordinate(0),
                   VertexChannelNames.Tangent(0),
                   VertexChannelNames.Binormal(0));
           }
           
           foreach (NodeContent child in input.Children)
           {
               GenerateTangents(child, context);
           }
       }




GenericExampleBumpNormalMapping.zip

Thursday, July 26, 2007

Generic XNA - SkyBox



So a sky box, having done the Hazy Mind tutorial and had a play with Riemers tutorials I decided to create my own skybox. I like the way both tutorials do there sky box's but I kind of like cube maps, it means I keep 6 textures in one place, have only one texture variable and so makes my life a little easier when passing textures to my skybox. So my version of a skybox uses a box mesh (the same one from Riemers tutorial) but instead of passing a texture to each side of the box or having to pass multiple textures to the mesh I just pass a single cube map. I then wrote a shader to render the cube map on the skybox mesh.

In this example I also have a basic camera that is a static class, this does not use the GameComponents at all as I want it to be used by all drawable elements in my code, so this class is driven by the game loop directly from in my game class.

So, the code...

SkyBox Class

   public class SkyBox : DrawableGameComponent
   {
       private Model skyboxMesh;
       public Vector3 myPosition;
       public Quaternion myRotation;
       public Vector3 myScale;

       public TextureCube environ;

       Effect shader;

       string modelAsset;
       string shaderAsset;
       string textureAsset;

       ContentManager content;

       public SkyBox(Game game,string modelAsset,string shaderAsset,string textureAsset) : base(game)
       {
           content = new ContentManager(game.Services);

           this.modelAsset = modelAsset;
           this.shaderAsset = shaderAsset;
           this.textureAsset = textureAsset;

           myPosition = new Vector3(0, 0, 0);
           myRotation = new Quaternion(0, 0, 0, 1);
           myScale = new Vector3(55, 55, 55);
       }

       protected override void LoadGraphicsContent(bool loadAllContent)
       {
           if (loadAllContent)
           {
               skyboxMesh = content.Load<Model>(modelAsset);
               shader = content.Load<Effect>(shaderAsset);
               environ = content.Load<TextureCube>(textureAsset);
           }
           base.LoadGraphicsContent(loadAllContent);
       }

       public override void Draw(GameTime gameTime)
       {
           Matrix World = Matrix.CreateScale(myScale) *
                           Matrix.CreateFromQuaternion(myRotation) *
                           Matrix.CreateTranslation(Camera.myPosition);

           shader.Parameters["World"].SetValue(World);
           shader.Parameters["View"].SetValue(Camera.myView);
           shader.Parameters["Projection"].SetValue(Camera.myProjection);
           shader.Parameters["surfaceTexture"].SetValue(environ);

           shader.Parameters["EyePosition"].SetValue(Camera.myPosition);

           for (int pass = 0; pass < shader.CurrentTechnique.Passes.Count; pass++)
           {
               for (int msh = 0; msh < skyboxMesh.Meshes.Count; msh++)
               {
                   ModelMesh mesh = skyboxMesh.Meshes[msh];
                   for (int prt = 0; prt < mesh.MeshParts.Count; prt++)
                       mesh.MeshParts[prt].Effect = shader;
                   mesh.Draw();
               }
           }

           base.Draw(gameTime);
       }        
   }


Camera Class

   public sealed class Camera
   {
       public static Vector3 myPosition;
       public static Vector3 myTarget;
       public static Quaternion myRotation;

       private static Matrix myWorld;
       public static Matrix myView;
       public static Matrix myProjection;
       public static Viewport myViewport;

       private Camera()
       { }

       public static void Initialize()
       {
           myTarget = new Vector3();
           myPosition = new Vector3(0, 0, 0);
           myRotation = new Quaternion(0, 0, 0, 1);
       }

       public  static void Update()
       {
           myWorld = Matrix.Identity;

           myView = Matrix.Invert(Matrix.CreateFromQuaternion(myRotation) *
                                   Matrix.CreateTranslation(myPosition));

           float aspectRatio = myViewport.Width / myViewport.Height;
           myProjection = Matrix.CreatePerspectiveFieldOfView(1, aspectRatio, myViewport.MinDepth, myViewport.MaxDepth);

           myProjection = Matrix.CreatePerspectiveFieldOfView(MathHelper.Pi / 3.0f, (float)myViewport.Width / (float)myViewport.Height, myViewport.MinDepth, myViewport.MaxDepth);
       }
       public static void Rotate(Vector3 axis, float angle)
       {
           axis = Vector3.Transform(axis, Matrix.CreateFromQuaternion(myRotation));
           myRotation = Quaternion.Normalize(Quaternion.CreateFromAxisAngle(axis, angle) * myRotation);

           Update();
       }
       public static void Translate(Vector3 distance)
       {
           myPosition += Vector3.Transform(distance, Matrix.CreateFromQuaternion(myRotation));            
           Update();
       }

       public static void Revolve(Vector3 target, Vector3 axis, float angle)
       {
           Rotate(axis, angle);
           Vector3 revolveAxis = Vector3.Transform(axis, Matrix.CreateFromQuaternion(myRotation));
           Quaternion rotate = Quaternion.CreateFromAxisAngle(revolveAxis, angle);
           myPosition = Vector3.Transform(target - myPosition, Matrix.CreateFromQuaternion(rotate));

           Update();
       }      
   }



SkyBox Shader

//////////////////////////////////////////////////////////////
//                                                            //
//    Writen by C.Humphrey                                    //
//    26/07/2007                                                //
//                                                            //
//                                                            //
//    Shader used to render a cube map to an inverted box        //
//    mesh.                                                    //
//                                                            //
//////////////////////////////////////////////////////////////

Texture surfaceTexture;
samplerCUBE TextureSampler = sampler_state
{
   texture = <surfaceTexture> ;
   magfilter = LINEAR;
   minfilter = LINEAR;
   mipfilter = LINEAR;
   AddressU = Mirror;
   AddressV = Mirror;
};

float4x4 World : World;
float4x4 View : View;
float4x4 Projection : Projection;

float3 EyePosition : CameraPosition;

struct VS_INPUT
{
   float4 Position    : POSITION0;
   float3 Normal : NORMAL0;    
};

struct VS_OUTPUT
{
   float4 Position    : POSITION0;
   float3 ViewDirection : TEXCOORD2;
       
};

float4 CubeMapLookup(float3 CubeTexcoord)
{    
   return texCUBE(TextureSampler, CubeTexcoord);
}

VS_OUTPUT Transform(VS_INPUT Input)
{
   float4x4 WorldViewProjection = mul(mul(World, View), Projection);
   float3 ObjectPosition = mul(Input.Position, World);
   
   VS_OUTPUT Output;
   Output.Position    = mul(Input.Position, WorldViewProjection);    
   
   Output.ViewDirection = EyePosition - ObjectPosition;    
   
   return Output;
}

struct PS_INPUT
{    
   float3 ViewDirection : TEXCOORD2;
};

float4 BasicShader(PS_INPUT Input) : COLOR0
{    
   float3 ViewDirection = normalize(Input.ViewDirection);    
      return CubeMapLookup(-ViewDirection);
}

technique BasicShader
{
   pass P0
   {
       VertexShader = compile vs_2_0 Transform();
       PixelShader  = compile ps_2_0 BasicShader();
   }
}



So the sky box is instantiated like this in the Game1 constructor:

           SkyBox skyBox = new SkyBox(this,"Content/Models/skybox", "Content/Shaders/skybox", "Content/Textures/cubeMap");
           this.Components.Add(skyBox);



The configuration of the camera is a little more detaild. First of all, I want the user to resize the viewport, so I need to wire an event to manage this and update the cameras viewport properties, also, if the user has multiple screens and they drag the game from one window to the next, the camera viewport has to be reset again so this even also needs to be wired. So first off I write my methods to be called by the events.


       /// <summary>
       /// Should the user move the game screen over to another monitor.
       /// </summary>
       /// <param name="sender"></param>
       /// <param name="e"></param>
       void DeviceChanged(object sender, EventArgs e)
       {
           Camera.myViewport = graphics.GraphicsDevice.Viewport;
           Camera.myViewport.MinDepth = 1f;
           Camera.myViewport.MaxDepth = 1000f;
       }

       /// <summary>
       /// Should the game window screen size change.
       /// </summary>
       /// <param name="sender"></param>
       /// <param name="e"></param>
       public void Resize(object sender, EventArgs e)
       {
           Camera.myViewport.Width = Window.ClientBounds.Width;
           Camera.myViewport.Height = Window.ClientBounds.Height;
       }



I now wire these events up in the Game1 constructor.

           Window.ClientSizeChanged += new EventHandler(Resize);
           Window.ScreenDeviceNameChanged += new EventHandler(DeviceChanged);



Now I have those bases covered I need to initialize my camera, this is done in the Initialize method of the Game1 class.


           Camera.Initialize();
           Camera.myViewport = graphics.GraphicsDevice.Viewport;
           Camera.myViewport.MinDepth = 1f;
           Camera.myViewport.MaxDepth = 1000f;



Now all I have to do is update my camera, and yes, you guessed it, this is done in the Update method of the Game2 class.


           Camera.Update();


Controls
Mouse rotates camera.
Esc to exit.

Again, any comments, issues, suggestions or fixes, please comment here.

OOPS! I forgot to add the following method to the SkyBox class, basicly without it if you move the game window to another screen your app will crash as you will get memory violations. Sorry. I have not updated the zip so you will have to do this manualy, source from now on will include this method if needed.


       protected override void UnloadGraphicsContent(bool unloadAllContent)
       {
           content.Unload();
           base.UnloadGraphicsContent(unloadAllContent);
       }



GenericExampleSkyBox.zip

Saturday, July 21, 2007

Engine Design - Fire

Right,

Here is the code for my fire, this fire effect is not to robust really, I need to do a little more work on it so I can have true volumetric fire, also I don't like the multi mesh approach I am using either so will probably play with the shader to get it to run on a single mesh. In short I am not happy with it yet, but I am posting it here so you can at least see what it does and it might give you some ideas of how to do your own. When I started out playing with this shader my knowledge of shaders was very limited, now I have a bit more experience I will more than likely improve on this, if I do I will post it her.

So, the code...
RCFire


public class RCLayeredFire : RCObject, IRCChildRenderer, IRCLoadable
   {
       public bool Colapsed;
       private RCModel[] flame;
       private RCModel[] flame2;
       private string flameModelAsset;
       private string flameTextureAsset;
       private int flameLayers;
       private float animSpeed;
       private float tick;
       private float flameOffset;

       private Vector3 camerasLastPosition = new Vector3(0,0,0);
       
       private Texture2D flameTexture;

       public float AnimationSpeed
       {
           get { return animSpeed; }
           set { animSpeed = value; }
       }

       public float FlameOffSet
       {
           get { return flameOffset; }
           set { flameOffset = value; }
       }
       
       public RCLayeredFire(string modelAsset,string textureAsset,int layers,string Name) : base(Name)
       {    
           flameModelAsset = modelAsset;
           flameTextureAsset = textureAsset;
           flameLayers = layers;
           flame = new RCModel[flameLayers];
           flame2 = new RCModel[flameLayers];
           base.Rotate(new Vector3(1, 0, 0), MathHelper.PiOver2);

           for (int f = 0; f < flameLayers; f++)
           {
               flame[f] = new RCModel(flameModelAsset, "flame" + f.ToString());
               flame[f].Rotation = myRotation;

               flame2[f] = new RCModel(flameModelAsset, "flame" + f.ToString());
               flame2[f].Rotation = myRotation;
               flame2[f].Rotate(new Vector3(0, 0, 1), MathHelper.Pi);
           }            
       }

       public void LoadGraphicsContent(GraphicsDevice myDevice, ContentManager myLoader)
       {
           flameTexture = myLoader.Load<Texture2D>(flameTextureAsset);
           for (int f = 0; f < flameLayers; f++)
           {  
               flame[f].Rotation = myRotation;
               // Now collapsed to a sigle position to ease rotation.
               flame[f].Position = new Vector3(myPosition.X, myPosition.Y, myPosition.Z + (((float)f) / 10f));
               //flame[f].Position = myPosition;
               flame[f].SetShader(myShader);                
               flame[f].LoadGraphicsContent(myDevice, myLoader);
               flame[f].UseBasicRender = false;

               flame2[f].Rotation = myRotation;
               flame2[f].Rotate(new Vector3(0, 0, 1), MathHelper.Pi);
               flame2[f].Position = new Vector3(myPosition.X, myPosition.Y, myPosition.Z - (((float)f) / 10f));                
               flame2[f].SetShader(myShader);
               flame2[f].LoadGraphicsContent(myDevice, myLoader);
           }
       }
       
       public void RenderChildren(GraphicsDevice myDevice)
       {
           CullMode cull = myDevice.RenderState.CullMode;
           bool depthBuffer = myDevice.RenderState.DepthBufferEnable;
           bool Zwrite = myDevice.RenderState.DepthBufferWriteEnable;
           bool AlphaEnable = myDevice.RenderState.AlphaBlendEnable;
           BlendFunction blendOp = myDevice.RenderState.BlendFunction;
           Blend srcBlend = myDevice.RenderState.SourceBlend;
           Blend destblend = myDevice.RenderState.DestinationBlend;

           //if (cull != CullMode.None)
           //    myDevice.RenderState.CullMode = CullMode.None;
           if (depthBuffer != true)
               myDevice.RenderState.DepthBufferEnable = true;
           if (Zwrite != true)
               myDevice.RenderState.DepthBufferWriteEnable = true;
           if (AlphaEnable != true)
               myDevice.RenderState.AlphaBlendEnable = true;
           if (blendOp != BlendFunction.Add)
               myDevice.RenderState.BlendFunction = BlendFunction.Add;
           if (srcBlend != Blend.One)
               myDevice.RenderState.SourceBlend = Blend.One;
           if (destblend != Blend.One)
               myDevice.RenderState.DestinationBlend = Blend.One;

           if (RCCameraManager.ActiveCamera.Position != camerasLastPosition)
           {
               Vector3 tminusp = myPosition - RCCameraManager.ActiveCamera.Position;
               tminusp.Normalize();
               float angle = (float)Math.Acos(Vector3.Dot(tminusp, Vector3.Forward));
               //this.Rotate(new Vector3(0, 0, 1), angle);
               //this.Revolve(myPosition, new Vector3(0, 0, 1), angle);
               camerasLastPosition = RCCameraManager.ActiveCamera.Position;
           }            

           Effect effect = RCShaderManager.GetShader(myShader).Effect;
           if (effect.Parameters["flameTexture"] != null)
               effect.Parameters["flameTexture"].SetValue(flameTexture);
           if (effect.Parameters["ticks"] != null)
               effect.Parameters["ticks"].SetValue(tick += animSpeed);

           for (int f = 0; f < flameLayers; f++)
           {
               flame[f].Rotation = myRotation;
               flame[f].Scaling = myScaling;              
               //flame[f].AnimationSpeed = animSpeed;

               flame2[f].Rotation = myRotation;
               flame2[f].Rotate(new Vector3(0, 0, 1), MathHelper.Pi);
               flame2[f].Scaling = myScaling;
               flame2[f].AnimationSpeed = animSpeed;

               if (effect.Parameters["Index"] != null)
                   effect.Parameters["Index"].SetValue(flameOffset + (float)Convert.ToDouble(f) / 10);
               
               flame[f].Draw(myDevice);
               //flame2[f].Draw(myDevice);
           }


           if (cull != myDevice.RenderState.CullMode)
               myDevice.RenderState.CullMode = cull;
           if (depthBuffer != myDevice.RenderState.DepthBufferEnable)
               myDevice.RenderState.DepthBufferEnable = depthBuffer;
           if (Zwrite != myDevice.RenderState.DepthBufferWriteEnable)
               myDevice.RenderState.DepthBufferWriteEnable = Zwrite;
           if (AlphaEnable != myDevice.RenderState.AlphaBlendEnable)
               myDevice.RenderState.AlphaBlendEnable = AlphaEnable;
           if (blendOp != myDevice.RenderState.BlendFunction)
               myDevice.RenderState.BlendFunction = blendOp;
           if (srcBlend != myDevice.RenderState.SourceBlend)
               myDevice.RenderState.SourceBlend = srcBlend;
           if (destblend != myDevice.RenderState.DestinationBlend)
               myDevice.RenderState.DestinationBlend = destblend;
       }        
   }






So using this class in my engine for the screen shot above I set it up like this.

           RCLayeredFire fire = new RCLayeredFire("Content/Models/Plane", "Content/Textures/flame", 6, "f");
           fire.Position = new Vector3(0, -9f, 54);
           fire.Scaling = new Vector3(3, 1, 5);
           fire.AnimationSpeed = 0.1f;
           fire.SetShader("FLA");
           fire.FlameOffSet = .2f;
           game.Scene.AddObject(fire, "f");



I have also found a bug (one of many) the mesh you are applying this to needs to have a texture with it or you will get a flame like this



As you can see there are a few bits of code commented out, this is because I am still working on this class, so little time, so much to do.

Flame.fx
Here is the shader (thanks again NVIDIA)

/*
   Volumetric flame effect
   based on Yury Uralsky's "Volumetric Fire"
   http://www.cgshaders.org/shaders/show.php?id=39
   $Id: //sw/devtools/SDK/9.5/SDK/MEDIA/HLSL/Flame.fx#2 $

   This revolves a cross section of a flame image around the Y axis to
   produce a cylindrical volume, and then perturbs the texture coordinates
   with 4 octaves of animated 3D procedural noise to produce the flame effect.
   
   Mod History: [Oriional shader from the NVIDIA 9.5 SDK]    
   
   23//03/2007 C. Humphrey charles.humphrey53@yahoo.co.uk http://www.randomchaos.co.uk
   Modified so that the shader can be placed on an object that has
   its position other than at 0,0,0 and made compatible with XNA.
*/

string XFile <string UIWidget="None";> = "slices_10y.x";
string description = "3D Flame";

// Added by C.Humphrey
float Index
<
   string UIWidget = "slider";
   float UIMin = 0.0;
   float UIMax = 1.0;
   float UIStep = 0.1;
>;

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

float ticks : Time
<
string units = "sec";
string UIWidget="None";
>;

/************* TWEAKABLES **************/

float noiseFreq
<
   string UIWidget = "slider";
   float UIMin = 0.0; float UIMax = 1.0; float UIStep = 0.01;
> = .10;

float noiseStrength
<
   string UIWidget = "slider";
   float UIMin = 0.0; float UIMax = 5.0; float UIStep = 0.01;
> = 1.0;

float timeScale
<
   string UIWidget = "slider";
   string UIName = "Speed";
   float UIMin = 0.0; float UIMax = 1.0; float UIStep = 0.01;
> = 1.0;

float3 noiseScale = { 1.0, 1.0, 1.0 };
float3 noiseAnim = { 0.0, -0.1, 0.0 };

float4 flameColor <string UIName = "flame color"; string UIWidget="Color";> = { 0.2, 0.2, 0.2, 1.0 };
float3 flameScale <string UIName = "flame scale";> = { 1.0, -1.0, 1.0 };
float3 flameTrans <string UIName = "flame offset";> = { 0.0, 0.0, 0.0 };

// Textures /////////////////

#define VOLUME_SIZE 32

texture noiseTexture
<
//    string Name = "noiseL8_32x32x32.dds";
   string ResourceType = "3D";
   string function = "GenerateNoise1f";
   float3 Dimensions = { VOLUME_SIZE, VOLUME_SIZE, VOLUME_SIZE};
>;

texture flameTexture
<
   string ResourceName = "flame.png";
   string ResourceType = "2D";
>;

// Vector-valued noise
float4 GenerateNoise4f(float3 Pos : POSITION) : COLOR
{
   float4 c;
   float3 P = Pos*VOLUME_SIZE;
   c.r = noise(P);
   c.g = noise(P + float3(11, 17, 23));
   c.b = noise(P + float3(57, 93, 65));
   c.a = noise(P + float3(77, 15, 111));
//    return c*0.5+0.5;
   return abs(c);
}

// Scalar noise
float GenerateNoise1f(float3 Pos : POSITION) : COLOR
{
   float3 P = Pos*VOLUME_SIZE;
//    return noise(P)*0.5+0.5;
   return abs(noise(P));
}

// Tracked matricies
float4x4 wvp : WorldViewProjection <string UIWidget="WVP";>;
float4x4 world : World <string UIWidget="World";>;

//////////////////////////////

// Structures
struct appdata {
   float3 Position    : POSITION;
   float4 UV        : TEXCOORD0;
   float4 Tangent    : TANGENT0;
   float4 Binormal    : BINORMAL0;
   float4 Normal    : NORMAL;
};

struct vertexOutput {
   float4 HPosition    : POSITION;
   float3 NoisePos     : TEXCOORD0;
   float3 FlamePos     : TEXCOORD1;
   float2 UV           : TEXCOORD2;
};

// Vertex shader
vertexOutput flameVS(appdata IN,
                   uniform float4x4 WorldViewProj,
                   uniform float4x4 World,
                   uniform float3 noiseScale,                    
                   uniform float noiseFreq,
                   uniform float3 noiseAnim,
                   uniform float3 flameScale,
                   uniform float3 flameTrans,
                   uniform float timeScale
                   )
{
   vertexOutput OUT;
   float4 objPos = float4(IN.Position.x,IN.Position.y,IN.Position.z,1.0);
   float3 worldPos = mul(objPos, World).xyz;

   OUT.HPosition = mul(objPos, WorldViewProj);
   float time = fmod(ticks, 10.0);    // avoid large texcoords
   OUT.NoisePos = worldPos*noiseScale*noiseFreq + time*timeScale*noiseAnim;
   OUT.FlamePos = worldPos*flameScale + flameTrans;
   
   // Mod by C.Humphrey so flame can be anywhere in the scene.    
   IN.Position.y += Index;    
   OUT.FlamePos.xz = IN.Position.xy;
   OUT.FlamePos.y = IN.Position.z;        
   // End Mod
   
   OUT.UV = IN.UV;
   return OUT;
}

// Pixel shaders
half4 noise3D(uniform sampler3D NoiseMap, float3 P)
{
   //return tex3D(NoiseMap, P)*2-1;
   return tex3D(NoiseMap, P);
}

half4 turbulence4(uniform sampler3D NoiseMap, float3 P)
{
   half4 sum = noise3D(NoiseMap, P)*0.5 +
                noise3D(NoiseMap, P*2)*0.25 +
                noise3D(NoiseMap, P*4)*0.125 +
               noise3D(NoiseMap, P*8)*0.0625;
   return sum;
}

half4 flamePS(vertexOutput IN,
             uniform sampler3D NoiseMap,
             uniform sampler2D FlameTex,
             uniform half noiseStrength,
             uniform half4 flameColor
             ) : COLOR
{
//    return tex3D(NoiseMap,IN.NoisePos) * flameColor;
//  return turbulence4(NoiseMap, IN.NoisePos) * flameColor;

   half2 uv;
   uv.x = length(IN.FlamePos.xz);    // radial distance in XZ plane    
   uv.y = IN.FlamePos.y;    
   
   //uv.y += turbulence4(NoiseMap, IN.NoisePos) * noiseStrength;
   uv.y += turbulence4(NoiseMap, IN.NoisePos) * noiseStrength / uv.x;    
       
   return tex2D(FlameTex, uv) * flameColor;
}

/****************************************************/
/********** SAMPLERS ********************************/
/****************************************************/

sampler3D noiseTextureSampler = sampler_state
{
   Texture = <noiseTexture>;
   MinFilter = Linear;
   MagFilter = Linear;
   MipFilter = Linear;
};

sampler2D flameTextureSampler = sampler_state
{
   Texture = <flameTexture>;
   MinFilter = Linear;
   MagFilter = Linear;
   MipFilter = Linear;
   AddressU = Clamp;
   AddressV = Clamp;    
};


/****************************************************/
/********** TECHNIQUES ******************************/
/****************************************************/

technique ps20 <string Script = "Pass=p1d;";>
{
   pass p1d <string Script = "Draw=geometry;";>
   {        
       VertexShader = compile vs_1_1 flameVS(wvp, world,noiseScale, noiseFreq, noiseAnim, flameScale, flameTrans, timeScale);
/*
       ZEnable = true;
       ZWriteEnable = true;
       CullMode = None;

       AlphaBlendEnable = true;
       BlendOp = Add;        
       SrcBlend = One;
       DestBlend = One;
*/    
       PixelShader = compile ps_2_0 flamePS(noiseTextureSampler, flameTextureSampler, noiseStrength, flameColor);
   }
}

/***************************** eof ***/


I suggest you don't use this code or shader as it is really not of much use in a 3D world. Once I have some spare time I will make this fully functional. I guess if nothing else you can have a play with the shader and learn from it...

Tuesday, July 17, 2007

Shader Fog

Have found out how to do fog in the shader so this means that you can now have fog in your XBox 360 games as well as your Windows based games.

As usual it is a matter of adding the right semantic and setting the correct renderstate in the shader.

So, the code.

Shader Model 1.1 - 2
In your Shader's vertex return structure, add the following

float Fog : FOG;



In your vertex shader set the fog you require:

Output.Fog = 1.0f - ( length( Input.Position - EyePosition) / 50.0f);


Note: This is an example you can generate this how ever you need to.

Now all you have to do is set the render state for fog, so in the top of your pass add this

FOGENABLE = (3);
FOGCOLOR = (float4(.5,.5,.5,1));



Where 3 is Linear fog and float4(.5,.5,.5,1) would be the color gray.

Shader Model 3
To get fog working on SM 3 and/or the XBox 360 is a little different

  • Remove ALL state settings in the technique.
  • Swap the FOG simantic for a TEXCOORD.
  • As the FOGCOLOR state has been removed, you need to
       calulate this in the shader per vertex lerp between the
       calculated color and the fog color based on the FOG
       TEXCOORD

    For Example:

    Out.Color = lerp(g_FogColor, color * ((In.Diffuse * g_MaterialDiffuseColor) * diffuse + g_MaterialAmbientColor), In.Fog);



    Thanks to Leaf at XNA UK User Group and Jon Watte for your help with this I am sure this will help many others in there XNA development. Jon's shader also has the ability to specify the height of the fog and so giving a much richer fog effect, nice...

    If you have any interest in the discussions we had then take a look here or the comments on this post.

    Leaf has also put up an example of Jon's shader in action and you can find that here.
  • 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!