BoyUnderTheMoon
BoyUnderTheMoon

Reputation: 771

DirectX11 Shadow Mapping Issues

I'm attempting to add directional shadow mapping to my terrain project, but I'm encountering a few issues. For reference, I'm following the RasterTek shadows tutorial.

The tutorial essentially follows the process of: Create a light > Create a depth texture based on the lights view > Render models and apply shadow shader.

The main issue I'm struggling with is how the tutorial handles the light. It essentially simulates a position and creates an ortho and view matrix. The issue seems to be escalating from how the light is set up. For a simple test, I created a plane, and set the light direction directly down, so everything should be lit, however, the following happens:

enter image description here

And when terrain is generated:

enter image description here Here is code from a few areas that I think would be useful:

Light set up

mLight->SetPosition(XMFLOAT3(10.0f, 30.0f, -0.1f));
mLight->SetLookAt(XMFLOAT3(-10.0f, 0.0f, 0.0f));
mLight->GenerateOthoMatrix(40.0f, 1.0f, 50.0f);

Light GenerateOthoMatrix

void GenerateOthoMatrix(float width, float nearClip, float farClip)
{
    mOrthoMatrix = XMMatrixOrthographicLH(width, width, nearClip, farClip);
}

Light GenerateViewMatrix

XMVECTOR up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);
XMVECTOR pos = XMLoadFloat3(&mPosition);
XMVECTOR la = XMLoadFloat3(&mLookAt);

mViewMatrix = XMMatrixLookAtLH(pos, la, up);

Depth Render Pass

mRenderTexture->SetRenderTarget(mGraphicsDevice->GetContext());
mRenderTexture->ClearRenderTarget(mGraphicsDevice->GetContext());

mLight->GenerateViewMatrix();

mDepthShader.Info.worldMatrix = mTerrain->GetWorldMatrix();
mDepthShader.Info.viewMatrix = mLight->GetViewMatrix();
mDepthShader.Info.projMatrix = mLight->GetOrthoMatrix();

mTerrain->Render(mGraphicsDevice->GetContext());
mDepthShader.Render(mGraphicsDevice->GetContext(), mTerrain->GetIndexCount());

mGraphicsDevice->ResetBackBuffer();
mGraphicsDevice->ResetViewport();

Shader Render calls simply map the 'info' settings to constant buffers and then call their relative vertex/pixel shaders.

Terrain Render calls just setup Index/Vertex buffers and topology, ready for the shader DrawIndexed.

RenderTexture is essentially a second viewport to render to and get a depth texture from

Main Render Pass

mTerrain->Render(mGraphicsDevice->GetContext());
mLight->GenerateViewMatrix();

mShader.Info.lightProj = mLight->GetOrthoMatrix();
mShader.Info.lightView = mLight->GetViewMatrix();
mShader.Info.depthTex = mRenderTexture->GetSRV();
mShader.Render(mGraphicsDevice->GetContext(), mTerrain->GetIndexCount());

Depth Vertex Shader

cbuffer SPerFrameCB : register(b0)
{
    matrix worldMatrix;
    matrix viewMatrix;
    matrix projMatrix;
};

struct VertexIn
{
    float4 Pos    : POSITION;
};

struct VertexOut
{
    float4 Pos    : SV_POSITION;
    float4 DPos   : TEXTURE0;
};

VertexOut main(VertexIn vin)
{
    VertexOut vout;

    vin.Pos.w = 1.0f;

    vout.Pos = mul(vin.Pos, worldMatrix);
    vout.Pos = mul(vout.Pos, viewMatrix);
    vout.Pos = mul(vout.Pos, projMatrix);

    vout.DPos = vout.Pos;

    return vout;
}

Depth Pixel Shader

struct PixelIn
{
    float4 Pos  : SV_POSITION;
    float4 DPos : TEXTURE0;
};

float4 main(PixelIn pin) : SV_Target

{
    float depthVal = pin.DPos.z / pin.DPos.w;
    float4 colour = float4(depthVal, depthVal, depthVal, 1.0f);

    return colour;
}

Shadow Vertex Shader

cbuffer SPerFrameCB : register(b0)
{
    matrix worldMatrix;
    matrix viewMatrix;
    matrix projMatrix;
    matrix lightViewMatrix;
    matrix lightProjMatrix;
};

struct VertexIn
{
    float4 Pos    : POSITION;
    float2 Tex    : TEXCOORD0;
    float3 Normal : NORMAL;
};

struct VertexOut
{
    float4 Pos    : SV_POSITION;
    float2 Tex    : TEXCOORD0;
    float3 Normal : NORMAL;
    float4 LightV : TEXCOORD1;
};

VertexOut main(VertexIn vin)
{
    VertexOut vout;

    vin.Pos.w = 1.0f;

    float4 worldPos = mul(vin.Pos, worldMatrix);

    vout.Pos = worldPos;
    vout.Pos = mul(vout.Pos, viewMatrix);
    vout.Pos = mul(vout.Pos, projMatrix);

    vout.LightV = worldPos;
    vout.LightV = mul(vout.LightV, lightViewMatrix);
    vout.LightV = mul(vout.LightV, lightProjMatrix);

    vout.Tex = vin.Tex;

    vout.Normal = mul(vin.Normal, (float3x3)worldMatrix);
    vout.Normal = normalize(vout.Normal);

    return vout;
}

Shadow Pixel Shader

Texture2D shaderTexture;

Texture2D lowerTex  : register(t0);
Texture2D mediumTex : register(t1);
Texture2D higherTex : register(t2);
Texture2D depthTex : register(t3);

SamplerState SampleTypeClamp : register(s0);
SamplerState SampleTypeWrap  : register(s1);

cbuffer SPerLightCB : register(b0)
{
    float4 ambientColour;
    float4 diffuseColour;
    float3 lightDirection;
    float padding;
};

struct PixelIn
{
    float4 Pos    : SV_POSITION;
    float2 Tex    : TEXCOORD0;
    float3 Normal : NORMAL;
    float4 LightV : TEXCOORD1;
};

float4 main(PixelIn pin) : SV_Target
{
    float bias = 0.001f;
    float3 lightDir = -lightDirection;
    float4 colour = ambientColour;

    float2 projTexCoord;
    projTexCoord.x =  pin.LightV.x / pin.LightV.w / 2.0f + 0.5f;
    projTexCoord.y = -pin.LightV.y / pin.LightV.w / 2.0f + 0.5f;

    if ((saturate(projTexCoord.x) == projTexCoord.x) && (saturate(projTexCoord.y) == projTexCoord.y))
    {
        float depthVal = depthTex.Sample(SampleTypeClamp, projTexCoord).r;
        float lightDepthVal = pin.LightV.z / pin.LightV.w;
        lightDepthVal -= bias;

        if (lightDepthVal < depthVal)
        {
            float lightIntensity = saturate(dot(pin.Normal, lightDir));

            if (lightIntensity > 0.0f)
            {
                colour += diffuseColour * lightIntensity;
                colour = saturate(colour);
            }
        }
    }

    float4 lowerColour = lowerTex.Sample(SampleTypeWrap, pin.Tex);
    float4 mediumColour = mediumTex.Sample(SampleTypeWrap, pin.Tex);
    float4 higherColour = higherTex.Sample(SampleTypeWrap, pin.Tex);
    float4 texColour;
    float slope = 1.0f - pin.Normal.y, bVal;

    if (slope < 0.4f)
    {
        bVal = slope / 0.4f;
        texColour = lerp(lowerColour, mediumColour, bVal);
    }

    if (slope >= 0.4f && slope < 0.6f)
    {
        bVal = (slope - 0.4f) * (1.0f / (0.6f - 0.4f));
        texColour = lerp(mediumColour, higherColour, bVal);
    }

    if (slope >= 0.6f)
    {
        texColour = higherColour;
    }

    colour *= texColour;

    return colour;
}

I'm very sorry for the large amounts of code - I'm not sure which sections would help best in identifying the issue. If anyone could help, or provide a shadow mapping resource I would be very grateful. There doesn't seem to be many shadow mapping resources, or at least I haven't been able to find many.

Upvotes: 1

Views: 1874

Answers (2)

kaiser
kaiser

Reputation: 1009

I know this Problem very well... I just looked at the pictures, didnt read your long text.

I think the light does not see the whole scene, just the parts that are illuminated.

There are some solutions but none is pretty good. Thats a typical problem.

Try to increase The frustum of The light perspective. XMMatrixOrthographicLH needs a greater width. Increasing The viewing frustum of light decreases The details of your shadow map.

There is not the perfekt solution... You need to try what values fit to your Problem.

Upvotes: 0

Francis Cugler
Francis Cugler

Reputation: 7905

It appears that your Depth Shaders looks good. I've noticed differences in your Shadow shaders though. I have went through and completely both series for DirectX 10 & 11 through rastertek. I'll show you what my shadow shaders look like; however I do not remember if they have been changed from one lesson to another. I'll post them here for you to compare to.

Shadow.vsh

/////////////////////////////////////////////////
// Filename: shadow.vsh
/////////////////////////////////////////////////

/////////////
// GLOBALS //
/////////////
cbuffer MatrixBuffer
{
    matrix worldMatrix;
    matrix viewMatrix;
    matrix projectionMatrix;
    matrix lightViewMatrix;
    matrix lightProjectionMatrix;
};

//////////////////////
// CONSTANT BUFFERS //
//////////////////////
cbuffer LightBuffer2
{
    float3  lightPosition;
    float   padding;
};

//////////////
// TYPEDFES //
struct VertexInputType
{
    float4 position : POSITION;
    float2 tex : TEXCOORD0;
    float3 normal : NORMAL;
};

struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
    float3 normal : NORMAL;
    float4 lightViewPosition : TEXCOORD1;
    float3 lightPos : TEXCOORD2;
};

/////////////////////////////////////////////////
// Vertex Shader
/////////////////////////////////////////////////
PixelInputType ShadowVertexShader( VertexInputType input )
{
    PixelInputType output;
    float4 worldPosition;

    // Change The Position Vector To Be 4 Units For Proper Matrix Calculations
    input.position.w = 1.0f;

    // Calculate The Position Of The Vertex Against The World, View And Projection Matrices
    output.position = mul( input.position, worldMatrix );
    output.position = mul( output.position, viewMatrix );
    output.position = mul( output.position, projectionMatrix );

    // Calculate The Position Of The Vertex As Viewed By The Light Source
    output.lightViewPosition = mul( input.position, worldMatrix );
    output.lightViewPosition = mul( output.lightViewPosition, lightViewMatrix );
    output.lightViewPosition = mul( output.lightViewPosition, lightProjectionMatrix );

    // Store The Texture Coordinate For The Pixel Shader
    output.tex = input.tex;

    // Calculate The Normal Vector Against The World Matrix Only
    output.normal = mul( input.normal, (float3x3)worldMatrix );

    // Normalize The Normal Vector
    output.normal = normalize( output.normal );

    // Calculate The Position Of The Vertex In The World
    worldPosition = mul( input.position, worldMatrix );

    // Determine The Light Position Based On The Position Of The Light And The Position Of The Vertex In The World
    output.lightPos = lightPosition.xyz - worldPosition.xyz;

    // Normalize The Light Position Vector
    output.lightPos = normalize( output.lightPos );

    return output;
} // ShadowVertexShader

Shadow.psh

/////////////////////////////////////////////////
// Filename: shadow.ps
/////////////////////////////////////////////////

//////////////
// TEXTURES //
Texture2D depthMapTexture : register(t0);

///////////////////
// SAMPLE STATES //
///////////////////
SamplerState SampleTypeClamp : register(s0);

//////////////
// TYPEDEFS //
//////////////
struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
    float3 normal : NORMAL;
    float4 lightViewPosition : TEXCOORD1;
    float3 lightPos : TEXCOORD2;
};

/////////////////////////////////////////////////
// Pixel Shader
/////////////////////////////////////////////////
float4 ShadowPixelShader( PixelInputType input ) : SV_TARGET
{
    float   bias;
    float4  color;
    float2  projectTexCoord;
    float   depthValue;
    float   lightDepthValue;
    float   lightIntensity;

    // Set The Bias Value For Fixing The Floating Point Precision Issues
    bias = 0.001f;

    // Set The Default Output Color To Be Black (Shadow)
    color = float4( 0.0f, 0.0f, 0.0f, 1.0f );

    // Calculate The Projected Texture Coordinates
    projectTexCoord.x =  input.lightViewPosition.x / input.lightViewPosition.w / 2.0f + 0.5f;
    projectTexCoord.y = -input.lightViewPosition.y / input.lightViewPosition.w / 2.0f + 0.5f;

    // Determine If The Projected Coordinates Are In The [0,1] Range. If So Then This Pixel Is In The View Of The Light
    if ( (saturate( projectTexCoord.x) == projectTexCoord.x) && (saturate(projectTexCoord.y) == projectTexCoord.y) ) 
    {
        // Sample The Shadow Map Depth Value From The Depth Texture Using The Sampler At The Projected Texture Coordinate Location
        depthValue = depthMapTexture.Sample( SampleTypeClamp, projectTexCoord).r;

        // Calculate The Depth Of The Light
        lightDepthValue = input.lightViewPosition.z / input.lightViewPosition.w;

        // Subtract The Bias From The LightDepthValue
        lightDepthValue = lightDepthValue - bias;

        // Compare The Depth Of The Shadow Map Value And The Depth Of The Light To Determine Whether To Shadow Or To Light This Pixel
        // If The Light Is In Front Of The Object Then Light The Pixel, If Not Then Shadow This Pixel Since An Object (Occluder) Is Casting A Shadow On It
        if ( lightDepthValue < depthValue ) 
        {
            // Calculate The Amount Of Light On This Pixel
            lightIntensity = saturate( dot( input.normal, input.lightPos ) );

            // If This Pixel Is Illuminated Then Set It To Pure White (Non-Shadow)
            if ( lightIntensity > 0.0f )
            {
                // Determine The Final Diffuse Color Based On The Diffuse Color And The Amount Of Light Intensity
                color = float4( 1.0f, 1.0f, 1.0f, 1.0f );           
            }
        }
    }

    return color;
} // ShadowPixelShader

Also make sure that your corresponding .h & .cpp Shader files match the correct input and output structures that are in your shaders. It appears that you are using the light direction as opposed to the light position when calculating the light intensity in your pixel shader. You do have more textures added into your version of the shader than I but I don't think that would make the difference in this case. I do not have access to see your entire solution, so it is hard to tell where the error may be coming from. I just hope that his helps to serve you as a guide.

Upvotes: 2

Related Questions