Google
 

Friday, August 17, 2007

Generic XNA - SM3 Fog



So here is the SM3 (Shader Model 3) HLSL fog. Now this is taken pretty much 99% from Leaf's example, but thought I would comment on how easy this technique is to put into your existing shaders.

I also found that adding this to a shader pushes the operation count up a fair bit, the limit in SM2 is 64 operations per shader, so 128 in all, 64 in the Vertex Shader and another 64 in the Pixel Shader. So What I did was put the fog code into a second pass so giving me another 64x64 operation slots to play with.



So here are the shaders:

This first shader is the one used for the buildings you see in the shots, it is a single pass and a simple texture shader.

ShaderFog.fx

float4x4 World : World;
float4x4 WorldViewProject : WorldViewProjection;
float3 EyePosition : CameraPosition;
texture thisTexture;
float3  LightPos  = (0,-1,0);

float4 ambient = {0.25, 0.25, 0.25, 1.0};
float4 diffuse = {1.0, 1.0, 1.0, 1.0};
float4 specularColor = {0.2, 0.2, 0.2, 1.0};
float shininess = 40;

float fogNear = 10;
float fogFar = 100;
float fogAltitudeScale = 10;
float fogThinning = 100;
float4 fogColor = {0.5, 0.5, 0.5, 1.0};

sampler TextureSampler = sampler_state
{
   Texture = <thisTexture>;
};

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

struct VS_OUTPUT
{
   float4 Position : POSITION;
   float2 Texcoord : TEXCOORD0;
   float3 Normal : TEXCOORD1;    
   float3 WorldPos : TEXCOORD2;
};
struct PS_INPUT
{
   float4 Position : TEXCOORD4;
   float2 Texcoord : TEXCOORD0;
   float3 Normal : TEXCOORD1;    
   float3 WorldPos : TEXCOORD2;
};

float4 blinn2(
       float3 N,
       float3 L,
       float3 V,
       uniform float4 diffuseColor,
       uniform float4 specularColor,
       uniform float shininess)
{
   float3 H = normalize(V+L);
   float4 lighting = lit(dot(L,N), dot(H,N), shininess);
   return diffuseColor*lighting.y + specularColor*lighting.z;
}


VS_OUTPUT Transform(VS_INPUT Input)
{
   VS_OUTPUT Output;

   Output.WorldPos = mul(Input.Position,World);
   Output.Position = mul(Input.Position, WorldViewProject);
   Output.Texcoord = Input.Texcoord;
   Output.Normal = mul(Input.Normal,World);

   return Output;
}

float4 Texture(PS_INPUT Input) : COLOR0
{
   float4 colorMap = tex2D(TextureSampler, Input.Texcoord.xy) * 1.5;
   float3 N = normalize(Input.Normal);
   float3 V = normalize(EyePosition - Input.WorldPos);
   float3 L = normalize(LightPos - Input.WorldPos);
   
   float4 C = ambient*colorMap;
   
   C += blinn2(N, L, V, colorMap * diffuse, specularColor * colorMap.a, shininess);
   float d = length(Input.WorldPos - EyePosition);
   float l = saturate((d - fogNear) / (fogFar - fogNear) / clamp(Input.Position.y / fogAltitudeScale + 1, 1, fogThinning));
   return lerp(C, fogColor, l);    
};

technique TransformTexture
{
   pass P0
   {
       VertexShader = compile vs_1_1 Transform();
       PixelShader  = compile ps_2_0 Texture();
   }
}



This is the shader I use for the floor tile, again this is a single pass. Just a variation on the theam really.

Floor.fx

float4x4 World : World;
float4x4 WorldViewProject : WorldViewProjection;
float3 EyePosition : CameraPosition;
texture thisTexture;
float3  LightPos  = (0,-1,0);

float4 ambient = {0.25, 0.25, 0.25, 1.0};
float4 diffuse = {1.0, 1.0, 1.0, 1.0};
float4 specularColor = {0.2, 0.2, 0.2, 1.0};
float shininess = 0;

float fogNear = 10;
float fogFar = 100;
float fogAltitudeScale = 10;
float fogThinning = 100;
float4 fogColor = {0.5, 0.5, 0.5, 1.0};

sampler TextureSampler = sampler_state
{
   Texture = <thisTexture>;
};

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

struct VS_OUTPUT
{
   float4 Position : POSITION;
   float2 Texcoord : TEXCOORD0;
   float3 Normal : TEXCOORD1;    
   float3 WorldPos : TEXCOORD2;
};
struct PS_INPUT
{
   float4 Position : TEXCOORD4;
   float2 Texcoord : TEXCOORD0;
   float3 Normal : TEXCOORD1;    
   float3 WorldPos : TEXCOORD2;
};

float4 blinn2(
       float3 N,
       float3 L,
       float3 V,
       uniform float4 diffuseColor,
       uniform float4 specularColor,
       uniform float shininess)
{
   float3 H = normalize(V+L);
   float4 lighting = lit(dot(L,N), dot(H,N), shininess);
   return diffuseColor*lighting.y + specularColor*lighting.z;
}


VS_OUTPUT Transform(VS_INPUT Input)
{
   VS_OUTPUT Output;

   Output.WorldPos = mul(Input.Position,World);
   Output.Position = mul(Input.Position, WorldViewProject);
   Output.Texcoord = Input.Texcoord * 500;
   Output.Normal = mul(Input.Normal,World);

   return Output;
}

float4 Texture(PS_INPUT Input) : COLOR0
{
   float4 colorMap = tex2D(TextureSampler, Input.Texcoord.xy);
   float3 N = normalize(Input.Normal);
   float3 V = normalize(EyePosition - Input.WorldPos);
   float3 L = normalize(LightPos - Input.WorldPos);
   
   float4 C = ambient*colorMap;
   
   C += blinn2(N, L, V, colorMap * diffuse, specularColor * colorMap.a, shininess);
   float d = length(Input.WorldPos - EyePosition);
   float l = saturate((d - fogNear) / (fogFar - fogNear) / clamp(Input.Position.y / fogAltitudeScale + 1, 1, fogThinning));
   return lerp(C, fogColor, l);
};

technique TransformTexture
{
   pass P0
   {
       VertexShader = compile vs_1_1 Transform();
       PixelShader  = compile ps_2_0 Texture();
   }
}


And finaly this is the shader used for the sky box, now, you may think, why is he using a sky box when he has such dense fog? Well it is here as an example and this is where I have put the fog calcs into the second pass so you can see how to do it (or rather how I did it)


/////////////////////////////////////////////////////////////
//                                                            //
//    Writen by C.Humphrey                                    //
//    26/07/2007                                                //
//                                                            //
//                                                            //
//    Shader used to render a cube map to an inverted box        //
//    mesh.                                                    //
//                                                            //
// 17/08/2006 Multi pass fog aded.                            //
//                                                            //
//////////////////////////////////////////////////////////////

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;

float4 ambient = {0.25, 0.25, 0.25, 1.0};
float4 diffuse = {1.0, 1.0, 1.0, 1.0};
float4 specularColor = {0.2, 0.2, 0.2, 1.0};
float shininess = 10;

float fogNear = 500;
float fogFar = 1000;
float fogAltitudeScale = 0;
float fogThinning = 1;
float4 fogColor = {0.5, 0.5, 0.5, 1.0};

float4 c;

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

struct VS_OUTPUT
{
   float4 Position    : POSITION0;
   float3 ViewDirection : TEXCOORD2;
   float3 Normal : TEXCOORD0;
   float4 WorldPos : TEXCOORD1;
};

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;    
   
   Output.WorldPos = mul(Input.Position,World);
   Output.Normal = mul(Input.Normal,World);
   
   return Output;
}

float4 blinn2(
       float3 N,
       float3 L,
       float3 V,
       uniform float4 diffuseColor,
       uniform float4 specularColor,
       uniform float shininess)
{
   float3 H = normalize(V+L);
   float4 lighting = lit(dot(L,N), dot(H,N), shininess);
   return diffuseColor*lighting.y + specularColor*lighting.z;
}

struct PS_INPUT
{    
   float3 ViewDirection : TEXCOORD2;
   float3 Normal : TEXCOORD0;
   float3 WorldPos : TEXCOORD1;
   float4 Position : TEXCOORD3;
};

float4 BasicShader(PS_INPUT Input) : COLOR0
{    
   float3 ViewDirection = normalize(Input.ViewDirection);        
   float4 C = CubeMapLookup(-ViewDirection);
 
   c = C;
   return C;
}
float4 Fog(PS_INPUT Input) : COLOR0
{
   float3 ViewDirection = normalize(Input.ViewDirection);        
   
   float3 N = normalize(Input.Normal);
   float3 V = normalize(EyePosition - Input.WorldPos);
   float3 L = normalize(Input.WorldPos);
   L = normalize(Input.WorldPos);
   
   float4 C = c;
 
   C += blinn2(N, L, V, C * diffuse, specularColor * C.a, shininess);
   float d = fogFar-(fogFar/35);
   float l = saturate((d - fogNear) / (fogFar - fogNear) / clamp(Input.Position.y / fogAltitudeScale + 1, 1, fogThinning));
   return lerp(C, fogColor, l);
}

technique BasicShader
{
   pass P0
   {
       VertexShader = compile vs_2_0 Transform();
       PixelShader  = compile ps_2_0 BasicShader();
   }
   pass P1
   {
       AlphaBlendEnable    = true;
       SrcBlend = SrcAlpha;
       DestBlend = InvSrcAlpha;
       PixelShader  = compile ps_2_0 Fog();
   }
}




Controls
Mouse rotates camera
Arrow Keys translate camera
Esc to exit.

GenericExampleShaderFog.zip

Thanks to Leaf for his example on the XNA UK User Group Without it I would not know how to do it...

3 comments:

  1. Why you call it SM 3 Fog, when you use SM 2.0? The instruction limits in SM 2.0 are 256 for Vertex Shader and 32 (texture) + 64 (arithmetic) for the Pixel Shader. If you use SM 2.x you got 96 to 512 instructions for the Pixel Shader depending on your hardware.

    ReplyDelete
  2. I call it SM3 fog as the method will work in SM3, I am using SM2 as my laptop can't do SM3, if you set it as SM2 and compile for the 360 it wil compile as SM3 by default (I think).

    Oops, thought it was 64 for each, any how the extra calcs are in the pixel shader so, as shown, if you exceed 64 (arithmetic) slots (SM2) then you can add another pass to do the fog.

    Thanks for the heads up on the shaders stats though.

    I guess I should take more time compiling my posts eh..

    ReplyDelete
  3. SM 2.x is only the Vertex Shader, on the pixel shader it is 2.0a and 2.0b, 2.0a is Nvidia Geforce FX only, and 2.0b is Radeon x700+ only. So you would ONLY use those in say, a fragment shader where it will actually run on.
    SM 2.0 will run on Geforce FX and Radeon 9500+, whilst 2.0b will not run on the 9500, making it only good for programatically constructed/combined shaders where you can detect the hardware and then compile appropriately. As such this is SM3 becuase when writing a shader file for any hardware you have to unfortunately go with the setting that is not hardware specific.
    (And since there is currently no way to compile shaders at runtime for the content pipeline, we have to specify a shader model beforehand, so we cannot use a or b properly.

    This is a tutorial after all.

    ReplyDelete

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