[SOLVED] Cascaded Shadow Maps 3.5(worked) 3.6(don't worked)

can someone help me with this demo, i cant make it work in the latest version. it’ don’t show any shadow. It work on monogame version v3.5.

My version source
https://drive.google.com/file/d/0BwtN8HNVzAZzdXJFb3hhVGNMZlk/view?usp=sharing

original version source

Are you using @kosmonautgames’ engine ?
The sample uses z/w to compute the depth, whereas the other one uses linear (from what I remember)

EDIT: I tried your code, and when i make the app draw the rendertarget of the shadowmap, it is full red => empty.

Probably a problem with the sampler declaration:

Texture2DArray ShadowMap : register(t0);
SamplerComparisonState ShadowSampler : register(s0);

it’s work for u? im talking about compatibility, is same project …i had try it on monogame v3.5 and it worked, but it don’t work on monogame v3.6.

is any1 know setting do i ned in order to make it work in v3.6?

I tried too, the default source from git. The shadow map cascade is correct.

Edit: I think i fixed it.

it looks like the SamplerComparisonState was not initialized correctly/ not working correctly. This is a general monogame problem right now. The sampler states are simply not doing what they are supposed to.

All of this is in the Mesh.fx

An easy fix is to change the SamplerComparisonState to a SamplerState like so

SamplerState ShadowSampler : register(s0);

and make manual comparisons instead, so replace
Ln 81
return ShadowMap.SampleCmpLevelZero(ShadowSampler, float3(shadowPos.xy, cascadeIdx), lightDepth);
with
return ShadowMap.SampleLevel(ShadowSampler, float3(uv, cascadeIdx), 0) > z;

Ln 116
return ShadowMap.SampleCmpLevelZero(ShadowSampler, float3(shadowPos.xy, cascadeIdx), lightDepth);
with
return ShadowMap.SampleLevel(ShadowSampler, float3(shadowPos.xy, cascadeIdx), 0) > lightDepth;

Here is the correct mesh.fx

static const uint NumCascades = 4;

// Parameters.

matrix World;
matrix ViewProjection;

float3 CameraPosWS;
matrix ShadowMatrix;
float4 CascadeSplits;
float4 CascadeOffsets[NumCascades];
float4 CascadeScales[NumCascades];

float3 LightDirection;
float3 LightColor;
float3 DiffuseColor;

float Bias;
float OffsetScale;

// Resources.

Texture2DArray ShadowMap : register(t0);

SamplerState ShadowSampler : register(s0);

// Structures.

struct VSInput
{
    float3 PositionOS : SV_POSITION;
    float3 NormalOS   : NORMAL;
    float3 Uv         : TEXCOORD0;
};

struct VSOutput
{
    float4 PositionCS : SV_Position;
    float3 PositionWS : POSITIONWS;
    float3 NormalWS   : NORMALWS;
    float DepthVS     : DEPTHVS;
};

struct PSInput
{
    float4 PositionSS : SV_Position;
    float3 PositionWS : POSITIONWS;
    float3 NormalWS   : NORMALWS;
    float DepthVS     : DEPTHVS;
};

// Vertex shader.

VSOutput VSMesh(VSInput input)
{
    VSOutput output;

    // Calculate the world space position.
    output.PositionWS = mul(float4(input.PositionOS, 1), World).xyz;

    // Calculate the clip space position.
    output.PositionCS = mul(float4(output.PositionWS, 1), ViewProjection);

    output.DepthVS = output.PositionCS.w;

    // Rotate the normal into world space.
    output.NormalWS = normalize(mul(input.NormalOS, (float3x3) World));

    return output;
}

// Pixel shader.

float SampleShadowMap(
    float2 baseUv, float u, float v, float2 shadowMapSizeInv,
    uint cascadeIdx, float depth)
{
    float2 uv = baseUv + float2(u, v) * shadowMapSizeInv;
    float z = depth;

    return ShadowMap.SampleLevel(ShadowSampler, float3(uv, cascadeIdx), 0) > z;
}

float SampleShadowMapOptimizedPCF(float3 shadowPos,
    float3 shadowPosDX, float3 shadowPosDY,
    uint cascadeIdx, uint filterSize)
{
    float2 shadowMapSize;
    float numSlices;
    ShadowMap.GetDimensions(shadowMapSize.x, shadowMapSize.y, numSlices);

    float lightDepth = shadowPos.z;

    const float bias = Bias;

    lightDepth -= bias;

    float2 uv = shadowPos.xy * shadowMapSize; // 1 unit - 1 texel

        float2 shadowMapSizeInv = 1.0 / shadowMapSize;

        float2 baseUv;
    baseUv.x = floor(uv.x + 0.5);
    baseUv.y = floor(uv.y + 0.5);

    float s = (uv.x + 0.5 - baseUv.x);
    float t = (uv.y + 0.5 - baseUv.y);

    baseUv -= float2(0.5, 0.5);
    baseUv *= shadowMapSizeInv;

    float sum = 0;

    if (filterSize == 2)
    {
        return ShadowMap.SampleLevel(ShadowSampler, float3(shadowPos.xy, cascadeIdx), 0) > lightDepth;
    }
    else if (filterSize == 3)
    {
        float uw0 = (3 - 2 * s);
        float uw1 = (1 + 2 * s);

        float u0 = (2 - s) / uw0 - 1;
        float u1 = s / uw1 + 1;

        float vw0 = (3 - 2 * t);
        float vw1 = (1 + 2 * t);

        float v0 = (2 - t) / vw0 - 1;
        float v1 = t / vw1 + 1;

        sum += uw0 * vw0 * SampleShadowMap(baseUv, u0, v0, shadowMapSizeInv, cascadeIdx, lightDepth);
        sum += uw1 * vw0 * SampleShadowMap(baseUv, u1, v0, shadowMapSizeInv, cascadeIdx, lightDepth);
        sum += uw0 * vw1 * SampleShadowMap(baseUv, u0, v1, shadowMapSizeInv, cascadeIdx, lightDepth);
        sum += uw1 * vw1 * SampleShadowMap(baseUv, u1, v1, shadowMapSizeInv, cascadeIdx, lightDepth);

        return sum * 1.0f / 16;
    }
    else if (filterSize == 5)
    {
        float uw0 = (4 - 3 * s);
        float uw1 = 7;
        float uw2 = (1 + 3 * s);

        float u0 = (3 - 2 * s) / uw0 - 2;
        float u1 = (3 + s) / uw1;
        float u2 = s / uw2 + 2;

        float vw0 = (4 - 3 * t);
        float vw1 = 7;
        float vw2 = (1 + 3 * t);

        float v0 = (3 - 2 * t) / vw0 - 2;
        float v1 = (3 + t) / vw1;
        float v2 = t / vw2 + 2;

        sum += uw0 * vw0 * SampleShadowMap(baseUv, u0, v0, shadowMapSizeInv, cascadeIdx, lightDepth);
        sum += uw1 * vw0 * SampleShadowMap(baseUv, u1, v0, shadowMapSizeInv, cascadeIdx, lightDepth);
        sum += uw2 * vw0 * SampleShadowMap(baseUv, u2, v0, shadowMapSizeInv, cascadeIdx, lightDepth);

        sum += uw0 * vw1 * SampleShadowMap(baseUv, u0, v1, shadowMapSizeInv, cascadeIdx, lightDepth);
        sum += uw1 * vw1 * SampleShadowMap(baseUv, u1, v1, shadowMapSizeInv, cascadeIdx, lightDepth);
        sum += uw2 * vw1 * SampleShadowMap(baseUv, u2, v1, shadowMapSizeInv, cascadeIdx, lightDepth);

        sum += uw0 * vw2 * SampleShadowMap(baseUv, u0, v2, shadowMapSizeInv, cascadeIdx, lightDepth);
        sum += uw1 * vw2 * SampleShadowMap(baseUv, u1, v2, shadowMapSizeInv, cascadeIdx, lightDepth);
        sum += uw2 * vw2 * SampleShadowMap(baseUv, u2, v2, shadowMapSizeInv, cascadeIdx, lightDepth);

        return sum * 1.0f / 144;
    }
    else // filterSize == 7
    {
        float uw0 = (5 * s - 6);
        float uw1 = (11 * s - 28);
        float uw2 = -(11 * s + 17);
        float uw3 = -(5 * s + 1);

        float u0 = (4 * s - 5) / uw0 - 3;
        float u1 = (4 * s - 16) / uw1 - 1;
        float u2 = -(7 * s + 5) / uw2 + 1;
        float u3 = -s / uw3 + 3;

        float vw0 = (5 * t - 6);
        float vw1 = (11 * t - 28);
        float vw2 = -(11 * t + 17);
        float vw3 = -(5 * t + 1);

        float v0 = (4 * t - 5) / vw0 - 3;
        float v1 = (4 * t - 16) / vw1 - 1;
        float v2 = -(7 * t + 5) / vw2 + 1;
        float v3 = -t / vw3 + 3;

        sum += uw0 * vw0 * SampleShadowMap(baseUv, u0, v0, shadowMapSizeInv, cascadeIdx, lightDepth);
        sum += uw1 * vw0 * SampleShadowMap(baseUv, u1, v0, shadowMapSizeInv, cascadeIdx, lightDepth);
        sum += uw2 * vw0 * SampleShadowMap(baseUv, u2, v0, shadowMapSizeInv, cascadeIdx, lightDepth);
        sum += uw3 * vw0 * SampleShadowMap(baseUv, u3, v0, shadowMapSizeInv, cascadeIdx, lightDepth);

        sum += uw0 * vw1 * SampleShadowMap(baseUv, u0, v1, shadowMapSizeInv, cascadeIdx, lightDepth);
        sum += uw1 * vw1 * SampleShadowMap(baseUv, u1, v1, shadowMapSizeInv, cascadeIdx, lightDepth);
        sum += uw2 * vw1 * SampleShadowMap(baseUv, u2, v1, shadowMapSizeInv, cascadeIdx, lightDepth);
        sum += uw3 * vw1 * SampleShadowMap(baseUv, u3, v1, shadowMapSizeInv, cascadeIdx, lightDepth);

        sum += uw0 * vw2 * SampleShadowMap(baseUv, u0, v2, shadowMapSizeInv, cascadeIdx, lightDepth);
        sum += uw1 * vw2 * SampleShadowMap(baseUv, u1, v2, shadowMapSizeInv, cascadeIdx, lightDepth);
        sum += uw2 * vw2 * SampleShadowMap(baseUv, u2, v2, shadowMapSizeInv, cascadeIdx, lightDepth);
        sum += uw3 * vw2 * SampleShadowMap(baseUv, u3, v2, shadowMapSizeInv, cascadeIdx, lightDepth);

        sum += uw0 * vw3 * SampleShadowMap(baseUv, u0, v3, shadowMapSizeInv, cascadeIdx, lightDepth);
        sum += uw1 * vw3 * SampleShadowMap(baseUv, u1, v3, shadowMapSizeInv, cascadeIdx, lightDepth);
        sum += uw2 * vw3 * SampleShadowMap(baseUv, u2, v3, shadowMapSizeInv, cascadeIdx, lightDepth);
        sum += uw3 * vw3 * SampleShadowMap(baseUv, u3, v3, shadowMapSizeInv, cascadeIdx, lightDepth);

        return sum * 1.0f / 2704;
    }
}

float3 SampleShadowCascade(
    float3 shadowPosition, 
    float3 shadowPosDX, float3 shadowPosDY,
    uint cascadeIdx, uint2 screenPos,
    bool visualizeCascades,
    uint filterSize)
{
    shadowPosition += CascadeOffsets[cascadeIdx].xyz;
    shadowPosition *= CascadeScales[cascadeIdx].xyz;

    shadowPosDX *= CascadeScales[cascadeIdx].xyz;
    shadowPosDY *= CascadeScales[cascadeIdx].xyz;

    float3 cascadeColor = float3(1.0f, 1.0f, 1.0f);

    if (visualizeCascades)
    {
        const float3 CascadeColors[NumCascades] =
        {
            float3(1.0f, 0.0f, 0.0f),
            float3(0.0f, 1.0f, 0.0f),
            float3(0.0f, 0.0f, 1.0f),
            float3(1.0f, 1.0f, 0.0f)
        };

        cascadeColor = CascadeColors[cascadeIdx];
    }

    // TODO: Other shadow map modes.

    float shadow = SampleShadowMapOptimizedPCF(shadowPosition, shadowPosDX, shadowPosDY, cascadeIdx, filterSize);

    return shadow * cascadeColor;
}

float3 GetShadowPosOffset(float nDotL, float3 normal)
{
    float2 shadowMapSize;
    float numSlices;
    ShadowMap.GetDimensions(shadowMapSize.x, shadowMapSize.y, numSlices);

    float texelSize = 2.0f / shadowMapSize.x;
    float nmlOffsetScale = saturate(1.0f - nDotL);
    return texelSize * OffsetScale * nmlOffsetScale * normal;
}

float3 ShadowVisibility(
    float3 positionWS, float depthVS, float nDotL, 
    float3 normal, uint2 screenPos, 
    bool filterAcrossCascades,
    bool visualizeCascades,
    uint filterSize)
{
    float3 shadowVisibility = 1.0f;
    uint cascadeIdx = 0;

    // Figure out which cascade to sample from.
    [unroll]
    for (uint i = 0; i < NumCascades - 1; ++i)
    {
        [flatten]
        if (depthVS > CascadeSplits[i])
            cascadeIdx = i + 1;
    }

    // Apply offset
    float3 offset = GetShadowPosOffset(nDotL, normal) / abs(CascadeScales[cascadeIdx].z);

    // Project into shadow space
    float3 samplePos = positionWS + offset;
    float3 shadowPosition = mul(float4(samplePos, 1.0f), ShadowMatrix).xyz;
    float3 shadowPosDX = ddx_fine(shadowPosition);
    float3 shadowPosDY = ddy_fine(shadowPosition);

    shadowVisibility = SampleShadowCascade(shadowPosition, 
        shadowPosDX, shadowPosDY, cascadeIdx, screenPos,
        visualizeCascades, filterSize);

    if (filterAcrossCascades)
    {
        // Sample the next cascade, and blend between the two results to
        // smooth the transition
        const float BlendThreshold = 0.1f;
        float nextSplit = CascadeSplits[cascadeIdx];
        float splitSize = cascadeIdx == 0 ? nextSplit : nextSplit - CascadeSplits[cascadeIdx - 1];
        float splitDist = (nextSplit - depthVS) / splitSize;

        [branch]
        if (splitDist <= BlendThreshold && cascadeIdx != NumCascades - 1)
        {
            float3 nextSplitVisibility = SampleShadowCascade(shadowPosition,
                shadowPosDX, shadowPosDY, cascadeIdx + 1, screenPos,
                visualizeCascades, filterSize);
            float lerpAmt = smoothstep(0.0f, BlendThreshold, splitDist);
            shadowVisibility = lerp(nextSplitVisibility, shadowVisibility, lerpAmt);
        }
    }

    return shadowVisibility;
}

float4 PSMesh(PSInput input,
    bool visualizeCascades, bool filterAcrossCascades, 
    uint filterSize)
{
    // Normalize after interpolation.
    float3 normalWS = normalize(input.NormalWS);

    // Convert color to grayscale, just beacuse it looks nicer.
    float diffuseValue = 0.299 * DiffuseColor.r + 0.587 * DiffuseColor.g + 0.114 * DiffuseColor.b;
	float3 diffuseAlbedo = float3(diffuseValue, diffuseValue, diffuseValue);

    float nDotL = saturate(dot(normalWS, LightDirection));
    uint2 screenPos = uint2(input.PositionSS.xy);
    float3 shadowVisibility = ShadowVisibility(
        input.PositionWS, input.DepthVS, nDotL, normalWS, screenPos, 
        filterAcrossCascades, visualizeCascades, filterSize);

    float3 lighting = 0.0f;

    // Add the directional light.
    lighting += nDotL * LightColor * diffuseAlbedo * (1.0f / 3.14159f) * shadowVisibility;

    // Ambient light.
    lighting += float3(0.2f, 0.2f, 0.2f) * 1.0f * diffuseAlbedo;

    return float4(max(lighting, 0.0001f), 1);
}

float4 PSMeshVisualizeFalseFilterFalseFilterSizeFilter2x2(PSInput input) : COLOR
{
    return PSMesh(input, false, false, 2);
}

float4 PSMeshVisualizeTrueFilterFalseFilterSizeFilter2x2(PSInput input) : COLOR
{
    return PSMesh(input, true, false, 2);
}

float4 PSMeshVisualizeFalseFilterFalseFilterSizeFilter3x3(PSInput input) : COLOR
{
    return PSMesh(input, false, false, 3);
}

float4 PSMeshVisualizeTrueFilterFalseFilterSizeFilter3x3(PSInput input) : COLOR
{
    return PSMesh(input, true, false, 3);
}

float4 PSMeshVisualizeFalseFilterFalseFilterSizeFilter5x5(PSInput input) : COLOR
{
    return PSMesh(input, false, false, 5);
}

float4 PSMeshVisualizeTrueFilterFalseFilterSizeFilter5x5(PSInput input) : COLOR
{
    return PSMesh(input, true, false, 5);
}

float4 PSMeshVisualizeFalseFilterFalseFilterSizeFilter7x7(PSInput input) : COLOR
{
    return PSMesh(input, false, false, 7);
}

float4 PSMeshVisualizeTrueFilterFalseFilterSizeFilter7x7(PSInput input) : COLOR
{
    return PSMesh(input, true, false, 7);
}

float4 PSMeshVisualizeFalseFilterTrueFilterSizeFilter2x2(PSInput input) : COLOR
{
    return PSMesh(input, false, true, 2);
}

float4 PSMeshVisualizeTrueFilterTrueFilterSizeFilter2x2(PSInput input) : COLOR
{
    return PSMesh(input, true, true, 2);
}

float4 PSMeshVisualizeFalseFilterTrueFilterSizeFilter3x3(PSInput input) : COLOR
{
    return PSMesh(input, false, true, 3);
}

float4 PSMeshVisualizeTrueFilterTrueFilterSizeFilter3x3(PSInput input) : COLOR
{
    return PSMesh(input, true, true, 3);
}

float4 PSMeshVisualizeFalseFilterTrueFilterSizeFilter5x5(PSInput input) : COLOR
{
    return PSMesh(input, false, true, 5);
}

float4 PSMeshVisualizeTrueFilterTrueFilterSizeFilter5x5(PSInput input) : COLOR
{
    return PSMesh(input, true, true, 5);
}

float4 PSMeshVisualizeFalseFilterTrueFilterSizeFilter7x7(PSInput input) : COLOR
{
    return PSMesh(input, false, true, 7);
}

float4 PSMeshVisualizeTrueFilterTrueFilterSizeFilter7x7(PSInput input) : COLOR
{
    return PSMesh(input, true, true, 7);
}

// Techniques.

#define VS_PROFILE vs_5_0
#define PS_PROFILE ps_5_0

technique VisualizeFalseFilterFalseFilterSizeFilter2x2
{
    pass
    {
        VertexShader = compile VS_PROFILE VSMesh();
        PixelShader = compile PS_PROFILE PSMeshVisualizeFalseFilterFalseFilterSizeFilter2x2();
    }
}

technique VisualizeTrueFilterFalseFilterSizeFilter2x2
{
    pass
    {
        VertexShader = compile VS_PROFILE VSMesh();
        PixelShader = compile PS_PROFILE PSMeshVisualizeTrueFilterFalseFilterSizeFilter2x2();
    }
}

technique VisualizeFalseFilterFalseFilterSizeFilter3x3
{
    pass
    {
        VertexShader = compile VS_PROFILE VSMesh();
        PixelShader = compile PS_PROFILE PSMeshVisualizeFalseFilterFalseFilterSizeFilter3x3();
    }
}

technique VisualizeTrueFilterFalseFilterSizeFilter3x3
{
    pass
    {
        VertexShader = compile VS_PROFILE VSMesh();
        PixelShader = compile PS_PROFILE PSMeshVisualizeTrueFilterFalseFilterSizeFilter3x3();
    }
}

technique VisualizeFalseFilterFalseFilterSizeFilter5x5
{
    pass
    {
        VertexShader = compile VS_PROFILE VSMesh();
        PixelShader = compile PS_PROFILE PSMeshVisualizeFalseFilterFalseFilterSizeFilter5x5();
    }
}

technique VisualizeTrueFilterFalseFilterSizeFilter5x5
{
    pass
    {
        VertexShader = compile VS_PROFILE VSMesh();
        PixelShader = compile PS_PROFILE PSMeshVisualizeTrueFilterFalseFilterSizeFilter5x5();
    }
}

technique VisualizeFalseFilterFalseFilterSizeFilter7x7
{
    pass
    {
        VertexShader = compile VS_PROFILE VSMesh();
        PixelShader = compile PS_PROFILE PSMeshVisualizeFalseFilterFalseFilterSizeFilter7x7();
    }
}

technique VisualizeTrueFilterFalseFilterSizeFilter7x7
{
    pass
    {
        VertexShader = compile VS_PROFILE VSMesh();
        PixelShader = compile PS_PROFILE PSMeshVisualizeTrueFilterFalseFilterSizeFilter7x7();
    }
}

technique VisualizeFalseFilterTrueFilterSizeFilter2x2
{
    pass
    {
        VertexShader = compile VS_PROFILE VSMesh();
        PixelShader = compile PS_PROFILE PSMeshVisualizeFalseFilterTrueFilterSizeFilter2x2();
    }
}

technique VisualizeTrueFilterTrueFilterSizeFilter2x2
{
    pass
    {
        VertexShader = compile VS_PROFILE VSMesh();
        PixelShader = compile PS_PROFILE PSMeshVisualizeTrueFilterTrueFilterSizeFilter2x2();
    }
}

technique VisualizeFalseFilterTrueFilterSizeFilter3x3
{
    pass
    {
        VertexShader = compile VS_PROFILE VSMesh();
        PixelShader = compile PS_PROFILE PSMeshVisualizeFalseFilterTrueFilterSizeFilter3x3();
    }
}

technique VisualizeTrueFilterTrueFilterSizeFilter3x3
{
    pass
    {
        VertexShader = compile VS_PROFILE VSMesh();
        PixelShader = compile PS_PROFILE PSMeshVisualizeTrueFilterTrueFilterSizeFilter3x3();
    }
}

technique VisualizeFalseFilterTrueFilterSizeFilter5x5
{
    pass
    {
        VertexShader = compile VS_PROFILE VSMesh();
        PixelShader = compile PS_PROFILE PSMeshVisualizeFalseFilterTrueFilterSizeFilter5x5();
    }
}

technique VisualizeTrueFilterTrueFilterSizeFilter5x5
{
    pass
    {
        VertexShader = compile VS_PROFILE VSMesh();
        PixelShader = compile PS_PROFILE PSMeshVisualizeTrueFilterTrueFilterSizeFilter5x5();
    }
}

technique VisualizeFalseFilterTrueFilterSizeFilter7x7
{
    pass
    {
        VertexShader = compile VS_PROFILE VSMesh();
        PixelShader = compile PS_PROFILE PSMeshVisualizeFalseFilterTrueFilterSizeFilter7x7();
    }
}

technique VisualizeTrueFilterTrueFilterSizeFilter7x7
{
    pass
    {
        VertexShader = compile VS_PROFILE VSMesh();
        PixelShader = compile PS_PROFILE PSMeshVisualizeTrueFilterTrueFilterSizeFilter7x7();
    }
}

Note that the shadows are still broken though, since the texture samplers in monogame are just doing random stuff now and do not sample the way they are supposed to. Had the same problem previously in my engine.

Interesting problem really, their comparison pixel is not the direct neighbor but the one behind (2 pixels apart, instead of 1)

thanks for help, i think should report to dev team this issue.

Exactly the part I pointed to :wink:

Just change the shadow map evaluation sampler state in SamplerStateUtility to this:

public static readonly SamplerState ShadowMap = new SamplerState
         {
             AddressU = TextureAddressMode.Clamp,
             AddressV = TextureAddressMode.Clamp,
             AddressW = TextureAddressMode.Clamp,
             Filter = TextureFilter.Linear,
             ComparisonFunction = CompareFunction.LessEqual,
             FilterMode = TextureFilterMode.Comparison 
         };

We have to manually set the filter mode to comparison, because we want to use SamplerComparisonState in the CSM shader.

2 Likes

hi @kosmonautgames can you guide me how to implement this with your deferred playground demo. I tried to do it, but the shadow didn’t show out.