Vulkan_PCSS soft shadow

Posted by MarkB423 on Wed, 02 Feb 2022 08:01:18 +0100

This paper briefly describes the implementation. The specific steps and principles can be viewed in Yan Dashen's high-quality real-time rendering.

1, Introduction to PCSS

1.1 PCF

Percentage asymptotic filtering (PCF) is a simple and common technique for shadow edge backtracking. It samples around the clip, then calculates the proportion of the sample closer to the light source than the clip, uses this proportion to scale the scattered light and specular light components, and then colors the clip. Using this technique, the shadow edges look blurred.

However, the internal sampling of large objects is a big waste. We can't only sample the object boundary, so there is a big bottleneck in performance.

1.2 PCSS

pcss is distance dependent. It tries to find all possible occlusions by searching the nearby area on the shadow map. The average distance of these obstructions from this location is used to determine the width of the sample area. As the average occlusion moves farther away from the receiver and closer to the light, the width of the surface area of the sample increases.

If no shelter can be found, the position is fully lit without further treatment. Similarly, if the position is completely obscured, the processing can end. Otherwise, continue to sample the region of interest and calculate the approximate contribution of light. In order to save processing costs, the width of the sample area can be used to change the number of samples. Other techniques can be implemented, such as using a lower sampling rate to obtain less important long-distance soft shadows.

Specific implementation steps:

  1. Calculation of average depth of shelter;
  2. Penumbra area calculation;
  3. Applicability PCF sampling;

It should be noted that:

  • The calculation of penumbra area is shown in the triangle scale relationship below:

  • The calculation of the area size in the calculation of the average depth of the occlusion is shown in the figure below (the fixed area size can also be simplified):

2, Code implementation

This section only describes the implementation of Shader. For C + code, please refer to the previous shadow related articles.

2.1 shadow map pass

Simply take the shadow map:

Vertex shader:

#version 330 core

in vec3 position;

uniform mat4 modelViewProjection;

void main()
{
	gl_Position = modelViewProjection * vec4(position, 1);
}

Slice shader:

#version 330 core

out float outDepth;

void main()
{
    outDepth = gl_FragCoord.z;
}

2.2 pcss pass

Using shadow map to calculate PCSS

Vertex shader:

#version 330 core

in vec3 position; 
in vec3 normal;
in vec2 texcoords;

out vec2 vTexcoords; 
out vec3 vNormal;
out vec3 vViewDir;
out vec3 vWorldPosition;
out vec3 vCameraPosition;

uniform mat4 model; 
uniform mat4 view; 
uniform mat4 projection; 
uniform vec3 eyePosition;

void main()
{
    vTexcoords = texcoords;
	vNormal = (model * vec4(normal, 0)).xyz;
	vec4 worldPosition = model * vec4(position, 1.0f);
	vWorldPosition = worldPosition.xyz;
	vec4 cameraPosition = view * model * vec4(position, 1.0f);
	vCameraPosition = cameraPosition.xyz;
	vViewDir = normalize(eyePosition - vWorldPosition);
    gl_Position = projection * cameraPosition;
}

The implementation steps in the chip shader are basically the same as the above PCSS implementation steps. See the notes for details.

Slice shader:

#version 330 core

#define NEAR 0.1

#define HARD_SHADOWS 0
#define SOFT_SHADOWS 1

in vec2 vTexcoords;
in vec3 vNormal;
in vec3 vViewDir;
in vec3 vWorldPosition;
in vec3 vCameraPosition;

struct LightSource
{
	vec3 diffuseColor;
	float diffusePower;
	vec3 specularColor;
	float specularPower;
	vec3 position;
	int type;
	float size;

};

layout (std140) uniform LightSources
{
    LightSource lightSources[2];
};

uniform sampler2D shadowMap0;
uniform samplerCube shadowCubeMap0;
uniform mat4 shadowMapViewProjection0;

uniform mat4 invView;
uniform mat4 lightProjection;
uniform vec3 eyePosition;
uniform vec3 ambientColor = vec3(0.8,0.8,0.8);
uniform vec3 specularColor = vec3(1,1,1);
uniform float specularity = 0;
uniform float frustumSize = 1;
uniform sampler2D tex0;
uniform sampler1D distribution0;
uniform sampler1D distribution1;
uniform int numBlockerSearchSamples = 16;
uniform int numPCFSamples = 16;
uniform int displayMode = 0;
uniform int selectedLightSource = -1;

out vec3 outColor;

//Random sampling data
vec2 RandomDirection(sampler1D distribution, float u)
{
   return texture(distribution, u).xy * 2 - vec2(1);
}

//Conventional light treatment
vec3 LightContribution(vec3 diffuseColor)
{
	float NdotL=max(0, dot(vNormal, normalize(vViewDir - lightSources[0].position)));
	return diffuseColor * lightSources[0].diffuseColor * lightSources[0].diffusePower * NdotL  + 
			pow(NdotL, specularity) * specularColor * lightSources[0].specularColor * lightSources[0].specularPower;
}

//Coordinate point to camera space
vec3 ShadowCoords(mat4 shadowMapViewProjection)
{
	vec4 projectedCoords = shadowMapViewProjection * vec4(vWorldPosition, 1);
	vec3 shadowCoords = projectedCoords.xyz / projectedCoords.w;
	shadowCoords = shadowCoords * 0.5 + 0.5;
	return shadowCoords;
}

//Occlusion range calculation
float SearchWidth(float uvLightSize, float receiverDistance)
{
	return uvLightSize * (receiverDistance - NEAR) / eyePosition.z;
}

//Average depth of occlusion query
float FindBlockerDistance_DirectionalLight(vec3 shadowCoords, sampler2D shadowMap, float uvLightSize)
{
	int blockers = 0;
	float avgBlockerDistance = 0;
	float searchWidth = SearchWidth(uvLightSize, shadowCoords.z);
	for (int i = 0; i < numBlockerSearchSamples; i++)
	{
		float z = texture(shadowMap, shadowCoords.xy + RandomDirection(distribution0, i / float(numBlockerSearchSamples)) * searchWidth).r;
		if (z < (shadowCoords.z - 0.002f))
		{
			blockers++;
			avgBlockerDistance += z;
		}
	}
	if (blockers > 0)
		return avgBlockerDistance / blockers;
	else
		return -1;
}

//PCF
float PCF_DirectionalLight(vec3 shadowCoords, sampler2D shadowMap, float uvRadius)
{
	float sum = 0;
	for (int i = 0; i < numPCFSamples; i++)
	{
		float z = texture(shadowMap, shadowCoords.xy + RandomDirection(distribution0, i / float(numPCFSamples)) * uvRadius).r;
		sum += (z < (shadowCoords.z - 0.002f)) ? 1 : 0;
	}
	return sum / numPCFSamples;
}

//ShadowMap
float ShadowMapping_DirectionalLight(vec3 shadowCoords, sampler2D shadowMap, float uvLightSize)
{
	float z = texture(shadowMap, shadowCoords.xy).x;
	return (z < (shadowCoords.z - 0.002f)) ? 0 : 1;
}

//Soft shadow calculation
float PCSS_DirectionalLight(vec3 shadowCoords, sampler2D shadowMap, float uvLightSize)
{
	// Calculation of average depth of shelter
	float blockerDistance = FindBlockerDistance_DirectionalLight(shadowCoords, shadowMap, uvLightSize);
	if (blockerDistance == -1)
		return 1;		

	// Penumbra area calculation
	float penumbraWidth = ((shadowCoords.z - blockerDistance) / blockerDistance) * uvLightSize;

	// PCF
	float uvRadius = penumbraWidth * NEAR / shadowCoords.z;
	return 1 - PCF_DirectionalLight(shadowCoords, shadowMap, uvRadius);
}

void main()
{
	vec3 diffuseColor = texture(tex0, vTexcoords).rgb;
	switch (displayMode)
	{
		case HARD_SHADOWS:	//Hard shadow
			outColor = LightContribution(diffuseColor) * ShadowMapping_DirectionalLight(ShadowCoords(shadowMapViewProjection0), shadowMap0, lightSources[0].size / frustumSize);
			break;
		case SOFT_SHADOWS:  //Soft shadow
			outColor = LightContribution(diffuseColor) * PCSS_DirectionalLight(ShadowCoords(shadowMapViewProjection0), shadowMap0, lightSources[0].size / frustumSize);
			break;
		default:
			outColor = vec3(0.3,0.3,0.3);
	}
	outColor += ambientColor*diffuseColor;
}

The following effects can be seen when running (32 sampling range, the default size of area light source is 0.5):

Compare the original shadow map effect:

Put another low PCF effect (4 sampling range, area light default size 0.5):

You can clearly see the time-consuming of PCFF from the frame rate. If you have time in the future, continue to use VSSM method to optimize the time-consuming of PCSS in the first step blocker search and the third pcf sampling.

Topics: Vulkan