17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-03 15:30:21 +01:00

Add screen-space contact shadows for the sun, lamps and headlights

This commit is contained in:
2026-10-17 03:11:47 +02:00
parent c4da5c9867
commit 27e5c07837
8 changed files with 268 additions and 25 deletions

View File

@@ -8,6 +8,8 @@ layout(binding = LOCAL_SHADOW_TEX) uniform samplerCubeArrayShadow local_shadowma
layout(binding = LOCAL_SHADOW_LAMPS_TEX) uniform samplerCubeArrayShadow local_shadowmap_lamps;
// shadow maps of the headlights of vehicles, see gl::HEADLIGHT_SHADOW_TEX and gl::scene_ubs::headlight_shadow
layout(binding = HEADLIGHT_SHADOW_TEX) uniform sampler2DArrayShadow headlight_shadowmap;
// depth of the opaque scene drawn before the colour, for the contact shadows (gl::scene_ubs::contact_shadow_params)
layout(binding = SCENE_DEPTH_TEX) uniform sampler2D scene_depth;
#include <envmapping.glsl>
#include <conversion.glsl>
@@ -42,7 +44,83 @@ float length2(vec3 v)
return dot(v, v);
}
// how many more lamps and headlights the fragment traces contact shadows to, see add_headlights()
int contact_shadow_budget = 0;
// contact shadow of a light in direction Light_dir (view space, towards the light), 0..1: whether the depth of the scene drawn
// before the colour (scene_depth) holds a surface between the fragment and a point up to Length metres towards the light. it
// catches what the shadow maps are too coarse for (rails on the sleepers, bolts, edges of panels, where a thing stands on the
// ground), and things too small or near for them. only what is in the depth casts it, and only within the image.
// the ray starts a pixel and a half off the visible side of the surface: the depth there, sampled anywhere within a pixel of
// the projected point, then stays behind the ray on a flat surface, so it doesn't shadow itself. a surface nearer than the
// ray by more than the thickness is taken for something in front of the ray, not something it goes through.
// the shadow fades over the last part of the ray (it doesn't end with an edge) and over the last quarter of the range
float contact_shadow(vec3 light_dir, float raylength, float steps)
{
float range = contact_shadow_params.y;
vec3 position = f_pos.xyz;
float fade = 1.0 - smoothstep(range * 0.75, range, length(position));
if ((fade <= 0.0) || (raylength <= 0.0) || (steps < 1.0))
return 0.0;
vec3 normal = normalize(f_normal);
if (dot(normal, position) > 0.0)
normal = -normal;
if (dot(normal, light_dir) <= 0.0)
return 0.0;
vec2 size = vec2(textureSize(scene_depth, 0));
// the projection is perspective with the depth n / w (reverse z, infinite far plane): w is the distance along the axis
// of the view, and a depth d is a surface at n / d
float near = projection[3][2];
float distance = (projection * vec4(position, 1.0)).w;
float pixel = 2.0 * distance / (abs(projection[1][1]) * size.y);
vec4 start = projection * vec4(position + normal * (pixel * 1.5), 1.0);
vec4 ray = projection * vec4(light_dir * raylength, 0.0);
float thickness = 0.3 + pixel * 4.0;
float bias = pixel * 0.5;
// a different start along the ray in neighbouring pixels: the steps give noise instead of bands
float jitter = fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715))));
for (float i = 0.0; i < steps; i += 1.0)
{
float t = (i + jitter) / steps;
vec4 clip = start + ray * t;
if (clip.w <= near)
break;
vec2 coords = (clip.xy / clip.w) * 0.5 + 0.5;
if (any(lessThan(coords, vec2(0.0))) || any(greaterThan(coords, vec2(1.0))))
break;
float depth = texelFetch(scene_depth, min(ivec2(coords * size), ivec2(size) - 1), 0).r;
// nothing drawn there (the sky)
if (depth <= 0.0)
continue;
float nearer = clip.w - near / depth;
if ((nearer > bias) && (nearer < thickness))
return fade * (1.0 - smoothstep(0.6, 1.0, t));
}
return 0.0;
}
// contact shadow of a lamp or headlight Distance metres away, whose light reaches the fragment with Strength, for the first
// few lamps of a fragment (contact_shadow_lamps.x) which light it noticeably. the ray goes at most half way to the light,
// so the lamp doesn't shadow with its own fitting
float contact_shadow_lamp(vec3 light_dir, float distance, float strength)
{
if ((contact_shadow_budget <= 0) || (strength < 0.005))
return 0.0;
--contact_shadow_budget;
return contact_shadow(light_dir, min(contact_shadow_params.x, distance * 0.5), contact_shadow_params.w);
}
// share of the sun (the moon) held back before the fragment by the shadow map and the contact shadow, 0..1
float calc_shadow_map();
float calc_shadow()
{
float shadow = calc_shadow_map();
if ((contact_shadow_params.x > 0.0) && (shadow < 1.0))
shadow = max(shadow, contact_shadow(normalize(-lights[0].dir), contact_shadow_params.x, contact_shadow_params.z));
return shadow;
}
float calc_shadow_map()
{
#if SHADOWMAP_ENABLED
float distance = dot(f_pos.xyz, f_pos.xyz);
@@ -282,6 +360,9 @@ vec2 calc_headlights(light_s light, vec3 fragnormal, vec3 dpdx, vec3 dpdy)
// headlights with a shadow map: what is in the way holds the beam back
if ((light.headlight_weights.w > 0.5) && (lightintensity > 0.0) && (part.x > 0.0))
lightintensity *= 1.0 - headlight_beam_shadow(uint(light.headlight_weights.w - 0.5), fragnormal, light_dir);
if ((contact_shadow_budget > 0) && (lightintensity > 0.0) && (part.x > 0.0))
lightintensity *= 1.0 - contact_shadow_lamp(light_dir, distance,
part.x * atten * lightintensity * light.intensity * max(light.color.r, max(light.color.g, light.color.b)));
return part * atten * lightintensity;
}
@@ -352,6 +433,9 @@ vec2 calc_local_light(light_s light, vec3 fragnormal)
atten *= smoothstep(light.out_cutoff, light.in_cutoff, dot(-light_dir, normalize(light.dir)));
if ((atten > 0.0) && (light.headlight_weights.x >= 0.0) && (dot(fragnormal, light_dir) > 0.0))
atten *= 1.0 - local_light_shadow(light, fragnormal);
if ((atten > 0.0) && (contact_shadow_budget > 0))
atten *= 1.0 - contact_shadow_lamp(light_dir, sqrt(distance2),
atten * max(dot(fragnormal, light_dir), 0.0) * light.intensity * max(light.color.r, max(light.color.g, light.color.b)));
if (atten <= 0.0)
return vec2(0.0);
vec2 part = calc_light(light_dir, fragnormal);
@@ -378,6 +462,7 @@ vec3 add_headlights(vec3 fragcolor, vec3 fragnormal)
// worked out before the loops, which the fragments of a quad do not leave together
vec3 dpdx = dFdx(f_pos.xyz);
vec3 dpdy = dFdy(f_pos.xyz);
contact_shadow_budget = (contact_shadow_params.x > 0.0) ? int(contact_shadow_lamps.x) : 0;
if (lights_clustered != 0U)
{