I am trying to implement the Percentage-closer Soft Shadows (PCSS) from NVidia inside Unity but I am facing some issue and I don't know where they come from and then, I do not know how to solve them...

Here is my current setup.

I am using an orthographic camera to calculate my shadowmap. Here are the different steps and some pseudo-code.

// Setup camera
_shadowCamera.clearFlags = CameraClearFlags.Depth;
_shadowCamera.orthographic= true;

// Setup a render texture to output the shadowmap from the camera
RenderTexture _shadowTexture = new RenderTexture((int)_shadowMapSize, _shadowMapSize, 16, RenderTextureFormat.Shadowmap, RenderTextureReadWrite.Linear);

// Render scene using a replacement shader. This is only used to output 
_shadowCamera.SetReplacementShader(_shadowMapShader, "RenderType");

// Set the camera positions and matrix
_radius = _bounds;
Vector3 targetPos = _target.transform.position;
Vector3 lightDir = _light.transform.forward;
Quaternion lightRot = _light.transform.rotation;

_shadowCamera.transform.position = targetPos - lightDir * _radius;
_shadowCamera.transform.rotation = lightRot;
_shadowCamera.orthographicSize = _radius;

_shadowCamera.farClipPlane = _radius * 2.0f;

Matrix4x4 shadowViewMatrix = _shadowCamera.worldToCameraMatrix;
Matrix4x4 shadowProjectionMatrix = GL.GetGPUProjectionMatrix(_shadowCamera.projectionMatrix, false);

Matrix4x4 shadowBiasMatrix = Matrix4x4.identity;
shadowBiasMatrix.SetRow(0, new Vector4(0.5f, 0.0f, 0.0f, 0.5f));
shadowBiasMatrix.SetRow(1, new Vector4(0.0f, 0.5f, 0.0f, 0.5f));
shadowBiasMatrix.SetRow(2, new Vector4(0.0f, 0.0f, 1.0f, 0.0f));
shadowBiasMatrix.SetRow(3, new Vector4(0.0f, 0.0f, 0.0f, 1.0f));

_shadowMatrix = shadowBiasMatrix * shadowProjectionMatrix * shadowViewMatrix;

// Transfering data to shader
_material.SetMatrix("_ShadowMatrix", _shadowMatrix);
_material.SetTexture("_ShadowTexture", _shadowTexture);
_material.SetTexture("u_PointSampler", _pointSampler);
_material.SetFloat("u_NearPlane", _shadowCamera.nearClipPlane);
_material.SetFloat("u_LightWorldSize", _lightWorldSize);
_material.SetFloat("u_LightFrustrumWidth", _lightFrustrumWidth);

In my shader, I am simply doing the blocker search part and here is also some pseudo-code.

#define NEAR_PLANE u_NearPlane
#define LIGHT_WORLD_SIZE u_LightWorldSize
#define LIGHT_FRUSTUM_WIDTH u_LightFrustrumWidth

uniform Texture2D               _ShadowTexture;
uniform SamplerComparisonState  sampler_ShadowTexture;
uniform Texture2D               u_PointSampler;
uniform SamplerState            sampleru_PointSampler;

half4 coords = mul(_ShadowMatrix, float4(worldPos.xyz, 1.f));
float2 uv = coords.xy;
float zReceiver = coords.z;
float searchWidth = LIGHT_SIZE_UV * (zReceiver - NEAR_PLANE) / zReceiver;
float blockerSum = u_PointSampler.Sample(sampleru_PointSampler, float2(0, 0)).a;
float numBlockers = 0;

for (int i = 0; i < BLOCKER_SEARCH_NUM_SAMPLES; ++i)
    float shadowMapDepth = _ShadowTexture.Sample(sampleru_PointSampler, uv.xy + poissonDisk[i] * searchWidth).r;
    if (shadowMapDepth < zReceiver)
        blockerSum += shadowMapDepth;
float avgBlockerDepth = blockerSum / numBlockers;
return avgBlockerDepth;

Here is an example of my issue. As you can see on the right the shadowing seems correct but if you move the cylinder, you can see on the left, the penumbra is not computed correctly.


As I said I don't know what I am doing wrong, I suppose that this comes from the matrix or maybe the depth but there might be some other problems.

Additional information

I've rebuilt my system and I better understand the whole thing and I also think that I know where the issue comes from.

Let's focus on the near plane value, the shadow map and the zReceiver which is the z projection coordinate.

The shadow map and the zReceiver are calculated within the light view space as you can see in the following screenshot.

enter image description here

Now let's see how the searchWidth is calculated and some screenshots with different NEAR_PLANE values.

float searchWidth = LIGHT_SIZE_UV * (zReceiver - NEAR_PLANE) / zReceiver;

In those examples only the NEAR_PLANE value is changed and the camera near plane remains to 0.

enter image description here

As you can see with a NEAR_PLANE value of zero the searchWidth doesn't vary compared to other values. But I think that other values are wrong and don't behave right as I am using a directional light. Indeed, the penumbra should not be affected by the distance from the object to the light. To try fixing this, I only used the zReceiver without taking in account the NEAR_PLANE value and the results are not good.

float searchWidth = LIGHT_SIZE_UV * zReceiver;

As you can see in the following screenshots, in the red circles as you move the object far according to the shadow map, the width gets bigger and it shouldn't.

enter image description here

The correct behaviour is that the searchWidth shouldn't be affected by the object position in the shadow map but more by its height. I think that this should makes sense.

I would like know how could I calculate the searchWidth as it seems that my problem comes from its calculation. Maybe the issue also comes from the shadowmap or the zReceiver but I have some doubts about it.

  • 1
    $\begingroup$ Thanks for your answer @trichoplax. Concerning the screenshot it might be awkward as the whole thing is wrong because as you move the cylinder, you can notice that the penumbra doesn't behaves well. I would be glad to share the code because I am losing all my hair trying to make true PCSS... I just need to clean the code a bit :) $\endgroup$
    – MaT
    Dec 13, 2016 at 0:20
  • 1
    $\begingroup$ Will post some source soon. Hope somebody can help about that. $\endgroup$
    – MaT
    Dec 13, 2016 at 0:22
  • 1
    $\begingroup$ Alright, I'll move it to the question. $\endgroup$
    – MaT
    Dec 14, 2016 at 14:40
  • 1
    $\begingroup$ Incidentally, it's very well written. I was ready to upvote the answer if it had a solution at the end. $\endgroup$ Dec 14, 2016 at 14:41
  • 1
    $\begingroup$ @trichoplax Thanks I think that it's better to give as much informations as possible especially with this kind of topics. It also might help others. If you know somebody that might be able to help don't hesitate ;) $\endgroup$
    – MaT
    Dec 14, 2016 at 14:45


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.