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");
_shadowCamera.Render();
// 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 BLOCKER_SEARCH_NUM_SAMPLES 16
#define NEAR_PLANE u_NearPlane
#define LIGHT_WORLD_SIZE u_LightWorldSize
#define LIGHT_FRUSTUM_WIDTH u_LightFrustrumWidth
#define LIGHT_SIZE_UV (LIGHT_WORLD_SIZE / LIGHT_FRUSTUM_WIDTH)
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;
numBlockers++;
}
}
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.
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.
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.
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.