Sign in to follow this  
matt77hias

Shadow Map Artifacts: Part 2

Recommended Posts

Spotlight

My spotlight's intensity is cut off at a distance of 4 and at an angle of pi/4 radians (umbra angle). The corresponding light camera has a near plane at a distance of 0.1, a far plane at a distance of 4, an aspect ratio of 1 and a vertical/horizontal FOV of pi/2. The spotlight is positioned somewhere above the tree and faces downward to the floor. 

I obtain the following images after visualizing the shadow factor, obtained after 1x SampleCmpLevelZero:

Clipboard01.png.9bc129bcb51778e5dd74e8efb4abe897.pngClipboard02.png.0bdb2e9005f804c7ff63153dc57e37ef.png

This seems to result in the correct behavior. The bright border on the first image starts at the position of the spotlight and is due to the near plane distance of 0.1. This border will completely vanish after multiplying the shadow factor with the spotlight's contribution.

Omni light

My omni light's intensity is cut off at a distance of 4. The corresponding light camera has a near plane at a distance of 0.1, a far plane at a distance of 4, an aspect ratio of 1 and a vertical/horizontal FOV of pi/2. This means that one of the six light cameras (after applying some rotations), is completely identical to the spotlight camera above. I double checked this in the code myself. So apart from the fact that a spotlight corresponds to one DSV (and is part of one SRV to a Texture2DArray for all spotlight shadow maps) and an omni light corresponds to six DSVs (and is part of one SRV to a Texture2DCubeArray for all omni light shadow cube maps), the shadow maps generation is pretty much the same for both types of lights.

I obtain the following images after visualizing the shadow factor, obtained after 1x SampleCmpLevelZero:

Clipboard03.png.57f999be03d67427355add271a6cf5b9.pngClipboard04.png.625a05168e9e4a79e1a18252f8eec0f1.png

This is clearly wrong. All six shadow maps seem kind of stretched. Without the stretching, the curtain's shadow would still be in the close vicinity of the curtain itself and the leaves' shadow would be much smaller (equal to the leaves' shadow for the spotlight). Furthermore, the circular shadow at the end of the pillar's shadow is associated to the buckets in front of the curtains. So I assume something goes wrong in the shadow factor calculation of the omni light.

Shadow factor calculation

I use the following code for calculating the shadow factor in HLSL:

float ShadowFactor(SamplerComparisonState pcf_sampler, 
    TextureCubeArray< float > shadow_maps, uint index,
    float3 p_view, float2 projection_values) {

    const float p_view_z = Max(abs(p_view));
    const float p_ndc_z  = ViewZtoNDCZ(p_view_z, projection_values);
    const float4 loc     = float4(p_view, index);

    return shadow_maps.SampleCmpLevelZero(pcf_sampler, loc, p_ndc_z);
}

The position in light view space is obtained after transforming the position in camera view space (used for shading):

const float3 p_view = mul(float4(p, 1.0f), light.cview_to_lview).xyz;

The conversion of the z coordinate to NDC space is done as follows:

/**
 Converts the given (linear) view z-coordinate to the (non-linear) NDC 
 z-coordinate.

 @param[in]     p_view_z
                The (linear) view z-coordinate.
 @param[in]     projection_values
                The projection values [view_projection22, view_projection32].
 @return        The (non-linear) NDC z-coordinate.
 */
float ViewZtoNDCZ(float p_view_z, float2 projection_values) {
    return projection_values.x + projection_values.y / p_view_z;
}

with the following dual C++ method:

   /**
     Returns the projection values from the given projection matrix to construct 
     the NDC position z-coordinate from the view position z-coordinate.

     @return        The projection values from the given projection matrix to 
                    construct the NDC position z-coordinate from the view position 
                    z-coordinate.
     */
    inline const XMVECTOR XM_CALLCONV GetNDCZConstructionValues(
        FXMMATRIX projection_matrix) noexcept {

        //        [ _  0  0  0 ]
        // p_view [ 0  _  0  0 ] = [_, _, p_view.z * X + Y, p_view.z] = p_proj
        //        [ 0  0  X  1 ]
        //        [ 0  0  Y  0 ]
        //
        // p_proj / p_proj.w     = [_, _, X + Y/p_view.z, 1] = p_ndc
        //
        // Construction of p_ndc.z from p_view and projection values
        // p_ndc.z = X + Y/p_view.z

        const F32 x = XMVectorGetZ(projection_matrix.r[2]);
        const F32 y = XMVectorGetZ(projection_matrix.r[3]);
        
        return XMVectorSet(x, y, 0.0f, 0.0f);
    }

The strange thing, I noticed, is that using p_ndc_z larger than one still results in some lit areas? (If I use 1.1 for my spotlight code, nothing is lit.)

For example: 

return shadow_maps.SampleCmpLevelZero(pcf_sampler, loc, 1.1);

Clipboard01.png.b0b4fbcb214e278c88b9fa9935ede51a.png

A similar situation occurs for a comparison value equal to 100000.0f. If I just return a zero, I would get the desired result in this case which indicates something strange is going on with the texture/texture sampling.

My shadow cube map array uses a DXGI_FORMAT_R16_TYPELESS format for the texture resource, a DXGI_FORMAT_D16_UNORM format for all DSVs (six for each shadow cube map) and a DXGI_FORMAT_R16_UNORM format for the SRV to the complete TextureCubeArray. Since I use an unsigned-normalized format, the SampleCmp family of methods guarantees a return value in the [0,1] range?

Any ideas what goes wrong?

Edited by matt77hias

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this