Improve shadow filters (#12195)

* Rewrite shadow filtering for the new distortion
* Calculate penumbra radius using a single sample
* Avoid peter-panning effect due to filtering of short shadows
* Add adaptive filter quality for soft shadows
* Avoid sharp shadows on surfaces without normals (e.g. plants)
* Increase default and maximum soft shadow radius
* Make line numbers in shader errors match the code
This commit is contained in:
x2048 2022-05-21 16:49:30 +02:00 committed by GitHub
parent a4ef62f5b2
commit dc45b85a54
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 131 additions and 220 deletions

@ -631,8 +631,8 @@ shadow_update_frames (Map shadows update frames) int 8 1 16
# Set the soft shadow radius size. # Set the soft shadow radius size.
# Lower values mean sharper shadows, bigger values mean softer shadows. # Lower values mean sharper shadows, bigger values mean softer shadows.
# Minimum value: 1.0; maximum value: 10.0 # Minimum value: 1.0; maximum value: 15.0
shadow_soft_radius (Soft shadow radius) float 1.0 1.0 10.0 shadow_soft_radius (Soft shadow radius) float 5.0 1.0 15.0
# Set the tilt of Sun/Moon orbit in degrees. # Set the tilt of Sun/Moon orbit in degrees.
# Value of 0 means no tilt / vertical orbit. # Value of 0 means no tilt / vertical orbit.

@ -25,6 +25,7 @@ uniform float animationTimer;
varying float cosLight; varying float cosLight;
varying float f_normal_length; varying float f_normal_length;
varying vec3 shadow_position; varying vec3 shadow_position;
varying float perspective_factor;
#endif #endif
@ -116,23 +117,16 @@ float getHardShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance
#if SHADOW_FILTER == 2 #if SHADOW_FILTER == 2
#define PCFBOUND 3.5 #define PCFBOUND 2.0 // 5x5
#define PCFSAMPLES 64.0 #define PCFSAMPLES 25
#elif SHADOW_FILTER == 1 #elif SHADOW_FILTER == 1
#define PCFBOUND 1.5 #define PCFBOUND 1.0 // 3x3
#if defined(POISSON_FILTER) #define PCFSAMPLES 9
#define PCFSAMPLES 32.0
#else
#define PCFSAMPLES 16.0
#endif
#else #else
#define PCFBOUND 0.0 #define PCFBOUND 0.0
#if defined(POISSON_FILTER) #define PCFSAMPLES 1
#define PCFSAMPLES 4.0
#else
#define PCFSAMPLES 1.0
#endif
#endif #endif
#ifdef COLORED_SHADOWS #ifdef COLORED_SHADOWS
float getHardShadowDepth(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) float getHardShadowDepth(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
{ {
@ -149,59 +143,31 @@ float getHardShadowDepth(sampler2D shadowsampler, vec2 smTexCoord, float realDis
} }
#endif #endif
float getBaseLength(vec2 smTexCoord) #define BASEFILTERRADIUS 1.0
{
float l = length(2.0 * smTexCoord.xy - 1.0 - CameraPos.xy); // length in texture coords
return xyPerspectiveBias1 / (1.0 / l - xyPerspectiveBias0); // return to undistorted coords
}
float getDeltaPerspectiveFactor(float l) float getPenumbraRadius(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
{ {
return 0.04 * pow(512.0 / f_textureresolution, 0.4) / (xyPerspectiveBias0 * l + xyPerspectiveBias1); // original distortion factor, divided by 10
}
float getPenumbraRadius(sampler2D shadowsampler, vec2 smTexCoord, float realDistance, float multiplier)
{
float baseLength = getBaseLength(smTexCoord);
float perspectiveFactor;
// Return fast if sharp shadows are requested // Return fast if sharp shadows are requested
if (PCFBOUND == 0.0) if (PCFBOUND == 0.0 || SOFTSHADOWRADIUS <= 0.0)
return 0.0; return 0.0;
if (SOFTSHADOWRADIUS <= 1.0) {
perspectiveFactor = getDeltaPerspectiveFactor(baseLength);
return max(2 * length(smTexCoord.xy) * 2048 / f_textureresolution / pow(perspectiveFactor, 3), SOFTSHADOWRADIUS);
}
vec2 clampedpos; vec2 clampedpos;
float texture_size = 1.0 / (2048 /*f_textureresolution*/ * 0.5);
float y, x; float y, x;
float depth = 0.0; float depth = getHardShadowDepth(shadowsampler, smTexCoord.xy, realDistance);
float pointDepth; // A factor from 0 to 1 to reduce blurring of short shadows
float maxRadius = SOFTSHADOWRADIUS * 5.0 * multiplier; float sharpness_factor = 1.0;
// conversion factor from shadow depth to blur radius
float depth_to_blur = f_shadowfar / SOFTSHADOWRADIUS / xyPerspectiveBias0;
if (depth > 0.0 && f_normal_length > 0.0)
// 5 is empirical factor that controls how fast shadow loses sharpness
sharpness_factor = clamp(5 * depth * depth_to_blur, 0.0, 1.0);
depth = 0.0;
float bound = clamp(PCFBOUND * (1 - baseLength), 0.0, PCFBOUND); float world_to_texture = xyPerspectiveBias1 / perspective_factor / perspective_factor
int n = 0; * f_textureresolution / 2.0 / f_shadowfar;
float world_radius = 0.2; // shadow blur radius in world float coordinates, e.g. 0.2 = 0.02 of one node
for (y = -bound; y <= bound; y += 1.0) return max(BASEFILTERRADIUS * f_textureresolution / 4096.0, sharpness_factor * world_radius * world_to_texture * SOFTSHADOWRADIUS);
for (x = -bound; x <= bound; x += 1.0) {
clampedpos = vec2(x,y);
perspectiveFactor = getDeltaPerspectiveFactor(baseLength + length(clampedpos) * texture_size * maxRadius);
clampedpos = clampedpos * texture_size * perspectiveFactor * maxRadius * perspectiveFactor + smTexCoord.xy;
pointDepth = getHardShadowDepth(shadowsampler, clampedpos.xy, realDistance);
if (pointDepth > -0.01) {
depth += pointDepth;
n += 1;
}
}
depth = depth / n;
depth = pow(clamp(depth, 0.0, 1000.0), 1.6) / 0.001;
perspectiveFactor = getDeltaPerspectiveFactor(baseLength);
return max(length(smTexCoord.xy) * 2 * 2048 / f_textureresolution / pow(perspectiveFactor, 3), depth * maxRadius);
} }
#ifdef POISSON_FILTER #ifdef POISSON_FILTER
@ -276,26 +242,23 @@ const vec2[64] poissonDisk = vec2[64](
vec4 getShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) vec4 getShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
{ {
vec2 clampedpos; float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance);
vec4 visibility = vec4(0.0);
float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance, 1.5); // scale to align with PCF
if (radius < 0.1) { if (radius < 0.1) {
// we are in the middle of even brightness, no need for filtering // we are in the middle of even brightness, no need for filtering
return getHardShadowColor(shadowsampler, smTexCoord.xy, realDistance); return getHardShadowColor(shadowsampler, smTexCoord.xy, realDistance);
} }
float baseLength = getBaseLength(smTexCoord); vec2 clampedpos;
float perspectiveFactor; vec4 visibility = vec4(0.0);
float scale_factor = radius / f_textureresolution;
float texture_size = 1.0 / (f_textureresolution * 0.5); int samples = (1 + 1 * int(SOFTSHADOWRADIUS > 1.0)) * PCFSAMPLES; // scale max samples for the soft shadows
int samples = int(clamp(PCFSAMPLES * (1 - baseLength) * (1 - baseLength), PCFSAMPLES / 4, PCFSAMPLES)); samples = int(clamp(pow(4.0 * radius + 1.0, 2.0), 1.0, float(samples)));
int init_offset = int(floor(mod(((smTexCoord.x * 34.0) + 1.0) * smTexCoord.y, 64.0-samples))); int init_offset = int(floor(mod(((smTexCoord.x * 34.0) + 1.0) * smTexCoord.y, 64.0-samples)));
int end_offset = int(samples) + init_offset; int end_offset = int(samples) + init_offset;
for (int x = init_offset; x < end_offset; x++) { for (int x = init_offset; x < end_offset; x++) {
clampedpos = poissonDisk[x]; clampedpos = poissonDisk[x] * scale_factor + smTexCoord.xy;
perspectiveFactor = getDeltaPerspectiveFactor(baseLength + length(clampedpos) * texture_size * radius);
clampedpos = clampedpos * texture_size * perspectiveFactor * radius * perspectiveFactor + smTexCoord.xy;
visibility += getHardShadowColor(shadowsampler, clampedpos.xy, realDistance); visibility += getHardShadowColor(shadowsampler, clampedpos.xy, realDistance);
} }
@ -306,26 +269,23 @@ vec4 getShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDistance
float getShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) float getShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
{ {
vec2 clampedpos; float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance);
float visibility = 0.0;
float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance, 1.5); // scale to align with PCF
if (radius < 0.1) { if (radius < 0.1) {
// we are in the middle of even brightness, no need for filtering // we are in the middle of even brightness, no need for filtering
return getHardShadow(shadowsampler, smTexCoord.xy, realDistance); return getHardShadow(shadowsampler, smTexCoord.xy, realDistance);
} }
float baseLength = getBaseLength(smTexCoord); vec2 clampedpos;
float perspectiveFactor; float visibility = 0.0;
float scale_factor = radius / f_textureresolution;
float texture_size = 1.0 / (f_textureresolution * 0.5); int samples = (1 + 1 * int(SOFTSHADOWRADIUS > 1.0)) * PCFSAMPLES; // scale max samples for the soft shadows
int samples = int(clamp(PCFSAMPLES * (1 - baseLength) * (1 - baseLength), PCFSAMPLES / 4, PCFSAMPLES)); samples = int(clamp(pow(4.0 * radius + 1.0, 2.0), 1.0, float(samples)));
int init_offset = int(floor(mod(((smTexCoord.x * 34.0) + 1.0) * smTexCoord.y, 64.0-samples))); int init_offset = int(floor(mod(((smTexCoord.x * 34.0) + 1.0) * smTexCoord.y, 64.0-samples)));
int end_offset = int(samples) + init_offset; int end_offset = int(samples) + init_offset;
for (int x = init_offset; x < end_offset; x++) { for (int x = init_offset; x < end_offset; x++) {
clampedpos = poissonDisk[x]; clampedpos = poissonDisk[x] * scale_factor + smTexCoord.xy;
perspectiveFactor = getDeltaPerspectiveFactor(baseLength + length(clampedpos) * texture_size * radius);
clampedpos = clampedpos * texture_size * perspectiveFactor * radius * perspectiveFactor + smTexCoord.xy;
visibility += getHardShadow(shadowsampler, clampedpos.xy, realDistance); visibility += getHardShadow(shadowsampler, clampedpos.xy, realDistance);
} }
@ -341,65 +301,57 @@ float getShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
vec4 getShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) vec4 getShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
{ {
vec2 clampedpos; float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance);
vec4 visibility = vec4(0.0);
float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance, 1.0);
if (radius < 0.1) { if (radius < 0.1) {
// we are in the middle of even brightness, no need for filtering // we are in the middle of even brightness, no need for filtering
return getHardShadowColor(shadowsampler, smTexCoord.xy, realDistance); return getHardShadowColor(shadowsampler, smTexCoord.xy, realDistance);
} }
float baseLength = getBaseLength(smTexCoord); vec2 clampedpos;
float perspectiveFactor; vec4 visibility = vec4(0.0);
float x, y;
float texture_size = 1.0 / (f_textureresolution * 0.5); float bound = (1 + 0.5 * int(SOFTSHADOWRADIUS > 1.0)) * PCFBOUND; // scale max bound for soft shadows
float y, x; bound = clamp(0.5 * (4.0 * radius - 1.0), 0.5, bound);
float bound = clamp(PCFBOUND * (1 - baseLength), PCFBOUND / 2, PCFBOUND); float scale_factor = radius / bound / f_textureresolution;
int n = 0; float n = 0.0;
// basic PCF filter // basic PCF filter
for (y = -bound; y <= bound; y += 1.0) for (y = -bound; y <= bound; y += 1.0)
for (x = -bound; x <= bound; x += 1.0) { for (x = -bound; x <= bound; x += 1.0) {
clampedpos = vec2(x,y); // screen offset clampedpos = vec2(x,y) * scale_factor + smTexCoord.xy;
perspectiveFactor = getDeltaPerspectiveFactor(baseLength + length(clampedpos) * texture_size * radius / bound);
clampedpos = clampedpos * texture_size * perspectiveFactor * radius * perspectiveFactor / bound + smTexCoord.xy; // both dx,dy and radius are adjusted
visibility += getHardShadowColor(shadowsampler, clampedpos.xy, realDistance); visibility += getHardShadowColor(shadowsampler, clampedpos.xy, realDistance);
n += 1; n += 1.0;
} }
return visibility / n; return visibility / max(n, 1.0);
} }
#else #else
float getShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) float getShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
{ {
vec2 clampedpos; float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance);
float visibility = 0.0;
float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance, 1.0);
if (radius < 0.1) { if (radius < 0.1) {
// we are in the middle of even brightness, no need for filtering // we are in the middle of even brightness, no need for filtering
return getHardShadow(shadowsampler, smTexCoord.xy, realDistance); return getHardShadow(shadowsampler, smTexCoord.xy, realDistance);
} }
float baseLength = getBaseLength(smTexCoord); vec2 clampedpos;
float perspectiveFactor; float visibility = 0.0;
float x, y;
float texture_size = 1.0 / (f_textureresolution * 0.5); float bound = (1 + 0.5 * int(SOFTSHADOWRADIUS > 1.0)) * PCFBOUND; // scale max bound for soft shadows
float y, x; bound = clamp(0.5 * (4.0 * radius - 1.0), 0.5, bound);
float bound = clamp(PCFBOUND * (1 - baseLength), PCFBOUND / 2, PCFBOUND); float scale_factor = radius / bound / f_textureresolution;
int n = 0; float n = 0.0;
// basic PCF filter // basic PCF filter
for (y = -bound; y <= bound; y += 1.0) for (y = -bound; y <= bound; y += 1.0)
for (x = -bound; x <= bound; x += 1.0) { for (x = -bound; x <= bound; x += 1.0) {
clampedpos = vec2(x,y); // screen offset clampedpos = vec2(x,y) * scale_factor + smTexCoord.xy;
perspectiveFactor = getDeltaPerspectiveFactor(baseLength + length(clampedpos) * texture_size * radius / bound);
clampedpos = clampedpos * texture_size * perspectiveFactor * radius * perspectiveFactor / bound + smTexCoord.xy; // both dx,dy and radius are adjusted
visibility += getHardShadow(shadowsampler, clampedpos.xy, realDistance); visibility += getHardShadow(shadowsampler, clampedpos.xy, realDistance);
n += 1; n += 1.0;
} }
return visibility / n; return visibility / max(n, 1.0);
} }
#endif #endif

@ -39,6 +39,7 @@ centroid varying vec2 varTexCoord;
varying float adj_shadow_strength; varying float adj_shadow_strength;
varying float f_normal_length; varying float f_normal_length;
varying vec3 shadow_position; varying vec3 shadow_position;
varying float perspective_factor;
#endif #endif
@ -253,6 +254,7 @@ void main(void)
shadow_position = applyPerspectiveDistortion(m_ShadowViewProj * mWorld * (inVertexPosition + vec4(normalOffsetScale * nNormal, 0.0))).xyz; shadow_position = applyPerspectiveDistortion(m_ShadowViewProj * mWorld * (inVertexPosition + vec4(normalOffsetScale * nNormal, 0.0))).xyz;
shadow_position.z -= z_bias; shadow_position.z -= z_bias;
perspective_factor = pFactor;
if (f_timeofday < 0.2) { if (f_timeofday < 0.2) {
adj_shadow_strength = f_shadow_strength * 0.5 * adj_shadow_strength = f_shadow_strength * 0.5 *

@ -1,6 +1,5 @@
uniform sampler2D baseTexture; uniform sampler2D baseTexture;
uniform vec4 emissiveColor;
uniform vec3 dayLight; uniform vec3 dayLight;
uniform vec4 skyBgColor; uniform vec4 skyBgColor;
uniform float fogDistance; uniform float fogDistance;
@ -26,6 +25,7 @@ uniform float animationTimer;
varying float cosLight; varying float cosLight;
varying float f_normal_length; varying float f_normal_length;
varying vec3 shadow_position; varying vec3 shadow_position;
varying float perspective_factor;
#endif #endif
@ -119,23 +119,16 @@ float getHardShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance
#if SHADOW_FILTER == 2 #if SHADOW_FILTER == 2
#define PCFBOUND 3.5 #define PCFBOUND 2.0 // 5x5
#define PCFSAMPLES 64.0 #define PCFSAMPLES 25
#elif SHADOW_FILTER == 1 #elif SHADOW_FILTER == 1
#define PCFBOUND 1.5 #define PCFBOUND 1.0 // 3x3
#if defined(POISSON_FILTER) #define PCFSAMPLES 9
#define PCFSAMPLES 32.0
#else
#define PCFSAMPLES 16.0
#endif
#else #else
#define PCFBOUND 0.0 #define PCFBOUND 0.0
#if defined(POISSON_FILTER) #define PCFSAMPLES 1
#define PCFSAMPLES 4.0
#else
#define PCFSAMPLES 1.0
#endif
#endif #endif
#ifdef COLORED_SHADOWS #ifdef COLORED_SHADOWS
float getHardShadowDepth(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) float getHardShadowDepth(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
{ {
@ -152,59 +145,31 @@ float getHardShadowDepth(sampler2D shadowsampler, vec2 smTexCoord, float realDis
} }
#endif #endif
float getBaseLength(vec2 smTexCoord) #define BASEFILTERRADIUS 1.0
{
float l = length(2.0 * smTexCoord.xy - 1.0 - CameraPos.xy); // length in texture coords
return xyPerspectiveBias1 / (1.0 / l - xyPerspectiveBias0); // return to undistorted coords
}
float getDeltaPerspectiveFactor(float l) float getPenumbraRadius(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
{ {
return 0.04 * pow(512.0 / f_textureresolution, 0.4) / (xyPerspectiveBias0 * l + xyPerspectiveBias1); // original distortion factor, divided by 10
}
float getPenumbraRadius(sampler2D shadowsampler, vec2 smTexCoord, float realDistance, float multiplier)
{
float baseLength = getBaseLength(smTexCoord);
float perspectiveFactor;
// Return fast if sharp shadows are requested // Return fast if sharp shadows are requested
if (PCFBOUND == 0.0) if (PCFBOUND == 0.0 || SOFTSHADOWRADIUS <= 0.0)
return 0.0; return 0.0;
if (SOFTSHADOWRADIUS <= 1.0) {
perspectiveFactor = getDeltaPerspectiveFactor(baseLength);
return max(2 * length(smTexCoord.xy) * 2048 / f_textureresolution / pow(perspectiveFactor, 3), SOFTSHADOWRADIUS);
}
vec2 clampedpos; vec2 clampedpos;
float texture_size = 1.0 / (2048 /*f_textureresolution*/ * 0.5);
float y, x; float y, x;
float depth = 0.0; float depth = getHardShadowDepth(shadowsampler, smTexCoord.xy, realDistance);
float pointDepth; // A factor from 0 to 1 to reduce blurring of short shadows
float maxRadius = SOFTSHADOWRADIUS * 5.0 * multiplier; float sharpness_factor = 1.0;
// conversion factor from shadow depth to blur radius
float depth_to_blur = f_shadowfar / SOFTSHADOWRADIUS / xyPerspectiveBias0;
if (depth > 0.0 && f_normal_length > 0.0)
// 5 is empirical factor that controls how fast shadow loses sharpness
sharpness_factor = clamp(5 * depth * depth_to_blur, 0.0, 1.0);
depth = 0.0;
float bound = clamp(PCFBOUND * (1 - baseLength), 0.0, PCFBOUND); float world_to_texture = xyPerspectiveBias1 / perspective_factor / perspective_factor
int n = 0; * f_textureresolution / 2.0 / f_shadowfar;
float world_radius = 0.2; // shadow blur radius in world float coordinates, e.g. 0.2 = 0.02 of one node
for (y = -bound; y <= bound; y += 1.0) return max(BASEFILTERRADIUS * f_textureresolution / 4096.0, sharpness_factor * world_radius * world_to_texture * SOFTSHADOWRADIUS);
for (x = -bound; x <= bound; x += 1.0) {
clampedpos = vec2(x,y);
perspectiveFactor = getDeltaPerspectiveFactor(baseLength + length(clampedpos) * texture_size * maxRadius);
clampedpos = clampedpos * texture_size * perspectiveFactor * maxRadius * perspectiveFactor + smTexCoord.xy;
pointDepth = getHardShadowDepth(shadowsampler, clampedpos.xy, realDistance);
if (pointDepth > -0.01) {
depth += pointDepth;
n += 1;
}
}
depth = depth / n;
depth = pow(clamp(depth, 0.0, 1000.0), 1.6) / 0.001;
perspectiveFactor = getDeltaPerspectiveFactor(baseLength);
return max(length(smTexCoord.xy) * 2 * 2048 / f_textureresolution / pow(perspectiveFactor, 3), depth * maxRadius);
} }
#ifdef POISSON_FILTER #ifdef POISSON_FILTER
@ -279,26 +244,23 @@ const vec2[64] poissonDisk = vec2[64](
vec4 getShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) vec4 getShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
{ {
vec2 clampedpos; float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance);
vec4 visibility = vec4(0.0);
float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance, 1.5); // scale to align with PCF
if (radius < 0.1) { if (radius < 0.1) {
// we are in the middle of even brightness, no need for filtering // we are in the middle of even brightness, no need for filtering
return getHardShadowColor(shadowsampler, smTexCoord.xy, realDistance); return getHardShadowColor(shadowsampler, smTexCoord.xy, realDistance);
} }
float baseLength = getBaseLength(smTexCoord); vec2 clampedpos;
float perspectiveFactor; vec4 visibility = vec4(0.0);
float scale_factor = radius / f_textureresolution;
float texture_size = 1.0 / (f_textureresolution * 0.5); int samples = (1 + 1 * int(SOFTSHADOWRADIUS > 1.0)) * PCFSAMPLES; // scale max samples for the soft shadows
int samples = int(clamp(PCFSAMPLES * (1 - baseLength) * (1 - baseLength), PCFSAMPLES / 4, PCFSAMPLES)); samples = int(clamp(pow(4.0 * radius + 1.0, 2.0), 1.0, float(samples)));
int init_offset = int(floor(mod(((smTexCoord.x * 34.0) + 1.0) * smTexCoord.y, 64.0-samples))); int init_offset = int(floor(mod(((smTexCoord.x * 34.0) + 1.0) * smTexCoord.y, 64.0-samples)));
int end_offset = int(samples) + init_offset; int end_offset = int(samples) + init_offset;
for (int x = init_offset; x < end_offset; x++) { for (int x = init_offset; x < end_offset; x++) {
clampedpos = poissonDisk[x]; clampedpos = poissonDisk[x] * scale_factor + smTexCoord.xy;
perspectiveFactor = getDeltaPerspectiveFactor(baseLength + length(clampedpos) * texture_size * radius);
clampedpos = clampedpos * texture_size * perspectiveFactor * radius * perspectiveFactor + smTexCoord.xy;
visibility += getHardShadowColor(shadowsampler, clampedpos.xy, realDistance); visibility += getHardShadowColor(shadowsampler, clampedpos.xy, realDistance);
} }
@ -309,26 +271,23 @@ vec4 getShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDistance
float getShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) float getShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
{ {
vec2 clampedpos; float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance);
float visibility = 0.0;
float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance, 1.5); // scale to align with PCF
if (radius < 0.1) { if (radius < 0.1) {
// we are in the middle of even brightness, no need for filtering // we are in the middle of even brightness, no need for filtering
return getHardShadow(shadowsampler, smTexCoord.xy, realDistance); return getHardShadow(shadowsampler, smTexCoord.xy, realDistance);
} }
float baseLength = getBaseLength(smTexCoord); vec2 clampedpos;
float perspectiveFactor; float visibility = 0.0;
float scale_factor = radius / f_textureresolution;
float texture_size = 1.0 / (f_textureresolution * 0.5); int samples = (1 + 1 * int(SOFTSHADOWRADIUS > 1.0)) * PCFSAMPLES; // scale max samples for the soft shadows
int samples = int(clamp(PCFSAMPLES * (1 - baseLength) * (1 - baseLength), PCFSAMPLES / 4, PCFSAMPLES)); samples = int(clamp(pow(4.0 * radius + 1.0, 2.0), 1.0, float(samples)));
int init_offset = int(floor(mod(((smTexCoord.x * 34.0) + 1.0) * smTexCoord.y, 64.0-samples))); int init_offset = int(floor(mod(((smTexCoord.x * 34.0) + 1.0) * smTexCoord.y, 64.0-samples)));
int end_offset = int(samples) + init_offset; int end_offset = int(samples) + init_offset;
for (int x = init_offset; x < end_offset; x++) { for (int x = init_offset; x < end_offset; x++) {
clampedpos = poissonDisk[x]; clampedpos = poissonDisk[x] * scale_factor + smTexCoord.xy;
perspectiveFactor = getDeltaPerspectiveFactor(baseLength + length(clampedpos) * texture_size * radius);
clampedpos = clampedpos * texture_size * perspectiveFactor * radius * perspectiveFactor + smTexCoord.xy;
visibility += getHardShadow(shadowsampler, clampedpos.xy, realDistance); visibility += getHardShadow(shadowsampler, clampedpos.xy, realDistance);
} }
@ -344,65 +303,57 @@ float getShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
vec4 getShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) vec4 getShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
{ {
vec2 clampedpos; float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance);
vec4 visibility = vec4(0.0);
float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance, 1.0);
if (radius < 0.1) { if (radius < 0.1) {
// we are in the middle of even brightness, no need for filtering // we are in the middle of even brightness, no need for filtering
return getHardShadowColor(shadowsampler, smTexCoord.xy, realDistance); return getHardShadowColor(shadowsampler, smTexCoord.xy, realDistance);
} }
float baseLength = getBaseLength(smTexCoord); vec2 clampedpos;
float perspectiveFactor; vec4 visibility = vec4(0.0);
float x, y;
float texture_size = 1.0 / (f_textureresolution * 0.5); float bound = (1 + 0.5 * int(SOFTSHADOWRADIUS > 1.0)) * PCFBOUND; // scale max bound for soft shadows
float y, x; bound = clamp(0.5 * (4.0 * radius - 1.0), 0.5, bound);
float bound = clamp(PCFBOUND * (1 - baseLength), PCFBOUND / 2, PCFBOUND); float scale_factor = radius / bound / f_textureresolution;
int n = 0; float n = 0.0;
// basic PCF filter // basic PCF filter
for (y = -bound; y <= bound; y += 1.0) for (y = -bound; y <= bound; y += 1.0)
for (x = -bound; x <= bound; x += 1.0) { for (x = -bound; x <= bound; x += 1.0) {
clampedpos = vec2(x,y); // screen offset clampedpos = vec2(x,y) * scale_factor + smTexCoord.xy;
perspectiveFactor = getDeltaPerspectiveFactor(baseLength + length(clampedpos) * texture_size * radius / bound);
clampedpos = clampedpos * texture_size * perspectiveFactor * radius * perspectiveFactor / bound + smTexCoord.xy; // both dx,dy and radius are adjusted
visibility += getHardShadowColor(shadowsampler, clampedpos.xy, realDistance); visibility += getHardShadowColor(shadowsampler, clampedpos.xy, realDistance);
n += 1; n += 1.0;
} }
return visibility / n; return visibility / max(n, 1.0);
} }
#else #else
float getShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) float getShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
{ {
vec2 clampedpos; float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance);
float visibility = 0.0;
float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance, 1.0);
if (radius < 0.1) { if (radius < 0.1) {
// we are in the middle of even brightness, no need for filtering // we are in the middle of even brightness, no need for filtering
return getHardShadow(shadowsampler, smTexCoord.xy, realDistance); return getHardShadow(shadowsampler, smTexCoord.xy, realDistance);
} }
float baseLength = getBaseLength(smTexCoord); vec2 clampedpos;
float perspectiveFactor; float visibility = 0.0;
float x, y;
float texture_size = 1.0 / (f_textureresolution * 0.5); float bound = (1 + 0.5 * int(SOFTSHADOWRADIUS > 1.0)) * PCFBOUND; // scale max bound for soft shadows
float y, x; bound = clamp(0.5 * (4.0 * radius - 1.0), 0.5, bound);
float bound = clamp(PCFBOUND * (1 - baseLength), PCFBOUND / 2, PCFBOUND); float scale_factor = radius / bound / f_textureresolution;
int n = 0; float n = 0.0;
// basic PCF filter // basic PCF filter
for (y = -bound; y <= bound; y += 1.0) for (y = -bound; y <= bound; y += 1.0)
for (x = -bound; x <= bound; x += 1.0) { for (x = -bound; x <= bound; x += 1.0) {
clampedpos = vec2(x,y); // screen offset clampedpos = vec2(x,y) * scale_factor + smTexCoord.xy;
perspectiveFactor = getDeltaPerspectiveFactor(baseLength + length(clampedpos) * texture_size * radius / bound);
clampedpos = clampedpos * texture_size * perspectiveFactor * radius * perspectiveFactor / bound + smTexCoord.xy; // both dx,dy and radius are adjusted
visibility += getHardShadow(shadowsampler, clampedpos.xy, realDistance); visibility += getHardShadow(shadowsampler, clampedpos.xy, realDistance);
n += 1; n += 1.0;
} }
return visibility / n; return visibility / max(n, 1.0);
} }
#endif #endif
@ -489,7 +440,6 @@ void main(void)
shadow_color = visibility.gba; shadow_color = visibility.gba;
#else #else
if (cosLight > 0.0 || f_normal_length < 1e-3) if (cosLight > 0.0 || f_normal_length < 1e-3)
if (cosLight > 0.0)
shadow_int = getShadow(ShadowMapSampler, posLightSpace.xy, posLightSpace.z); shadow_int = getShadow(ShadowMapSampler, posLightSpace.xy, posLightSpace.z);
else else
shadow_int = 1.0; shadow_int = 1.0;
@ -540,6 +490,6 @@ void main(void)
- fogShadingParameter * length(eyeVec) / fogDistance, 0.0, 1.0); - fogShadingParameter * length(eyeVec) / fogDistance, 0.0, 1.0);
col = mix(skyBgColor, col, clarity); col = mix(skyBgColor, col, clarity);
col = vec4(col.rgb, base.a); col = vec4(col.rgb, base.a);
gl_FragColor = col; gl_FragColor = col;
} }

@ -30,6 +30,7 @@ centroid varying vec2 varTexCoord;
varying float adj_shadow_strength; varying float adj_shadow_strength;
varying float f_normal_length; varying float f_normal_length;
varying vec3 shadow_position; varying vec3 shadow_position;
varying float perspective_factor;
#endif #endif
varying vec3 eyeVec; varying vec3 eyeVec;
@ -162,6 +163,7 @@ void main(void)
shadow_position = applyPerspectiveDistortion(m_ShadowViewProj * mWorld * (inVertexPosition + vec4(normalOffsetScale * nNormal, 0.0))).xyz; shadow_position = applyPerspectiveDistortion(m_ShadowViewProj * mWorld * (inVertexPosition + vec4(normalOffsetScale * nNormal, 0.0))).xyz;
shadow_position.z -= z_bias; shadow_position.z -= z_bias;
perspective_factor = pFactor;
if (f_timeofday < 0.2) { if (f_timeofday < 0.2) {
adj_shadow_strength = f_shadow_strength * 0.5 * adj_shadow_strength = f_shadow_strength * 0.5 *

@ -489,6 +489,8 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
// Do not enable filter on shadow texture to avoid visual artifacts // Do not enable filter on shadow texture to avoid visual artifacts
// with colored shadows. // with colored shadows.
// Filtering is done in shader code anyway // Filtering is done in shader code anyway
layer.BilinearFilter = false;
layer.AnisotropicFilter = false;
layer.TrilinearFilter = false; layer.TrilinearFilter = false;
} }
driver->setMaterial(material); driver->setMaterial(material);

@ -771,6 +771,8 @@ ShaderInfo ShaderSource::generateShader(const std::string &name,
shaders_header << "#define SOFTSHADOWRADIUS " << shadow_soft_radius << "\n"; shaders_header << "#define SOFTSHADOWRADIUS " << shadow_soft_radius << "\n";
} }
shaders_header << "#line 0\n"; // reset the line counter for meaningful diagnostics
std::string common_header = shaders_header.str(); std::string common_header = shaders_header.str();
std::string vertex_shader = m_sourcecache.getOrLoad(name, "opengl_vertex.glsl"); std::string vertex_shader = m_sourcecache.getOrLoad(name, "opengl_vertex.glsl");

@ -670,6 +670,7 @@ std::string ShadowRenderer::readShaderFile(const std::string &path)
std::string prefix; std::string prefix;
if (m_shadow_map_colored) if (m_shadow_map_colored)
prefix.append("#define COLORED_SHADOWS 1\n"); prefix.append("#define COLORED_SHADOWS 1\n");
prefix.append("#line 0\n");
std::string content; std::string content;
fs::ReadFile(path, content); fs::ReadFile(path, content);

@ -274,7 +274,7 @@ void set_default_settings()
settings->setDefault("shadow_filters", "1"); settings->setDefault("shadow_filters", "1");
settings->setDefault("shadow_poisson_filter", "true"); settings->setDefault("shadow_poisson_filter", "true");
settings->setDefault("shadow_update_frames", "8"); settings->setDefault("shadow_update_frames", "8");
settings->setDefault("shadow_soft_radius", "1.0"); settings->setDefault("shadow_soft_radius", "5.0");
settings->setDefault("shadow_sky_body_orbit_tilt", "0.0"); settings->setDefault("shadow_sky_body_orbit_tilt", "0.0");
// Input // Input