Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

My terrain shader does quite a bit, but I don't know how to break it up in any way that will reduce my instruction count without removing key features of the terrain. It does the following:

  • Multi-texture splatting (allows for 3 textures to be painted onto the terrain).
  • Each texture from splatting also has a normal map.
  • Directional lighting
  • Allows shadows to cast onto it
  • Calculates if a pixel of terrain is underwater, and colors the terrain differently based on how deep underwater it is.
  • Calculates is the camera is underwater, and applies a "water fog" in that case.
  • Specularity

The most expensive part of the terrain is the part it has to do if it's underwater, but I don't know of any way to break that up into a separate shader technique, unless I rendered all of the terrain twice and used a clipping plane at the water elevation. I'm currently down to 69 instructions, but need to get down to 64 instructions to run on SM 2.0. You can critique the shader itself if that will get me down to 64 instructions, but really I'm wondering about how other engines would manage to get these features into an SM 2.0 shader.

Here's the shader if it helps (yea, I know it's a bit ugly, I'm not a graphics programmer usually):

//------- Constants --------
float4x4 View               : VIEW;
float4x4 Projection         : PROJECTION;
float4x4 World              : WORLD;
float4x4 WorldViewProjection: WORLDVIEWPROJECTION;
float4x4 LightViewProjection: LIGHTVIEWPROJECTION;
float4 LightDir             : LIGHT_DIRECTION;
float4 AmbientColor         : AMBIENT_COLOR;
float AmbientPower          : AMBIENT_POWER;
float4 SpecularColor        : SPECULAR_COLOR;
float SpecularPower         : SPECULAR_POWER;
float4 DiffuseColor         : DIFFUSE_COLOR;
float4 CameraForward        : VIEW_FORWARD;
float4 CameraPos            : VIEW_POS;
float FogNear               : FOG_NEAR;
float FogFar                : FOG_FAR;
float4 FogColor             : FOG_COLOR;
float FogAltitude           : FOG_ALTITUDE;
float FogThinning           : FOG_THINNING;

float TerrainScale          : SCALE_FACTOR;
float TerrainWidth          : TERRAIN_WIDTH;

float WaterElevation        : WATER_ELEVATION;
float4 WaterColor           : WATER_COLOR;

bool UsingClippingPlane     : USING_CLIPPING_PLANE;
float4 ClippingPlane        : CLIPPING_PLANE;

float DepthBias = 0.0001f;

float4 darkWater = float4(0.0f, 0.156f, 0.467f, 1.0f);
float4 lightWater = float4(0.0f, 0.345f, 0.6f, 1.0f);

//------- Texture Samplers --------
Texture TextureMap          : TEXTURE_MAP;
sampler TextureMapSampler = sampler_state { texture = <TextureMap> ; magfilter = LINEAR; minfilter = LINEAR; 
                                                                         mipfilter = LINEAR; AddressU  = Wrap;
                                                                         AddressV  = Wrap; AddressW  = Wrap;};

Texture GrassTexture        : GRASS_TEXTURE;
sampler GrassTextureSampler = sampler_state { texture = <GrassTexture> ; magfilter = LINEAR; minfilter = LINEAR; 
                                                                         mipfilter=LINEAR; AddressU  = Wrap;
                                                                         AddressV  = Wrap; AddressW  = Wrap;};

Texture SandTexture         : SAND_TEXTURE;
sampler SandTextureSampler = sampler_state { texture = <SandTexture> ; magfilter = LINEAR; minfilter = LINEAR; 
                                                                       mipfilter =LINEAR; AddressU  = Wrap;
                                                                       AddressV  = Wrap; AddressW  = Wrap;};

Texture RockTexture         : ROCK_TEXTURE;
sampler RockTextureSampler = sampler_state { texture = <RockTexture> ; magfilter = LINEAR; minfilter = LINEAR; 
                                                                       mipfilter = LINEAR; AddressU  = Wrap;
                                                                       AddressV  = Wrap; AddressW  = Wrap;};

Texture GrassNormal         :GRASS_NORMAL;
sampler2D GrassNormalSampler : TEXUNIT1 = sampler_state
{ Texture   = (GrassNormal); magfilter = LINEAR; minfilter = LINEAR; 
                             mipfilter = LINEAR; AddressU  = Wrap;
                             AddressV  = Wrap; AddressW  = Wrap;};

Texture SandNormal          : SAND_NORMAL;
sampler2D SandNormalSampler : TEXUNIT1 = sampler_state
{ Texture   = (SandNormal); magfilter  = LINEAR; minfilter = LINEAR; 
                             mipfilter = LINEAR; AddressU  = Wrap;
                             AddressV  = Wrap; AddressW  = Wrap;};

Texture RockNormal          : ROCK_NORMAL;
sampler2D RockNormalSampler : TEXUNIT1 = sampler_state
{ Texture   = (RockNormal); magfilter = LINEAR; minfilter = LINEAR; 
                             mipfilter=LINEAR; AddressU  = Wrap;
                             AddressV  = Wrap; AddressW  = Wrap;};

Texture ShadowMap               : SHADOW_MAP;   
sampler ShadowMapSampler = sampler_state { texture = <ShadowMap>; MinFilter = POINT; MagFilter = POINT;
                                                                  MipFilter = NONE;  AddressU = Clamp;
                                                                  AddressV = Clamp;  AddressW  = Wrap; };



// For any shader that can support shadow mapping, simply add 
// this function and call it from your pixel shader. Of course,
// you'll need to make sure this shader and material are setup to
// receive the ShadowMap sampler, and any needed parameters (e.g. LightViewProjection)                                                        
float4 ComputeShadowColor(float4 worldPos, float4 Color)
{
     // Find the position of this pixel in light space
    float4 lightingPosition = mul(worldPos, LightViewProjection);

    // Find the position in the shadow map for this pixel
    float2 ShadowTexCoord = 0.5 * lightingPosition.xy / 
                            lightingPosition.w + float2( 0.5, 0.5 );
    ShadowTexCoord.y = 1.0f - ShadowTexCoord.y;

    // Get the current depth stored in the shadow map
    float4 shadowInfo = tex2D(ShadowMapSampler, ShadowTexCoord);
    float shadowdepth = shadowInfo.r;
    float shadowOpacity = 0.5f + 0.5f * (1 - shadowInfo.g);

    // Calculate the current pixel depth
    // The bias is used to prevent folating point errors that occur when
    // the pixel of the occluder is being drawn
    float ourdepth = (lightingPosition.z / lightingPosition.w) - DepthBias;

    // Check to see if this pixel is in front or behind the value in the shadow map
    if ( shadowdepth < ourdepth)
    {
        // Shadow the pixel by lowering the intensity
        Color *= float4(shadowOpacity, shadowOpacity, shadowOpacity, 1);
    };

    return Color;
}

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

struct VS_OUTPUT
{
    float4 Position     : POSITION;
    float4 TexCoord     : TEXCOORD0;
    float3 Normal       : TEXCOORD1;
    float4 WorldPos     : TEXCOORD2;
    float4 ClipDistances    : TEXCOORD3;
};

VS_OUTPUT MultiTexturedNormaledVS( VS_INPUT input)    
{
     VS_OUTPUT Output;

     Output.Position = mul(input.Position, WorldViewProjection);
     Output.WorldPos = mul(input.Position, World);
     Output.Normal = normalize(mul(input.Normal, World));

     Output.TexCoord.x = input.Position.x * 0.03f / TerrainScale;
     Output.TexCoord.y = input.Position.z * 0.03f / TerrainScale;

     Output.TexCoord.z = input.Position.x / (TerrainWidth * TerrainScale);
     Output.TexCoord.w = input.Position.z / (TerrainWidth * TerrainScale);

     Output.ClipDistances = dot(Output.WorldPos, ClippingPlane);

     return Output;    
}

float4 MultiTexturedNormaledPS(VS_OUTPUT input) : COLOR0
{
     if ( UsingClippingPlane )
     {
         clip(input.ClipDistances);
     }

     float3 TerrainColorWeight = tex2D(TextureMapSampler, input.TexCoord.zw);

     // @TODO: Determine if this quad-tree node has all three textures on it, so we don't have to multiply by
     //        unneeded values.
     float4 normalFromMap = (2.0f * tex2D(RockNormalSampler, input.TexCoord) - 1.0f)    * TerrainColorWeight.r;
     normalFromMap += (2.0f * tex2D(GrassNormalSampler, input.TexCoord) - 1.0f)         * TerrainColorWeight.g;
     normalFromMap += (2.0f * tex2D(SandNormalSampler, input.TexCoord) - 1.0f)          * TerrainColorWeight.b;

     float4 Color = tex2D(RockTextureSampler, input.TexCoord)   * TerrainColorWeight.r;
     Color += tex2D(GrassTextureSampler, input.TexCoord)        * TerrainColorWeight.g;
     Color += tex2D(SandTextureSampler, input.TexCoord)         * TerrainColorWeight.b;

     // Factor in normal mapping and terrain vertex normals as well in lighting of the pixel
     float lightingFactor = saturate(dot(normalFromMap + input.Normal, -LightDir));

     float4 WorldPosToCam = float4(input.WorldPos - CameraPos);
     float d = length(WorldPosToCam);

     Color = ComputeShadowColor(input.WorldPos, Color);

     // Handle fog differently if there is water, and also handle if the camera is underwater
     float waterTerrainDiff = WaterElevation - input.WorldPos.y;

     float fogValue = FogThinning;
     float fogAlt = FogAltitude;
     float fogNearDist = FogNear;
     float fogFarDist = FogFar;
     float4 fogColor = FogColor;

     // If this pixel on the terrain is underwater, and the water elevation is not the default value
     if ( waterTerrainDiff > 0 )
     {
         if ( CameraPos.y < WaterElevation )
         {
            fogValue = 1;   // Setting FogThinning to 1 makes elevation-based fog become distance-based
            fogAlt = WaterElevation;
            fogNearDist = -200;
            fogFarDist = 250;
            fogColor = WaterColor;
         }
         else
         {      
            fogValue = 1;   // Setting FogThinning to 1 makes elevation-based fog become distance-based
            fogAlt = WaterElevation;
            fogNearDist = 0;
            fogFarDist = 10;
            d = waterTerrainDiff;

            waterTerrainDiff = clamp(waterTerrainDiff, 0, 100);
            fogColor = lerp(lightWater, darkWater, waterTerrainDiff / 100.f);
         }
     }

     float l = saturate((d - fogNearDist) / (fogFarDist - fogNearDist) / clamp(input.WorldPos.y / fogAlt + 1, 1, fogValue));

     Color.rgb *= (AmbientColor + (DiffuseColor * lightingFactor) + (SpecularColor * lightingFactor));
     Color.a = 1.0f;

     return lerp(Color, fogColor, l);
}

technique MultiTexturedNormaled
{
     pass Pass0
     {
         VertexShader = compile vs_1_1 MultiTexturedNormaledVS();
         PixelShader = compile ps_2_0 MultiTexturedNormaledPS();
     }
}
share|improve this question

2 Answers 2

up vote 1 down vote accepted

I was able to solve this problem by optimizing the shader.

This first change will get us down from 68 instructions to 67. We have logic in the shader that only occurs for pixels of terrain that are below the water level, you can see this logic separated by this code:

if ( waterTerrainDiff > 0 )
{
}

Within that was this:

waterTerrainDiff = clamp(waterTerrainDiff, 0, 100);

The clamp is to keep the value between 0 and 100, but we already know it must be greater than zero because of the condition that (waterTerrainDiff > 0), so instead of clamp was can use min:

waterTerrainDiff = min(waterTerrainDiff, 100);

This brings us down to 68 instruction slots. Now, on the next line of the shader was this:

fogColor = lerp(lightWater, darkWater, waterTerrainDiff / 100.f);

We were clamping the value of waterTerrainDiff between 0 and 100, but then just dividing the value by 100.f, so if we include the division in the min call, then we save another instruction, which brings us down to 67.

waterTerrainDiff = min(waterTerrainDiff / 100.f, 1.f);
fogColor = lerp(lightWater, darkWater, waterTerrainDiff);

And the second important change, will bring us from 67 instructions down to 64, which is the exact number we need. We had a way to split up the parts of the shader that occurred when the camera was underwater, with the part when the camera that was above the water. So this code below, will get split into two seperate shader techniques:

if ( waterTerrainDiff > 0 )
{
    if ( CameraPos.y < WaterElevation )
    {
        fogValue = 1;   // Setting FogThinning to 1 makes elevation-based fog become distance-based
        fogAlt = WaterElevation;
        fogNearDist = -200;
        fogFarDist = 250;
        fogColor = WaterColor;
    }
    else
    {      
        fogValue = 1;   // Setting FogThinning to 1 makes elevation-based fog become distance-based
        fogAlt = WaterElevation;
        fogNearDist = 0;
        fogFarDist = 10;
        d = waterTerrainDiff;

        waterTerrainDiff = min(waterTerrainDiff / 100.f, 1.f);
        fogColor = lerp(lightWater, darkWater, waterTerrainDiff);
    }
}

This goes in the shader technique that is called when the camera is underwater:

if ( waterTerrainDiff > 0 )
{
    fogValue = 1;
    fogAlt = WaterElevation;
    fogNearDist = -200;
    fogFarDist = 250;
    fogColor = WaterColor;
}

And here's the part in the shader technique that is called when the camera is above the water:

if ( waterTerrainDiff > 0 )
{
    fogValue = 1;
    fogAlt = WaterElevation;
    fogNearDist = 0;
    fogFarDist = 10;
    d = waterTerrainDiff;

    waterTerrainDiff = min(waterTerrainDiff / 100.f, 1.f);
    fogColor = lerp(lightWater, darkWater, waterTerrainDiff);
}

Here's a screenshot of the shader in action.

enter image description here

Below is the full resulting shader after the changes. It's quite a bit larger, but it meets the necessary criteria.

    //------- Constants --------
float4x4 View               : VIEW;
float4x4 Projection         : PROJECTION;
float4x4 World              : WORLD;
float4x4 WorldViewProjection: WORLDVIEWPROJECTION;
float4x4 LightViewProjection: LIGHTVIEWPROJECTION;
float4 LightDir             : LIGHT_DIRECTION;
float4 AmbientColor         : AMBIENT_COLOR;
float AmbientPower          : AMBIENT_POWER;
float4 SpecularColor        : SPECULAR_COLOR;
float SpecularPower         : SPECULAR_POWER;
float4 DiffuseColor         : DIFFUSE_COLOR;
float4 CameraForward        : VIEW_FORWARD;
float4 CameraPos            : VIEW_POS;
float FogNear               : FOG_NEAR;
float FogFar                : FOG_FAR;
float4 FogColor             : FOG_COLOR;
float FogAltitude           : FOG_ALTITUDE;
float FogThinning           : FOG_THINNING;

float TerrainScale          : SCALE_FACTOR;
float TerrainWidth          : TERRAIN_WIDTH;

float WaterElevation        : WATER_ELEVATION;
float4 WaterColor           : WATER_COLOR;

bool UsingClippingPlane     : USING_CLIPPING_PLANE;
float4 ClippingPlane        : CLIPPING_PLANE;

bool CameraUnderwater       : CAMERA_UNDERWATER;

float DepthBias = 0.0001f;

float4 darkWater = float4(0.0f, 0.126f, 0.367f, 1.0f);
float4 lightWater = float4(0.0f, 0.235f, 0.44f, 1.0f);

//------- Texture Samplers --------
Texture TextureMap          : TEXTURE_MAP;
sampler TextureMapSampler = sampler_state { texture = <TextureMap> ; magfilter = LINEAR; minfilter = LINEAR; 
                                                                         mipfilter = LINEAR; AddressU  = Wrap;
                                                                         AddressV  = Wrap; AddressW  = Wrap;};

Texture GrassTexture        : GRASS_TEXTURE;
sampler GrassTextureSampler = sampler_state { texture = <GrassTexture> ; magfilter = LINEAR; minfilter = LINEAR; 
                                                                         mipfilter=LINEAR; AddressU  = Wrap;
                                                                         AddressV  = Wrap; AddressW  = Wrap;};

Texture SandTexture         : SAND_TEXTURE;
sampler SandTextureSampler = sampler_state { texture = <SandTexture> ; magfilter = LINEAR; minfilter = LINEAR; 
                                                                       mipfilter =LINEAR; AddressU  = Wrap;
                                                                       AddressV  = Wrap; AddressW  = Wrap;};

Texture RockTexture         : ROCK_TEXTURE;
sampler RockTextureSampler = sampler_state { texture = <RockTexture> ; magfilter = LINEAR; minfilter = LINEAR; 
                                                                       mipfilter = LINEAR; AddressU  = Wrap;
                                                                       AddressV  = Wrap; AddressW  = Wrap;};

Texture GrassNormal         :GRASS_NORMAL;
sampler2D GrassNormalSampler : TEXUNIT1 = sampler_state
{ Texture   = (GrassNormal); magfilter = LINEAR; minfilter = LINEAR; 
                             mipfilter = LINEAR; AddressU  = Wrap;
                             AddressV  = Wrap; AddressW  = Wrap;};

Texture SandNormal          : SAND_NORMAL;
sampler2D SandNormalSampler : TEXUNIT1 = sampler_state
{ Texture   = (SandNormal); magfilter  = LINEAR; minfilter = LINEAR; 
                             mipfilter = LINEAR; AddressU  = Wrap;
                             AddressV  = Wrap; AddressW  = Wrap;};

Texture RockNormal          : ROCK_NORMAL;
sampler2D RockNormalSampler : TEXUNIT1 = sampler_state
{ Texture   = (RockNormal); magfilter = LINEAR; minfilter = LINEAR; 
                             mipfilter=LINEAR; AddressU  = Wrap;
                             AddressV  = Wrap; AddressW  = Wrap;};

Texture ShadowMap               : SHADOW_MAP;   
sampler ShadowMapSampler = sampler_state { texture = <ShadowMap>; MinFilter = POINT; MagFilter = POINT;
                                                                  MipFilter = NONE;  AddressU = Clamp;
                                                                  AddressV = Clamp;  AddressW  = Wrap; };



// For any shader that can support shadow mapping, simply add 
// this function and call it from your pixel shader. Of course,
// you'll need to make sure this shader and material are setup to
// receive the ShadowMap sampler, and any needed parameters (e.g. LightViewProjection)                                                        
float4 ComputeShadowColor(float4 worldPos, float4 Color)
{
     // Find the position of this pixel in light space
    float4 lightingPosition = mul(worldPos, LightViewProjection);

    // Find the position in the shadow map for this pixel
    float2 ShadowTexCoord = 0.5 * lightingPosition.xy / 
                            lightingPosition.w + float2( 0.5, 0.5 );
    ShadowTexCoord.y = 1.0f - ShadowTexCoord.y;

    // Get the current depth stored in the shadow map
    float4 shadowInfo = tex2D(ShadowMapSampler, ShadowTexCoord);
    float shadowdepth = shadowInfo.r;
    float shadowOpacity = 0.5f + 0.5f * (1 - shadowInfo.g);

    // Calculate the current pixel depth
    // The bias is used to prevent folating point errors that occur when
    // the pixel of the occluder is being drawn
    float ourdepth = (lightingPosition.z / lightingPosition.w) - DepthBias;

    // Check to see if this pixel is in front or behind the value in the shadow map
    if ( shadowdepth < ourdepth)
    {
        // Shadow the pixel by lowering the intensity
        Color *= float4(shadowOpacity, shadowOpacity, shadowOpacity, 1);
    };

    return Color;
}

//------- Technique: MultiTexturedNormaled --------

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

struct VS_OUTPUT
{
    float4 Position     : POSITION;
    float4 TexCoord     : TEXCOORD0;
    float3 Normal       : TEXCOORD1;
    float4 WorldPos     : TEXCOORD2;
    float4 ClipDistances    : TEXCOORD3;
};

 VS_OUTPUT MultiTexturedNormaledVS( VS_INPUT input)    
 {
     VS_OUTPUT Output;

     Output.Position = mul(input.Position, WorldViewProjection);
     Output.WorldPos = mul(input.Position, World);
     Output.Normal = normalize(mul(input.Normal, World));

     Output.TexCoord.x = input.Position.x * 0.03f / TerrainScale;
     Output.TexCoord.y = input.Position.z * 0.03f / TerrainScale;

     Output.TexCoord.z = input.Position.x / (TerrainWidth * TerrainScale);
     Output.TexCoord.w = input.Position.z / (TerrainWidth * TerrainScale);

     Output.ClipDistances = dot(Output.WorldPos, ClippingPlane);

     return Output;    
 }

 float4 MultiTexturedNormaledPS(VS_OUTPUT input) : COLOR0
 {
     if ( UsingClippingPlane )
     {
         clip(input.ClipDistances);
     }

     float3 TerrainColorWeight = tex2D(TextureMapSampler, input.TexCoord.zw);

     // @TODO: Determine if this quad-tree node has all three textures on it, so we don't have to multiply by
     //        unneeded values.
     float4 normalFromMap = (2.0f * tex2D(RockNormalSampler, input.TexCoord) - 1.0f)    * TerrainColorWeight.r;
     normalFromMap += (2.0f * tex2D(GrassNormalSampler, input.TexCoord) - 1.0f)         * TerrainColorWeight.g;
     normalFromMap += (2.0f * tex2D(SandNormalSampler, input.TexCoord) - 1.0f)          * TerrainColorWeight.b;

     float4 Color = tex2D(RockTextureSampler, input.TexCoord)   * TerrainColorWeight.r;
     Color += tex2D(GrassTextureSampler, input.TexCoord)        * TerrainColorWeight.g;
     Color += tex2D(SandTextureSampler, input.TexCoord)         * TerrainColorWeight.b;

     // Factor in normal mapping and terrain vertex normals as well in lighting of the pixel
     float lightingFactor = saturate(dot(normalFromMap + input.Normal, -LightDir));

     float4 WorldPosToCam = float4(input.WorldPos - CameraPos);
     float d = length(WorldPosToCam);

     Color = ComputeShadowColor(input.WorldPos, Color);

     // Handle fog differently if there is water, and also handle if the camera is underwater
     float waterTerrainDiff = WaterElevation - input.WorldPos.y;

     float fogValue = FogThinning;
     float fogAlt = FogAltitude;
     float fogNearDist = FogNear;
     float fogFarDist = FogFar;
     float4 fogColor = FogColor;

     // If this pixel on the terrain is underwater, and the water elevation is not the default value
     if ( waterTerrainDiff > 0 )
     {
        fogValue = 1;   // Setting FogThinning to 1 makes elevation-based fog become distance-based
        fogAlt = WaterElevation;
        fogNearDist = 0;
        fogFarDist = 30;
        d = waterTerrainDiff;

        waterTerrainDiff = min(waterTerrainDiff / 100.f, 1.f);
        fogColor = lerp(lightWater, darkWater, waterTerrainDiff);
     }

     float l = saturate((d - fogNearDist) / (fogFarDist - fogNearDist) / clamp(input.WorldPos.y / fogAlt + 1, 1, fogValue));

     Color.rgb *= (AmbientColor + (DiffuseColor * lightingFactor) + (SpecularColor * lightingFactor));
     Color.a = 1.0f;

     return lerp(Color, fogColor, l);
 }

 technique MultiTexturedNormaled
 {
     pass Pass0
     {
         VertexShader = compile vs_2_0 MultiTexturedNormaledVS();
         PixelShader = compile ps_2_0 MultiTexturedNormaledPS();
     }
 }

 float4 MultiTexturedNormaledCameraUnderwaterPS(VS_OUTPUT input) : COLOR0
 {
     if ( UsingClippingPlane )
     {
         clip(input.ClipDistances);
     }

     float3 TerrainColorWeight = tex2D(TextureMapSampler, input.TexCoord.zw);

     // @TODO: Determine if this quad-tree node has all three textures on it, so we don't have to multiply by
     //        unneeded values.
     float4 normalFromMap = (2.0f * tex2D(RockNormalSampler, input.TexCoord) - 1.0f)    * TerrainColorWeight.r;
     normalFromMap += (2.0f * tex2D(GrassNormalSampler, input.TexCoord) - 1.0f)         * TerrainColorWeight.g;
     normalFromMap += (2.0f * tex2D(SandNormalSampler, input.TexCoord) - 1.0f)          * TerrainColorWeight.b;

     float4 Color = tex2D(RockTextureSampler, input.TexCoord)   * TerrainColorWeight.r;
     Color += tex2D(GrassTextureSampler, input.TexCoord)        * TerrainColorWeight.g;
     Color += tex2D(SandTextureSampler, input.TexCoord)         * TerrainColorWeight.b;

     // Factor in normal mapping and terrain vertex normals as well in lighting of the pixel
     float lightingFactor = saturate(dot(normalFromMap + input.Normal, -LightDir));

     float4 WorldPosToCam = float4(input.WorldPos - CameraPos);
     float d = length(WorldPosToCam);

     Color = ComputeShadowColor(input.WorldPos, Color);

     // Handle fog differently if there is water, and also handle if the camera is underwater
     float waterTerrainDiff = WaterElevation - input.WorldPos.y;

     float fogValue = FogThinning;
     float fogAlt = FogAltitude;
     float fogNearDist = FogNear;
     float fogFarDist = FogFar;
     float4 fogColor = FogColor;

     // If this pixel on the terrain is underwater, and the water elevation is not the default value
     if ( waterTerrainDiff > 0 )
     {
        fogValue = 1;   // Setting FogThinning to 1 makes elevation-based fog become distance-based
        fogAlt = WaterElevation;
        fogNearDist = -200;
        fogFarDist = 250;
        fogColor = WaterColor;
     }

     float l = saturate((d - fogNearDist) / (fogFarDist - fogNearDist) / clamp(input.WorldPos.y / fogAlt + 1, 1, fogValue));

     Color.rgb *= (AmbientColor + (DiffuseColor * lightingFactor) + (SpecularColor * lightingFactor));
     Color.a = 1.0f;

     return lerp(Color, fogColor, l);
 }

 technique MultiTexturedNormaledCameraUnderwater
 {
     pass Pass0
     {
         VertexShader = compile vs_2_0 MultiTexturedNormaledVS();
         PixelShader = compile ps_2_0 MultiTexturedNormaledCameraUnderwaterPS();
     }
 }
share|improve this answer
    
Neat, glad you fixed it. –  Will Apr 11 '13 at 5:07
    
Couldn't the under water stuff be done by the water shader ... since technically water is either a deep blue or taken at least part form the terrain below using refraction and and reflection in varying amounts based on depth. Water would also be rendered "in place of" a terrain pixel where the heightmap is lower than the height of the water. –  Wardy Feb 10 at 12:13

Calculates is the camera is underwater, and applies a "water fog" in that case.

The calculation could be done by you on the CPU and passed in instead?

but really I'm wondering about how other engines would manage to get these features into an SM 2.0 shader

Sorry I went the shader critique route :/

In my terrain rendering I often bake colours in textures and the tainting for depth underwater (or whitening for high peaks) could be precomputed in this way.

share|improve this answer
    
Yes that could be done on the CPU, that would likely only save me 1 arithmetic instruction though, but that would bring me closer, I'll give it a shot. –  Nic Foster Apr 10 '13 at 13:48
    
Just tried it, didn't save me an instruction slot. –  Nic Foster Apr 10 '13 at 13:53

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.