Advertisement

How to add a fall off to a shadow map?

Started by December 29, 2017 01:26 PM
5 comments, last by MJP 7 years ago

        // Sample the shadow map depth value from the depth texture using the sampler at the projected texture coordinate location.
        depthValue = shaderTextures[6 + i].Sample(SampleTypeClamp, projectTexCoord).r;

        // Calculate the depth of the light.
        lightDepthValue = input.lightViewPositions[i].z / input.lightViewPositions[i].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.lightPositions));
                lightIntensity = saturate(dot(input.normal, normalize(input.lightPositions[i])));

                if(lightIntensity > 0.0f)
                {
                    // Determine the final diffuse color based on the diffuse color and the amount of light intensity.
                    color += (diffuseCols[i] * lightIntensity * 0.25f);
                }
            }
            else // shadow falloff here
            {
                float4 shadowcol = (1,1,1,1);
                float shadowintensity = saturate(length(input.lightpositions[i])*0.038);    
                color += shadowcol * shadowintensity*shadowintensity*shadowintensity;
            }
        }
    }

    // Saturate the final light color.
    color = saturate(color);

Hi, I want to add a fall off to the shadows in this pixel shader. This should be really straightforward - just get the distance between the light position and the vertex position, and multiply it by the light intensity at the pixel being shadowed, so the light intensity will increase and the shadow will fade away towards the edges. As you can see, I get the "lightPosition" from the input (which comes from the vertex shader, and was calculated by worldLightPosition - worldVertexPosition inside the vertex shader, so taking its length should give you the distance between the light and the pixel.)

I multiplied it by 0.038, an arbitrary number, to scale it down, because it needs to be between 0 and 1 before multiplying it by shadow color (1,1,1,1) to give a gradient. However, this does absolutely nothing, and I cant tell where its failing.

Please look at the attached files to see the full code of the vertex and pixel shaders. Any advice would be very welcome, thanks!

Light_ps.hlsl

Light_vs.hlsl

I'm not sure that I completely understand what you're trying to do here. Are you trying to add a penumbra to your shadow, so that the shadows don't have hard edges? If so, then the standard way to do this with shadow maps is to use percentage closer filtering (PCF for short). In very simple terms, PCF amounts to sampling the shadow map several times around a small region of the shadow map, performing the depth comparison for each sample, and then computing a single result by applying a filter kernel (the simplest filter kernel being a box filter, where you essentially just compute the average of all of the results).

The easiest way to get started with PCF is to let hardware before automatic 2x2 bilinear filtering for you. You'll have to make a few changes to your code to do this:

  1. Create a special "comparison" sampler state to use for sampling your shadow depth map. You do this by specifying "D3D11_COMPARISON_LESS_EQUAL" as the "ComparisonFunc" member of the D3D11_SAMPLER_DESC structure. This specifies that the hardware should return 1 when the passed in surface depth value is <= the shadow map depth value stored in the texture. You'll also want to use "D3D11_FILTER_COMPARISON_MIN_MAG_MIP_LINEAR" to specify that you want 2x2 bilinear filtering when you sample.
  2. In your shader code, declare your shadow sampler state with the type "SamplerComparisonState" instead of "SamplerState".
  3. Change your shader code to use SampleCmp instead of Sample. SampleCmp will return the filtered comparison result instead of the shadow map depth value. So you'll also want to restructure your code so that it looks something like this:

SamplerComparisonState ShadowSampler;

lightDepthValue = input.lightViewPositions[i].z / input.lightViewPositions[i].w;
lightDepthValue = lightDepthValue - bias;

float lightVisibility = shaderTextures[6 + i].SampleCmp(ShadowSampler, projectTexCoord, lightDepthValue);

lightIntensity = saturate(dot(input.normal, normalize(input.lightPositions[i]))) * lightVisibility;

color += (diffuseCols[i] * lightIntensity * 0.25f);

Once you've got the hang of that and you want to look into more advanced filtering techniques, you can check out a blog post I wrote that talks about some of the most common ways to do shadow map filtering (or jump right to the code sample).

Advertisement
1 hour ago, MJP said:

I'm not sure that I completely understand what you're trying to do here. Are you trying to add a penumbra to your shadow, so that the shadows don't have hard edges? If so, then the standard way to do this with shadow maps is to use percentage closer filtering (PCF for short). In very simple terms, PCF amounts to sampling the shadow map several times around a small region of the shadow map, performing the depth comparison for each sample, and then computing a single result by applying a filter kernel (the simplest filter kernel being a box filter, where you essentially just compute the average of all of the results).

The easiest way to get started with PCF is to let hardware before automatic 2x2 bilinear filtering for you. You'll have to make a few changes to your code to do this:

  1. Create a special "comparison" sampler state to use for sampling your shadow depth map. You do this by specifying "D3D11_COMPARISON_LESS_EQUAL" as the "ComparisonFunc" member of the D3D11_SAMPLER_DESC structure. This specifies that the hardware should return 1 when the passed in surface depth value is <= the shadow map depth value stored in the texture. You'll also want to use "D3D11_FILTER_COMPARISON_MIN_MAG_MIP_LINEAR" to specify that you want 2x2 bilinear filtering when you sample.
  2. In your shader code, declare your shadow sampler state with the type "SamplerComparisonState" instead of "SamplerState".
  3. Change your shader code to use SampleCmp instead of Sample. SampleCmp will return the filtered comparison result instead of the shadow map depth value. So you'll also want to restructure your code so that it looks something like this:


SamplerComparisonState ShadowSampler;

lightDepthValue = input.lightViewPositions[i].z / input.lightViewPositions[i].w;
lightDepthValue = lightDepthValue - bias;

float lightVisibility = shaderTextures[6 + i].SampleCmp(ShadowSampler, projectTexCoord).r;

lightIntensity = saturate(dot(input.normal, normalize(input.lightPositions[i]))) * lightVisibility;

color += (diffuseCols[i] * lightIntensity * 0.25f);

Once you've got the hang of that and you want to look into more advanced filtering techniques, you can check out a blog post I wrote that talks about some of the most common ways to do shadow map filtering (or jump right to the code sample).

Thank you so much. I made a new samplestate and changed the shader as instructed, however SampleCmp wont compile - it seems to need one more parameter.


        depthValue = shaderTextures[6 + i].Sample(SampleTypeClamp, projectTexCoord).r;
        // get the soft shadow edge
        float lightVisibility = shaderTextures[6 + i].SampleCmp(SampleTypeComp, projectTexCoord).r;

The third param it wants after float2 texCoord is "float CompareValue", so I tried putting in different values for that, but anything less than 1 does nothing, and over 1, everything does dark. The param after that  is "[int Offset]".

I attached the updated ps shader and the shader-error.txt for reference. Any idea what Im doing wrong? Thanks so much.

 

shader-error.txt

Light_ps.hlsl

Whoops, sorry about that! That value is what you're going to compare against the depth value in the shadow map, so in your case you want to use "lightDepthValue".  I'll update the code that I posted in case anybody else copy/pastes it.


The "offset" parameter is optional, so you can ignore that. It will offset the location that the texture is sampled by a number of texels equal to that parameter.

38 minutes ago, MJP said:

Whoops, sorry about that! That value is what you're going to compare against the depth value in the shadow map, so in your case you want to use "lightDepthValue".  I'll update the code that I posted in case anybody else copy/pastes it.


The "offset" parameter is optional, so you can ignore that. It will offset the location that the texture is sampled by a number of texels equal to that parameter.

I updated my code accordingly, but the shadows still look exactly the same. The only difference now is that the entire surface (both light and shadowed) has these grainy diagonal lines going across it. Also, I noticed that you removed the check


            if(lightDepthValue < depthValue)


before the light intensity calculation code. Is it okay to remove this?

Does the "diaognal lines" artifact look like this?

NoBias.png.c5f9c3bfbab631080b4f0a3d44617c9e.png

This is commonly known as "shadow acne", and the simplest fix is to increase your bias. PCF can introduce additional bias issues when shading surfaces that are not completely perpendicular to the light direction. It basically happens because you sample multiple points on a plane perpendicular to the light direction, and this plane will intersect with the receiver geometry. There are more complex techniques for reducing acne that are usually based around increasing the bias based on computing the slope of the receiver relative to the light direction, and you should try doing some google searching to get some ideas.

As for it not looking any different, here's a comparison of what the shadow edges should look like without PCF, and with PCF:

NoPCF.png.77e73a5a85dd0a3519df5ceb3b21176d.png

PCF.png.963cd208e9c4a8cea7fa05105936693c.png

So if your shadows still look like the first image, I would double-check your parameters that you used when creating the sampler state and make sure that you're using LINEAR filtering. This will give you 2x2 bilinear PCF, similar to the 2x2 texture filtering that can be used for normal textures. The filter kernel is only 2 texels wide, so the soft falloff will only end up being 1 shadow map texel wide. If you want a wider filter, you'll have to implement that manually by sampling the shadow map multiple times. Here's an example of what you get when using an optimized 7x7 filter kernel:

7x7_PCF.png.28029abf0550ad75275c302a9ce0a58e.png

As for the "lightDepthValue < depthValue" check, you no longer need that when you're sampling the depth map with SampleCmp. That function is essentially performing that check for you automatically, and giving you the result as a value between 0 and 1.

This topic is closed to new replies.

Advertisement