Skip to main content
GameDev.net gamedev.net
🔒 Locked

Problems with shadow mapping (DirectX 11)

Started by DividedByZero Dec 9, 2025 at 10:49 PM 5 replies 2.1k views
Original Post
DividedByZero
DividedByZero

Hi Guys,

I've been working the past few days trying to figure out where I am going wrong with my shadow mapping. I'm using a mix of older code of mine (which I had working many years ago) and the RasterTek tutorials.

I seem to be able to generate a depth map happily enough from the perspective of the light source and mapping that to texture slot 1 in the pixel shader, as seen below.

I'm also sending the light information to the Vertex shader as per below.

cbuffer CB_WVP          : register(b0)
{
    matrix worldMatrix;
    matrix viewMatrix;
    matrix projectionMatrix;
}

cbuffer CB_Object       : register(b1)
{
    matrix worldMatrixObject;
}

cbuffer LightBuffer     : register(b2)
{
    matrix matLightWorld;
    matrix matLightView;
    matrix matLightProjection;
};

struct VS_Input
{
    float4 position             : POSITION;
    float3 normal			    : NORMAL;
    float2 texcoord             : TEXCOORD0;
};

struct VS_Output
{
    float4 position             : SV_POSITION;
    float3 normal   		    : NORMAL;
    float2 texcoord             : TEXCOORD0;
    float4 lightViewPosition	: TEXCOORD1;
};

VS_Output vs_main(VS_Input input)
{
    float4 objectSpacePos = mul(float4(input.position.xyz, 1.0), worldMatrixObject);

    // Model position
    VS_Output output;
    output.position = mul(objectSpacePos, worldMatrix);
    output.position = mul(output.position, viewMatrix);
    output.position = mul(output.position, projectionMatrix);
    output.normal = normalize(input.normal);
    output.texcoord = input.texcoord;

    // Light position
    output.lightViewPosition = mul(input.position, matLightWorld);
    output.lightViewPosition = mul(output.lightViewPosition, matLightView);
    output.lightViewPosition = mul(output.lightViewPosition, matLightProjection);

    return output;
}

And the pixel shader as follows. Not that I'm not attempting to apply any sort of light shading to the object at this stage, just purely trying to get the shadow on to the plane (for the sake of removing complexity and further errors).

Texture2D TextureStandard       : register(t0);
Texture2D TextureDepthMap       : register(t1);
SamplerState SamplerClamp       : register(s0);
SamplerState SamplerWrap 		: register(s1);


struct PS_Input
{
    float4 position             : SV_POSITION;
    float3 normal			    : NORMAL;
    float2 textureCoord         : TEXCOORD0;
    float4 lightViewPosition	: TEXCOORD1;
};

float4 ps_main(PS_Input input) : SV_TARGET
{
    float bias = 0.0005f;

    float2 texCoord;
    texCoord.x = input.lightViewPosition.x / input.lightViewPosition.w / 2.0f + 0.5f;
    texCoord.y = -input.lightViewPosition.y / input.lightViewPosition.w / 2.0f + 0.5f;

    if ((saturate(texCoord.x) == texCoord.x) && (saturate(texCoord.y) == texCoord.y))
    {
        float depthValue = TextureDepthMap.Sample(SamplerClamp, texCoord).r;
        float lightDepthValue = (input.lightViewPosition.z / input.lightViewPosition.w) - bias;

        if (lightDepthValue < depthValue)
        {
            // ***** NEVER GETS TO HERE *****
            // Out of shadow
            return TextureStandard.Sample(SamplerWrap, input.textureCoord) * 1.0f;
        }
        else
        {
            // In shadow
            float4 color = TextureStandard.Sample(SamplerWrap, input.textureCoord) * 0.5f;
            color.w = 1.0f;
            return color;
        }
    }

    // The remainder that is ouside of the shadow frustum
    return TextureStandard.Sample(SamplerWrap, input.textureCoord);

}

However the end result is as per below.

So I can see the pixel shader can ‘sort of see’ what area it is working with for the shadow map, but just casts the entire area in to a shadow.

Any help would be greatly appreciated. I have been battling with this for a few days now and have run out of ideas.

Many thanks! :D

DividedByZero
DividedByZero

I didn’t realise how quiet this place has gotten.

JoeJ
JoeJ

DividedByZero said:
I didn’t realise how quiet this place has gotten.

Even in crowded times they wouldn't have done your debugging for you, i'm afraid.

To get started, i propose you visualize the distance from shading point to occluder as given by the shadow map.
This should give more information than the current black square you get. And ideally some clue on what might be wrong.

DividedByZero
DividedByZero

I'm not asking for debugging. As you can see I have put a hell of a lot of effort in to debugging this myself also.

And yes, five years ago, assistance for posts like this would was phenomenal. I'm am currently cross referencing with a previous thread I made in 2019 where I was experiencing very similar issues and that thread is four pages long with fantastic assistance, where we got there in the end.

So, to say people would have walked past a post like this, where it can be seen I have put in a hell of a lot of effort and not the usual “Code my project for me” thread, is quite inaccurate.

Don't worry though. I'll work it out. I always do.

I've reported my topic to the Moderators and asked for it to be closed, seems we are now on Stack Overflow V2.

Aressera
Aressera
  • When you apply the light view matrix in the vertex shader, you are applying it to the object-space position, while it should be applied to the world-space position.
  • Why do you have two world-space matrices? (one for object, one for MVP). There should just be the per-object matrix going from object to world space, then the VP matrix going from world to camera then projection space.
  • Seems weird to be applying bias in post-projection space. Applying bias in world space works better:
    vec3 p = surfacePosition + geometryNormal*normalBias + directionalLight_direction[light]*lightBias;
  • You don't get hardware filtering because you compare the depth manually, therefore shadows will have hard pixelated edges. Hardware filtering will compare 4 times and bilinearly interpolate. In OpenGL you can pass in the comparison depth as 3rd texture coordinate. I don't know if similar functionality exists in DirectX.
  • I'd try removing that minus sign in the texCoord.y = -input.lightViewPosition.y…. Seems like there should be no need to flip vertically if DirectX is internally consistent with its image coordinate system.
  • Shouldn't the light projection matrix be doing viewport transformations from [-1,1] NDC to [0,1] UV? I think I do it all with a single matrix and no 0.5 + 0.5*NDC in the shader:
    matrix = UV_SCALE_MATRIX * lightView.getProjectionMatrix() * lightView.getTransformMatrixInverse();
  • I use the texture border capability and clamp sampler to avoid shadows outside the light viewport, rather than manually checking coordinates in the shader. I don't know if similar functionality exists in DirectX.
  • You are wasting some compute by multiplying by the model, view, and projection matrices separately. It's better to compose those into a single model-view-projection matrix on the CPU and just multiply once in the shader. The exception is if you need world-space or view-space coordinates for other reasons.
RmbRT
RmbRT

OpenGL has a -1,1 clip space, while DX has -1,1 for x and y, but 0,1 for Z afaik.

Walk with God.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.