1
\$\begingroup\$

I'm figuring out shadowmapping in HLSL (see also this question). I understand that I need projection matrices for each cascading shadowmap.

I use this code to create the projections:

    Matrix CreateLightViewProjectionMatrix(LightProjectionDistance distance)
    {
        // Matrix with that will rotate in points the direction of the light
        Matrix lightRotation = Matrix.CreateLookAt(Vector3.Zero,
                                                   -_lightDir,
                                                   Vector3.Up);

        BoundingFrustum cameraFrustum = new BoundingFrustum(_view);
        switch (distance)
        {
            case LightProjectionDistance.Close:
                cameraFrustum = new BoundingFrustum(_view * _projectionClose);
                break;
            case LightProjectionDistance.Medium:
                cameraFrustum = new BoundingFrustum(_view * _projectionMedium);
                break;
            case LightProjectionDistance.Far:
                cameraFrustum = new BoundingFrustum(_view * _projectionFar);
                break;
        }

        // Get the corners of the frustum
        Vector3[] frustumCorners = cameraFrustum.GetCorners();

        // Transform the positions of the corners into the direction of the light
        for (int i = 0; i < frustumCorners.Length; i++)
        {
            frustumCorners[i] = Vector3.Transform(frustumCorners[i], lightRotation);
        }

        // Find the smallest box around the points
        BoundingBox lightBox = BoundingBox.CreateFromPoints(frustumCorners);

        Vector3 boxSize = lightBox.Max - lightBox.Min;
        Vector3 halfBoxSize = boxSize * 0.5f;

        // The position of the light should be in the center of the back
        // pannel of the box. 
        Vector3 lightPosition = lightBox.Min + halfBoxSize;
        lightPosition.Z = lightBox.Min.Z;

        // We need the position back in world coordinates so we transform 
        // the light position by the inverse of the lights rotation
        lightPosition = Vector3.Transform(lightPosition,
                                          Matrix.Invert(lightRotation));

        // Create the view matrix for the light
        Matrix lightView = Matrix.CreateLookAt(lightPosition,
                                               lightPosition - _lightDir,
                                               Vector3.Up);

        // Create the projection matrix for the light
        // The projection is orthographic since we are using a directional light
        Matrix lightProjection = Matrix.CreateOrthographic(boxSize.X, boxSize.Y,
                                                           -boxSize.Z, boxSize.Z);

        return lightView * lightProjection;
    }

Where the view matrices are like this:

        float aspectRatio = (float)graphics.PreferredBackBufferWidth / (float)graphics.PreferredBackBufferHeight;
        _projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
                                                         aspectRatio,
                                                         1f, 500.0f);
        _projectionClose = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
                                                         aspectRatio,
                                                         1f, 20.0f);
        _projectionMedium = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
                                                         aspectRatio,
                                                         20.0f, 100.0f);
        _projectionFar = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
                                                         aspectRatio,
                                                         100.0f, 500.0f);

In my Pixelshader I try this:

diffuse = input.Color;
float4 lightingPosition = mul(input.WorldPos, LightViewProjClose);
if (lightingPosition.x > -0.5 && lightingPosition.x<0.5 && lightingPosition.y>-0.5 && lightingPosition.y < 0.5)
{
   diffuse = float4(1,0,0,1);
}

lightingPosition = mul(input.WorldPos, LightViewProjMedium);
if (lightingPosition.x > -0.5 && lightingPosition.x<0.5 && lightingPosition.y>-0.5 && lightingPosition.y < 0.5)
{
   diffuse = float4(0,1,0,1);
}

lightingPosition = mul(input.WorldPos, LightViewProjFar);
if (lightingPosition.x > -0.5 && lightingPosition.x<0.5 && lightingPosition.y>-0.5 && lightingPosition.y < 0.5)
{
   diffuse = float4(0,0,1,1);
}

return diffuse;

Now I expected to see the picture colored in red (close), green (medium) and blue (far) bands. However it seems only the close range is actually colored and the medium and far distances are not triggered. I did set the parameters like this:

_myEffect.Parameters["LightViewProjClose"].SetValue(CreateLightViewProjectionMatrix(LightProjectionDistance.Close));
        _myEffect.Parameters["LightViewProjMedium"].SetValue(CreateLightViewProjectionMatrix(LightProjectionDistance.Medium));
        _myEffect.Parameters["LightViewProjFar"].SetValue(CreateLightViewProjectionMatrix(LightProjectionDistance.Far));

I'm getting frustrated as I'm trying to figure this out for the past couple of days with almost no progress. What am I missing? How can I get the pixel shader switch between the correct light projections so it can pick the right cascaded shadowmap?

Edit, after tweaking this is my shader code:

float4x4 World;
float4x4 View;
float4x4 Projection;
float4x4 LightViewProjClose;
float4x4 LightViewProjMedium;
float4x4 LightViewProjFar;


float3 LightDirection1 = float3(-1.5, 0.45, 0);
float4 LightColor1 = float4(1, 1, 1, 1);
float3 LightDirection2 =float3(1,1,0.2);
float4 LightColor2 = float4(0.2, 0.2, 0.25, 1);


float4 AmbientColor = float4(0.15, 0.15, 0.15, 0.15);
float DepthBias = 0.001f;

texture ShadowMap;
sampler ShadowMapSampler = sampler_state
{
    Texture = <ShadowMap>;
};

struct VertexShaderInput
{
    float4 Position : SV_POSITION;
    float4 Normal : NORMAL0;
    float4 Color : COLOR0;
};

struct ShadowVertexShaderInput
{
    float4 Position : SV_POSITION;
    float4 Normal : NORMAL0;
    float4 Color : COLOR0;
};

struct VertexShaderOutput
{
    float4 Position : SV_POSITION;
    float4 Normal   : TEXCOORD0;
    float4 Color    : COLOR0;
    float4 WorldPos : TEXCOORD2;
};

struct CreateShadowMap_VSOut
{
    float4 Position : POSITION;
    float4 Color : COLOR0;
};

VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
    VertexShaderOutput output;

    float4x4 WorldViewProj = mul(mul(World, View), Projection);

    // Transform the models verticies and normal
    output.Position = mul(input.Position, WorldViewProj);
    output.Normal =  input.Normal;
    output.Color = input.Color;

    // Save the vertices postion in world space
    output.WorldPos = mul(input.Position, World);

    return output;
}

float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
    // Color of the model
    float4 diffuseColor = input.Color;

    // Intensity based on the direction of the light
    float diffuseIntensity1 = clamp(dot(input.Normal.xyz, LightDirection1.xyz), 0, 1);
    float diffuseIntensity2 = clamp(dot(input.Normal.xyz, LightDirection2.xyz), 0, 1);

    // Final diffuse color with ambient color added
    float4 diffuse = saturate((diffuseIntensity1 * LightColor1 + diffuseIntensity2 * LightColor2) * diffuseColor);

    // Find the position of this pixel in light space in the close projection
    float4 lightingPosition = mul(input.WorldPos, LightViewProjClose);
    lightingPosition.xyz = 0.5 * lightingPosition.xyz / lightingPosition.w;
    if (lightingPosition.x > -0.5 && lightingPosition.x<0.5 && lightingPosition.y>-0.5 && lightingPosition.y < 0.5)
    {
        lightingPosition = mul(input.WorldPos, LightViewProjClose);
        // 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 (red component for close)
        float shadowdepth = tex2D(ShadowMapSampler, ShadowTexCoord).r;

        // Calculate the current pixel depth
        // The bias is used to prevent floating 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
            diffuse *= float4(0.5, 0.5, 0.5, 0);

        };
    }

    lightingPosition = mul(input.WorldPos, LightViewProjMedium);
    lightingPosition.xyz = 0.5 * lightingPosition.xyz / lightingPosition.w;
    if (lightingPosition.x > -0.5 && lightingPosition.x<0.5 && lightingPosition.y>-0.5 && lightingPosition.y < 0.5)
    {

        lightingPosition = mul(input.WorldPos, LightViewProjMedium);
        // 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 (green component for medium)
        float shadowdepth = tex2D(ShadowMapSampler, ShadowTexCoord).g;

        // Calculate the current pixel depth
        // The bias is used to prevent floating 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
            diffuse *= float4(0.5, 0.5, 0.5, 0);
        };
    }

    //do the same trick using the 'far' matrix.
    lightingPosition = mul(input.WorldPos, LightViewProjFar);
    lightingPosition.xyz = 0.5 * lightingPosition.xyz / lightingPosition.w;
    if (lightingPosition.x > -0.5 && lightingPosition.x<0.5 && lightingPosition.y>-0.5 && lightingPosition.y < 0.5)
    {
        lightingPosition = mul(input.WorldPos, LightViewProjFar);

        // 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 (blue component for far)
        float shadowdepth = tex2D(ShadowMapSampler, ShadowTexCoord).b;

        // Calculate the current pixel depth
        // The bias is used to prevent floating 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
            diffuse *= float4(0.5, 0.5, 0.5, 0);
        };
        //diffuse = float4(0, 0, 1, 1);
    }
    return diffuse;
}


CreateShadowMap_VSOut ShadowCloseVertexShaderFunction(VertexShaderInput input)
{
    CreateShadowMap_VSOut output;

    output.Position = mul(input.Position, mul(World, LightViewProjClose));
    output.Color = float4(output.Position.z / output.Position.w,0,0,1);

    return output;
}
CreateShadowMap_VSOut ShadowMediumVertexShaderFunction(VertexShaderInput input)
{
    CreateShadowMap_VSOut output;

    output.Position = mul(input.Position, mul(World, LightViewProjMedium));
    output.Color = float4(0,output.Position.z / output.Position.w, 0, 1);

    return output;
}
CreateShadowMap_VSOut ShadowFarVertexShaderFunction(VertexShaderInput input)
{
    CreateShadowMap_VSOut output;

    output.Position = mul(input.Position, mul(World, LightViewProjFar));
    output.Color = float4(0,0,output.Position.z / output.Position.w, 1);

    return output;
}

float4 ShadowPixelShaderFunction(CreateShadowMap_VSOut input) : COLOR0
{
    return input.Color;
}

technique MyTechnique
{
    pass Pass1
    {
        VertexShader = compile vs_4_0_level_9_3 VertexShaderFunction();
        PixelShader = compile ps_4_0_level_9_3 PixelShaderFunction();
    }
}

technique ShadowTechniqueClose
{

    pass Pass1
    {
        VertexShader = compile vs_4_0_level_9_3 ShadowCloseVertexShaderFunction();
        PixelShader = compile ps_4_0_level_9_3 ShadowPixelShaderFunction();
    }
}
technique ShadowTechniqueMedium
{

    pass Pass1
    {
        VertexShader = compile vs_4_0_level_9_3 ShadowMediumVertexShaderFunction();
        PixelShader = compile ps_4_0_level_9_3 ShadowPixelShaderFunction();
    }
}
technique ShadowTechniqueFar
{

    pass Pass1
    {
        VertexShader = compile vs_4_0_level_9_3 ShadowFarVertexShaderFunction();
        PixelShader = compile ps_4_0_level_9_3 ShadowPixelShaderFunction();
    }
}

The "close" shadows seem to work. However the 'medium' and 'far' shadows are still not working. The result looks like this:

Close shadows look nice though

What am I doing wrong here?

\$\endgroup\$
  • \$\begingroup\$ Well, once you divided by the w component after 'mul' -ing with the matrix, you don't need to divide anymore for that value. Now you do it for the texcoords which is unnecessary. \$\endgroup\$ – János Turánszki Dec 25 '15 at 21:54
  • \$\begingroup\$ Ah, yes thanks for that. I did that optimization (I guess there is plenty of optimization possible). \$\endgroup\$ – Felsir Dec 26 '15 at 11:47
1
\$\begingroup\$

Maybe this is not the whole problem, but you need to divide by the w component after multiplying with a projection matrix.
So after float4 lightingPosition = mul(input.WorldPos, LightViewProjClose); but before the if you should do this:

lightingPosition.xyz /= lightingPosition.w
\$\endgroup\$
  • \$\begingroup\$ Thank you. I updated the code and I got the red, green and blue bands showing as I expected. However, the shadowmaps are not yet working. I tried splitting the shadowmap techniques so it would be easier to debug. The screenshot shows the result of the 'close' technique. I still can't get it to work. So far I haven't found an example online that has a working shadowshader with cascaded shadowmaps. I feel I'm lacking knowledge to pull this off. \$\endgroup\$ – Felsir Dec 25 '15 at 21:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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