17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-05 20:39:09 +01:00

Replace cubemap scenery reflections with SSR, add Vulkan ray traced reflections

This commit is contained in:
2026-10-25 13:54:49 +01:00
parent 89f11895fc
commit f20b1a3071
45 changed files with 2725 additions and 174 deletions

View File

@@ -95,6 +95,7 @@
#define CLOUD_COVERAGE_TEX 21
#define CLOUD_HISTORY_TEX 22
#define SCENE_DEPTH_TEX 23
#define SSR_HISTORY_TEX 24
#define WEATHER_UBO_BINDING 5
#define WEATHER_SCRATCH_SSBO_BINDING 9
#define MAX_CLOUD_LAYERS 4
@@ -289,6 +290,13 @@ layout(std140, binding = SCENE_UBO_BINDING) uniform scene_ubo
vec4 contact_shadow_lamps;
// cascades of the sun shadows fitted to the depth of the view, see gl::scene_ubs
vec4 shadow_fit;
// screen space reflections and the reflection of the surroundings, see gl::scene_ubs
mat4 ssr_reprojection;
mat4 ssr_reprojection_cab;
mat4 ssr_cab_box;
vec4 ssr_params;
vec4 ssr_params2;
vec4 env_params;
};
#ifdef STAGE_VERTEX

View File

@@ -1,66 +1,49 @@
// the environment map: the sky and the clouds around the camera (the reflection pass draws nothing else into it since the
// screen space reflections took over the scenery), with its mipmaps and the levels prefiltered with the GGX lobe
layout(binding = ENV_TEX) uniform samplerCube envmap;
// the same map prefiltered with the GGX lobe (envmap_prefilter.comp), used when env_ggx is 1
layout(binding = ENV_GGX_TEX) uniform samplerCube envmap_ggx;
vec3 envmap_color( vec3 normal )
// the world space direction of the reflection of the view off a surface of the normal (view space)
vec3 envmap_direction(vec3 normal)
{
vec3 refvec = reflect(f_pos.xyz, normal); // view space
return vec3(inv_view * vec4(refvec, 0.0)); // world space
}
// the map in a world space direction, at a level of its mipmaps
vec3 envmap_sample_lod(vec3 direction, float lod)
{
vec3 envcolor;
if (ENVMAP_ENABLED)
{
vec3 refvec = reflect(f_pos.xyz, normal); // view space
refvec = vec3(inv_view * vec4(refvec, 0.0)); // world space
envcolor = texture(envmap, refvec).rgb;
vec3 envcolor = textureLod(envmap, direction, lod).rgb;
// Sanitize. The env cubemap can briefly contain NaN/Inf after a
// reflection-pass regeneration (m_empty_cubemap was bound during the
// face render and on NVIDIA can sample as undefined). Without this
// guard, a single bad texel propagates through env_reflection in
// apply_lights() and produces one-frame black flashes on glossy
// (esp. specgloss) surfaces.
// face render and on NVIDIA can sample as undefined), and the mipmap
// generation propagates a single NaN texel across the whole mip chain.
// Without this guard a single bad texel produces one-frame black
// flashes on glossy (esp. specgloss) surfaces.
if (any(isnan(envcolor)) || any(isinf(envcolor))) envcolor = vec3(0.0);
envcolor = max(envcolor, vec3(0.0));
return max(envcolor, vec3(0.0));
}
else
{
envcolor = vec3(0.5);
return vec3(0.5);
}
return envcolor;
}
// Roughness-aware env map lookup — uses mip levels for blurry reflections.
// lod 0.0 = mirror sharp, lod ~8.0 = fully diffuse blur.
vec3 envmap_color_lod(vec3 fragnormal, float lod)
{
if (ENVMAP_ENABLED)
{
vec3 refvec = reflect(f_pos.xyz, fragnormal); // view space — matches envmap_color exactly
refvec = vec3(inv_view * vec4(refvec, 0.0)); // world space — was missing
vec3 envcolor = textureLod(envmap, refvec, lod).rgb;
// See envmap_color() above — same NaN/Inf sanitize, also needed here
// because mipmap generation propagates a single NaN texel across the
// whole mip chain.
if (any(isnan(envcolor)) || any(isinf(envcolor))) envcolor = vec3(0.0);
return max(envcolor, vec3(0.0));
}
else
{
return vec3(0.5); // was vec3(0.0), match the non-LOD fallback
}
}
// reflection of the surroundings in a surface of the given perceptual roughness (0 mirror, 1 matte)
vec3 envmap_color_rough(vec3 fragnormal, float roughness)
// the map in a world space direction for a surface of the given perceptual roughness (0 mirror, 1 matte)
vec3 envmap_sample_rough(vec3 direction, float roughness)
{
// the plain map, its mipmaps (box filtered) standing for the roughness
const float MAX_REFLECTION_LOD = 8.0;
vec3 boxcolor = envmap_color_lod(fragnormal, roughness * MAX_REFLECTION_LOD);
vec3 boxcolor = envmap_sample_lod(direction, roughness * MAX_REFLECTION_LOD);
if (ENVMAP_ENABLED)
{
if (env_ggx == 0U || roughness <= ENVMAP_GGX_ROUGHNESS_MIN)
return boxcolor;
vec3 refvec = reflect(f_pos.xyz, fragnormal);
refvec = vec3(inv_view * vec4(refvec, 0.0));
float level = (roughness - ENVMAP_GGX_ROUGHNESS_MIN) / (1.0 - ENVMAP_GGX_ROUGHNESS_MIN) * float(ENVMAP_GGX_LEVELS - 1U);
vec3 ggxcolor = textureLod(envmap_ggx, refvec, level).rgb;
vec3 ggxcolor = textureLod(envmap_ggx, direction, level).rgb;
if (any(isnan(ggxcolor)) || any(isinf(ggxcolor))) ggxcolor = vec3(0.0);
ggxcolor = max(ggxcolor, vec3(0.0));
// the prefiltered map takes over over the first tenth of the roughness above its lowest level
@@ -71,3 +54,21 @@ vec3 envmap_color_rough(vec3 fragnormal, float roughness)
return boxcolor;
}
}
vec3 envmap_color( vec3 normal )
{
return envmap_sample_lod(envmap_direction(normal), 0.0);
}
// Roughness-aware env map lookup — uses mip levels for blurry reflections.
// lod 0.0 = mirror sharp, lod ~8.0 = fully diffuse blur.
vec3 envmap_color_lod(vec3 fragnormal, float lod)
{
return envmap_sample_lod(envmap_direction(fragnormal), lod);
}
// reflection of the surroundings in a surface of the given perceptual roughness (0 mirror, 1 matte)
vec3 envmap_color_rough(vec3 fragnormal, float roughness)
{
return envmap_sample_rough(envmap_direction(fragnormal), roughness);
}

View File

@@ -10,6 +10,7 @@ layout(binding = HEADLIGHT_SHADOW_TEX) uniform sampler2DArrayShadow headlight_sh
layout(binding = SCENE_DEPTH_TEX) uniform sampler2D scene_depth;
#include "envmapping.glsl"
#include "ssr.glsl"
#include "conversion.glsl"
#include "weather_common.glsl"
@@ -657,6 +658,55 @@ vec3 add_sky_ambient(vec3 basecolor, vec3 fragnormal)
return max(basecolor + sky_ambient_directional(worldnormal) * (sky_ambient_factor * share), vec3(0.0));
}
// the light of the ground below the horizon of the environment map, which holds the sky alone: the ground of the albedo
// env_params.x lit by the sky and the sun, as a surface facing up
vec3 reflection_ground()
{
vec3 light = ambient;
if (sky_ambient_factor != 0.0)
light = max(light + sky_ambient_directional(vec3(0.0, 1.0, 0.0)) * sky_ambient_factor, vec3(0.0));
light *= AMBIENT_SCALE;
if (lights_count > 0U)
{
vec3 sun = -normalize(mat3(inv_view) * lights[0].dir);
light += lights[0].color * (SUN_DIFFUSE_SCALE * lights[0].intensity * max(sun.y, 0.0));
}
return light * env_params.x;
}
// the reflection of the surroundings the scene doesn't give (the screen space reflections, the ray tracing): the environment
// map above the horizon (the sky and the clouds), the ground below it
vec3 reflection_surroundings(vec3 fragnormal, float roughness)
{
vec3 direction = normalize(envmap_direction(fragnormal));
vec3 sky = envmap_sample_rough(normalize(vec3(direction.x, max(direction.y, 0.0), direction.z)), roughness);
float below = smoothstep(0.0, 0.25, -direction.y);
if ((below <= 0.0) || (false == ENVMAP_ENABLED))
return sky;
return mix(sky, reflection_ground(), below);
}
// the least weight of the reflection of the surroundings (its specular colour times its share) a surface traces the scene for
const float REFLECTION_SCENE_WEIGHT = 0.004;
// the reflection of the scene in a surface of the given perceptual roughness: the colour times how much of the reflection the
// scene gives (rgb), and that share (a), the rest is left to reflection_surroundings()
vec4 reflection_scene(vec3 fragnormal, float roughness)
{
vec3 direction = normalize(reflect(f_pos.xyz, fragnormal));
vec4 result = ssr_trace(f_pos.xyz, normalize(f_normal), direction, roughness);
#ifdef REFLECTION_RAYS_ACTIVE
// what the screen doesn't hold is traced through the scenery (shaders/vk14/rt_reflections.glsl)
if ((result.a < 0.99) && (ssr_params2.z > 0.0) && (roughness <= ssr_params.z))
{
vec4 traced = rt_trace(f_pos.xyz, normalize(f_normal), direction, roughness);
result.rgb += traced.rgb * (1.0 - result.a);
result.a += traced.a * (1.0 - result.a);
}
#endif
return result;
}
// lights the surface: the ambient light, the sun (the moon), the headlights and lamps and the reflection of the surroundings.
// the units are the ones of the ambient light, the irradiance over pi: a white lambertian surface lit by 1 of it shows 1.
// Texturecolor is the albedo (the base colour of a metal), Specularity the specular level of a dielectric (0.5 is the
@@ -734,13 +784,22 @@ vec3 shade_surface(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float ref
ambient_share = 1.0;
diffuse_albedo *= 1.0 - cubemap_spec * min(cubemap_share, 1.0);
}
vec3 envcolor = (matte ? vec3(0.0) : envmap_color_rough(fragnormal, env_roughness));
// the reflection of the surroundings: the scene where the screen (or the ray tracing) gives it, the sky and the ground of the
// environment map elsewhere
vec3 envcolor = vec3(0.0);
vec4 scenecolor = vec4(0.0);
if (!matte)
{
envcolor = reflection_surroundings(fragnormal, env_roughness);
if (((ssr_params.x > 0.0) || (ssr_params2.z > 0.0)) && (cubemap_share * dot(cubemap_spec, vec3(1.0 / 3.0)) > REFLECTION_SCENE_WEIGHT))
scenecolor = reflection_scene(fragnormal, env_roughness);
}
if (lights_count == 0U)
return fragcolor * diffuse_albedo
+ emissioncolor * texturecolor
+ fragcolor * env_spec * ambient_share
+ envcolor * cubemap_spec * cubemap_share;
+ (scenecolor.rgb + envcolor * (1.0 - scenecolor.a)) * cubemap_spec * cubemap_share;
vec3 light_dir = normalize(-lights[0].dir);
float NdotL = max(dot(fragnormal, light_dir), 0.0);
@@ -757,7 +816,8 @@ vec3 shade_surface(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float ref
if (shadowtone < 1.0)
shadow = clamp(calc_shadow(), 0.0, 1.0);
// the cubemap knows nothing of what shades the fragment: it is dimmed a little in the shadow of the sun
// the environment map knows nothing of what shades the fragment: it is dimmed a little in the shadow of the sun (the scene the
// reflection finds is lit as it is)
float cubemap_shadow = 1.0;
if (sunless)
{
@@ -771,7 +831,7 @@ vec3 shade_surface(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float ref
// the reflection of the surroundings, before the light of the sun and the lamps joins the ambient light
vec3 env_reflection = fragcolor * env_spec * ambient_share
+ envcolor * cubemap_spec * (cubemap_share * cubemap_shadow);
+ (scenecolor.rgb + envcolor * ((1.0 - scenecolor.a) * cubemap_shadow)) * cubemap_spec * cubemap_share;
// the shadow leaves the share of the sun Shadowtone tells (the old shadow colour of the scenery) to the diffuse light,
// nothing to the highlight

155
shaders/ogl46/ssr.glsl Normal file
View File

@@ -0,0 +1,155 @@
// screen space reflections (gfx.ssr) of the colour pass of the main view, included by light_common.glsl. the reflection of a
// surface is traced as a ray through the depth of the opaque scene drawn before the colour (scene_depth: the reverse z of a
// projection with the far plane at infinity, a depth d is a surface n / d away along the axis of the view), and its colour is
// taken from the image of the last frame (ssr_history) at the place the point hit had in that frame. what the screen doesn't
// hold (off the image, behind something nearer, too far, too rough) is left to the caller: the alpha of the result says how much
// of the reflection the screen gave. scene_ubs.ssr_params is set only for the main view, zero elsewhere
layout(binding = SSR_HISTORY_TEX) uniform sampler2D ssr_history;
// the steps of a ray that found something are refined this many times, halving the step
const int SSR_REFINE_STEPS = 5;
// a value 0 .. 1 different for the neighbouring pixels (interleaved gradient noise), and from frame to frame with the temporal
// anti-aliasing, which blends the steps of the rays into a smooth image
float ssr_noise()
{
vec2 pixel = gl_FragCoord.xy + 5.588238 * ssr_params.w;
return fract(52.9829189 * fract(dot(pixel, vec2(0.06711056, 0.00583715))));
}
// the distance along the axis of the view of the surface in the depth at a place of the image (0 .. 1), 0: nothing there (the sky)
float ssr_surface(vec2 coords, vec2 size, float near)
{
float depth = texelFetch(scene_depth, clamp(ivec2(coords * size), ivec2(0), ivec2(size) - 1), 0).r;
return (depth > 0.0 ? near / depth : 0.0);
}
// the reflection the screen holds, of a surface at Position (view space) with the geometric normal Normal (view space, either
// side), for a ray along Direction (view space, normalized) and the perceptual roughness Roughness: the colour (rgb) times how
// much of it the screen holds (a)
vec4 ssr_trace(vec3 position, vec3 normal, vec3 direction, float roughness)
{
float steps = ssr_params.x;
if ((steps < 1.0) || (roughness > ssr_params.z))
return vec4(0.0);
// the side of the surface the camera sees; a ray going into it (a normal map turned far from the face) finds nothing
if (dot(normal, position) > 0.0)
normal = -normal;
if (dot(normal, direction) <= 0.0)
return vec4(0.0);
vec2 size = vec2(textureSize(scene_depth, 0));
float near = projection[3][2];
float pixelscale = 2.0 / (abs(projection[1][1]) * size.y);
// the ray starts two pixels off the surface, so the depth of the surface itself doesn't stop it
vec4 start = projection * vec4(position, 1.0);
vec3 origin = position + normal * (start.w * pixelscale * 2.0);
vec4 c0 = projection * vec4(origin, 1.0);
// the distance along the axis of the view changes with the ray: it ends in front of the near plane
float dw = (projection * vec4(direction, 0.0)).w;
float raylength = ssr_params.y;
if (dw < 0.0)
raylength = min(raylength, (c0.w - near * 2.0) / -dw);
if (raylength <= 0.0)
return vec4(0.0);
vec3 end = origin + direction * raylength;
vec4 c1 = projection * vec4(end, 1.0);
// the points of the ray are interpolated across the image (u) with 1 / w, which is linear there
float k0 = 1.0 / c0.w;
float k1 = 1.0 / c1.w;
vec2 s0 = c0.xy * k0;
vec2 s1 = c1.xy * k1;
vec2 delta = s1 - s0;
// the part of the ray over the image
float umax = 1.0;
if (abs(delta.x) > 1e-6)
umax = min(umax, ((delta.x > 0.0 ? 1.0 : -1.0) - s0.x) / delta.x);
if (abs(delta.y) > 1e-6)
umax = min(umax, ((delta.y > 0.0 ? 1.0 : -1.0) - s0.y) / delta.y);
if (umax <= 0.0)
return vec4(0.0);
// not more steps than the pixels the ray crosses
float pixels = length(delta * umax * size * 0.5);
float count = clamp(floor(pixels), 1.0, steps);
float jitter = ssr_noise();
float uprev = 0.0;
float wprev = c0.w;
float uhit = -1.0;
for (float i = 0.0; i < count; i += 1.0)
{
float u = umax * min((i + jitter) / count, 1.0);
float w = 1.0 / mix(k0, k1, u);
vec2 coords = mix(s0, s1, u) * 0.5 + 0.5;
float surface = ssr_surface(coords, size, near);
float bias = w * pixelscale * 0.5;
// the things in the depth are taken to be this thick behind their faces, with the length of the step on top
float thickness = max(0.25, surface * 0.02) + abs(w - wprev);
if ((surface > 0.0) && (w - surface > bias) && (w - surface < thickness))
{
// refined between the last step in front of the surface and this one behind it
float lo = uprev;
float hi = u;
for (int r = 0; r < SSR_REFINE_STEPS; ++r)
{
float mid = 0.5 * (lo + hi);
float wmid = 1.0 / mix(k0, k1, mid);
float smid = ssr_surface(mix(s0, s1, mid) * 0.5 + 0.5, size, near);
if ((smid > 0.0) && (wmid - smid > 0.0))
hi = mid;
else
lo = mid;
}
// what the ray went behind has to be close to it there: otherwise the ray went behind something thin, and goes on
float whit = 1.0 / mix(k0, k1, hi);
float shit = ssr_surface(mix(s0, s1, hi) * 0.5 + 0.5, size, near);
if ((shit > 0.0) && (abs(whit - shit) < max(0.25, shit * 0.02) + whit * pixelscale * 2.0))
{
uhit = hi;
break;
}
}
uprev = u;
wprev = w;
}
if (uhit < 0.0)
return vec4(0.0);
// the point hit, view space
vec3 hit = mix(origin * k0, end * k1, uhit) / mix(k0, k1, uhit);
vec2 hitcoords = mix(s0, s1, uhit) * 0.5 + 0.5;
// where it was in the last frame: the cab moves with the camera, the world stands still
mat4 reprojection = ssr_reprojection;
if (ssr_params2.y > 0.0)
{
vec3 box = (ssr_cab_box * vec4(hit, 1.0)).xyz;
if (all(lessThanEqual(abs(box), vec3(1.0))))
reprojection = ssr_reprojection_cab;
}
vec4 previous = reprojection * vec4(hit, 1.0);
if (previous.w <= 0.0)
return vec4(0.0);
vec2 coords = (previous.xy / previous.w) * 0.5 + 0.5;
float share = 1.0;
// faded towards the edges of the image, where it was hit and where it was in the last frame
vec2 edges = min(min(hitcoords, 1.0 - hitcoords), min(coords, 1.0 - coords));
share *= smoothstep(0.0, 0.04, min(edges.x, edges.y));
// and towards the end of the ray
float travelled = length(hit - origin);
share *= 1.0 - smoothstep(0.75, 1.0, travelled / ssr_params.y);
// the rougher surfaces hand over to the environment map
share *= 1.0 - smoothstep(ssr_params.z * 0.7, ssr_params.z, roughness);
// a ray towards the camera meets the backs of what the image shows
share *= 1.0 - smoothstep(0.4, 0.9, direction.z);
if (share <= 0.0)
return vec4(0.0);
// the lobe of a rough surface covers more of the image the farther the point hit is: a blurrier level of the history
float alpha = roughness * roughness;
float hitpixel = max((projection * vec4(hit, 1.0)).w, near) * pixelscale;
float lod = clamp(log2(max(2.0 * travelled * alpha / hitpixel, 1.0)), 0.0, ssr_params2.x);
vec3 color = textureLod(ssr_history, coords, lod).rgb;
if (any(isnan(color)) || any(isinf(color)))
return vec4(0.0);
return vec4(max(color, vec3(0.0)) * share, share);
}

View File

@@ -59,7 +59,8 @@
#define LOCAL_SHADOW_LAMPS_TEX 22
#define HEADLIGHT_SHADOW_TEX 23
#define SCENE_DEPTH_TEX 24
#define PASS_BINDING_COUNT 25
#define SSR_HISTORY_TEX 25
#define PASS_BINDING_COUNT 26
#define MATERIAL_TEXTURE_FIRST 0
#define CL_CLUSTER_UBO 0
#define CL_AABB_SSBO 1
@@ -72,6 +73,10 @@
#define POST_TEXTURES 4
#define POST_EXPOSURE_SSBO 4
#define COMPUTE_SET 1
#define RT_SET 2
#define RT_SCENE 0
#define RT_RECORDS 1
#define RT_TEXTURES 2
#define PUSH_CONSTANTS_SIZE 112
#define SPEC_OUTPUT_GAMMA 0
#define SPEC_SHADOWMAP_ENABLED 1
@@ -259,6 +264,12 @@ layout(std140, set = PASS_SET, binding = SCENE_UBO) uniform scene_ubo
vec4 contact_shadow_params;
vec4 contact_shadow_lamps;
vec4 shadow_fit;
mat4 ssr_reprojection;
mat4 ssr_reprojection_cab;
mat4 ssr_cab_box;
vec4 ssr_params;
vec4 ssr_params2;
vec4 env_params;
};
#ifndef LAYOUT_COMPUTE

View File

@@ -1,66 +1,49 @@
// the environment map: the sky and the clouds around the camera (the reflection pass draws nothing else into it since the
// screen space reflections took over the scenery), with its mipmaps and the levels prefiltered with the GGX lobe
layout(set = PASS_SET, binding = ENV_TEX) uniform samplerCube envmap;
// the same map prefiltered with the GGX lobe (envmap_prefilter.comp), used when env_ggx is 1
layout(set = PASS_SET, binding = ENV_GGX_TEX) uniform samplerCube envmap_ggx;
vec3 envmap_color( vec3 normal )
// the world space direction of the reflection of the view off a surface of the normal (view space)
vec3 envmap_direction(vec3 normal)
{
vec3 refvec = reflect(f_pos.xyz, normal); // view space
return vec3(inv_view * vec4(refvec, 0.0)); // world space
}
// the map in a world space direction, at a level of its mipmaps
vec3 envmap_sample_lod(vec3 direction, float lod)
{
vec3 envcolor;
if (ENVMAP_ENABLED)
{
vec3 refvec = reflect(f_pos.xyz, normal); // view space
refvec = vec3(inv_view * vec4(refvec, 0.0)); // world space
envcolor = texture(envmap, refvec).rgb;
vec3 envcolor = textureLod(envmap, direction, lod).rgb;
// Sanitize. The env cubemap can briefly contain NaN/Inf after a
// reflection-pass regeneration (m_empty_cubemap was bound during the
// face render and on NVIDIA can sample as undefined). Without this
// guard, a single bad texel propagates through env_reflection in
// apply_lights() and produces one-frame black flashes on glossy
// (esp. specgloss) surfaces.
// face render and on NVIDIA can sample as undefined), and the mipmap
// generation propagates a single NaN texel across the whole mip chain.
// Without this guard a single bad texel produces one-frame black
// flashes on glossy (esp. specgloss) surfaces.
if (any(isnan(envcolor)) || any(isinf(envcolor))) envcolor = vec3(0.0);
envcolor = max(envcolor, vec3(0.0));
return max(envcolor, vec3(0.0));
}
else
{
envcolor = vec3(0.5);
return vec3(0.5);
}
return envcolor;
}
// Roughness-aware env map lookup — uses mip levels for blurry reflections.
// lod 0.0 = mirror sharp, lod ~8.0 = fully diffuse blur.
vec3 envmap_color_lod(vec3 fragnormal, float lod)
{
if (ENVMAP_ENABLED)
{
vec3 refvec = reflect(f_pos.xyz, fragnormal); // view space — matches envmap_color exactly
refvec = vec3(inv_view * vec4(refvec, 0.0)); // world space — was missing
vec3 envcolor = textureLod(envmap, refvec, lod).rgb;
// See envmap_color() above — same NaN/Inf sanitize, also needed here
// because mipmap generation propagates a single NaN texel across the
// whole mip chain.
if (any(isnan(envcolor)) || any(isinf(envcolor))) envcolor = vec3(0.0);
return max(envcolor, vec3(0.0));
}
else
{
return vec3(0.5); // was vec3(0.0), match the non-LOD fallback
}
}
// reflection of the surroundings in a surface of the given perceptual roughness (0 mirror, 1 matte)
vec3 envmap_color_rough(vec3 fragnormal, float roughness)
// the map in a world space direction for a surface of the given perceptual roughness (0 mirror, 1 matte)
vec3 envmap_sample_rough(vec3 direction, float roughness)
{
// the plain map, its mipmaps (box filtered) standing for the roughness
const float MAX_REFLECTION_LOD = 8.0;
vec3 boxcolor = envmap_color_lod(fragnormal, roughness * MAX_REFLECTION_LOD);
vec3 boxcolor = envmap_sample_lod(direction, roughness * MAX_REFLECTION_LOD);
if (ENVMAP_ENABLED)
{
if (env_ggx == 0U || roughness <= ENVMAP_GGX_ROUGHNESS_MIN)
return boxcolor;
vec3 refvec = reflect(f_pos.xyz, fragnormal);
refvec = vec3(inv_view * vec4(refvec, 0.0));
float level = (roughness - ENVMAP_GGX_ROUGHNESS_MIN) / (1.0 - ENVMAP_GGX_ROUGHNESS_MIN) * float(ENVMAP_GGX_LEVELS - 1U);
vec3 ggxcolor = textureLod(envmap_ggx, refvec, level).rgb;
vec3 ggxcolor = textureLod(envmap_ggx, direction, level).rgb;
if (any(isnan(ggxcolor)) || any(isinf(ggxcolor))) ggxcolor = vec3(0.0);
ggxcolor = max(ggxcolor, vec3(0.0));
// the prefiltered map takes over over the first tenth of the roughness above its lowest level
@@ -71,3 +54,21 @@ vec3 envmap_color_rough(vec3 fragnormal, float roughness)
return boxcolor;
}
}
vec3 envmap_color( vec3 normal )
{
return envmap_sample_lod(envmap_direction(normal), 0.0);
}
// Roughness-aware env map lookup — uses mip levels for blurry reflections.
// lod 0.0 = mirror sharp, lod ~8.0 = fully diffuse blur.
vec3 envmap_color_lod(vec3 fragnormal, float lod)
{
return envmap_sample_lod(envmap_direction(fragnormal), lod);
}
// reflection of the surroundings in a surface of the given perceptual roughness (0 mirror, 1 matte)
vec3 envmap_color_rough(vec3 fragnormal, float roughness)
{
return envmap_sample_rough(envmap_direction(fragnormal), roughness);
}

View File

@@ -14,6 +14,7 @@ layout(set = PASS_SET, binding = HEADLIGHT_SHADOW_TEX) uniform sampler2DArraySha
layout(set = PASS_SET, binding = SCENE_DEPTH_TEX) uniform sampler2D scene_depth;
#include "envmapping.glsl"
#include "ssr.glsl"
#include "conversion.glsl"
#include "weather_common.glsl"
@@ -661,6 +662,61 @@ vec3 add_sky_ambient(vec3 basecolor, vec3 fragnormal)
return max(basecolor + sky_ambient_directional(worldnormal) * (sky_ambient_factor * share), vec3(0.0));
}
// the light of the ground below the horizon of the environment map, which holds the sky alone: the ground of the albedo
// env_params.x lit by the sky and the sun, as a surface facing up
vec3 reflection_ground()
{
vec3 light = ambient;
if (sky_ambient_factor != 0.0)
light = max(light + sky_ambient_directional(vec3(0.0, 1.0, 0.0)) * sky_ambient_factor, vec3(0.0));
light *= AMBIENT_SCALE;
if (lights_count > 0U)
{
vec3 sun = -normalize(mat3(inv_view) * lights[0].dir);
light += lights[0].color * (SUN_DIFFUSE_SCALE * lights[0].intensity * max(sun.y, 0.0));
}
return light * env_params.x;
}
// the reflection of the surroundings the scene doesn't give (the screen space reflections, the ray tracing): the environment
// map above the horizon (the sky and the clouds), the ground below it
vec3 reflection_surroundings(vec3 fragnormal, float roughness)
{
vec3 direction = normalize(envmap_direction(fragnormal));
vec3 sky = envmap_sample_rough(normalize(vec3(direction.x, max(direction.y, 0.0), direction.z)), roughness);
float below = smoothstep(0.0, 0.25, -direction.y);
if ((below <= 0.0) || (false == ENVMAP_ENABLED))
return sky;
return mix(sky, reflection_ground(), below);
}
// the ray traced reflections, in the fragment shaders of the scene (the layout with the set of the ray tracing)
#if defined(REFLECTION_RAYS) && defined(STAGE_FRAGMENT) && !defined(LAYOUT_COMPUTE) && !defined(LAYOUT_POST) && !defined(LAYOUT_CLUSTER) && !defined(LAYOUT_UI)
#define REFLECTION_RAYS_ACTIVE 1
#include "rt_reflections.glsl"
#endif
// the least weight of the reflection of the surroundings (its specular colour times its share) a surface traces the scene for
const float REFLECTION_SCENE_WEIGHT = 0.004;
// the reflection of the scene in a surface of the given perceptual roughness: the colour times how much of the reflection the
// scene gives (rgb), and that share (a), the rest is left to reflection_surroundings()
vec4 reflection_scene(vec3 fragnormal, float roughness)
{
vec3 direction = normalize(reflect(f_pos.xyz, fragnormal));
vec4 result = ssr_trace(f_pos.xyz, normalize(f_normal), direction, roughness);
#ifdef REFLECTION_RAYS_ACTIVE
// what the screen doesn't hold is traced through the scenery (shaders/vk14/rt_reflections.glsl)
if ((result.a < 0.99) && (ssr_params2.z > 0.0) && (roughness <= ssr_params.z))
{
vec4 traced = rt_trace(f_pos.xyz, normalize(f_normal), direction, roughness);
result.rgb += traced.rgb * (1.0 - result.a);
result.a += traced.a * (1.0 - result.a);
}
#endif
return result;
}
// lights the surface: the ambient light, the sun (the moon), the headlights and lamps and the reflection of the surroundings.
// the units are the ones of the ambient light, the irradiance over pi: a white lambertian surface lit by 1 of it shows 1.
// Texturecolor is the albedo (the base colour of a metal), Specularity the specular level of a dielectric (0.5 is the
@@ -738,13 +794,22 @@ vec3 shade_surface(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float ref
ambient_share = 1.0;
diffuse_albedo *= 1.0 - cubemap_spec * min(cubemap_share, 1.0);
}
vec3 envcolor = (matte ? vec3(0.0) : envmap_color_rough(fragnormal, env_roughness));
// the reflection of the surroundings: the scene where the screen (or the ray tracing) gives it, the sky and the ground of the
// environment map elsewhere
vec3 envcolor = vec3(0.0);
vec4 scenecolor = vec4(0.0);
if (!matte)
{
envcolor = reflection_surroundings(fragnormal, env_roughness);
if (((ssr_params.x > 0.0) || (ssr_params2.z > 0.0)) && (cubemap_share * dot(cubemap_spec, vec3(1.0 / 3.0)) > REFLECTION_SCENE_WEIGHT))
scenecolor = reflection_scene(fragnormal, env_roughness);
}
if (lights_count == 0U)
return fragcolor * diffuse_albedo
+ emissioncolor * texturecolor
+ fragcolor * env_spec * ambient_share
+ envcolor * cubemap_spec * cubemap_share;
+ (scenecolor.rgb + envcolor * (1.0 - scenecolor.a)) * cubemap_spec * cubemap_share;
vec3 light_dir = normalize(-lights[0].dir);
float NdotL = max(dot(fragnormal, light_dir), 0.0);
@@ -761,7 +826,8 @@ vec3 shade_surface(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float ref
if (shadowtone < 1.0)
shadow = clamp(calc_shadow(), 0.0, 1.0);
// the cubemap knows nothing of what shades the fragment: it is dimmed a little in the shadow of the sun
// the environment map knows nothing of what shades the fragment: it is dimmed a little in the shadow of the sun (the scene the
// reflection finds is lit as it is)
float cubemap_shadow = 1.0;
if (sunless)
{
@@ -775,7 +841,7 @@ vec3 shade_surface(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float ref
// the reflection of the surroundings, before the light of the sun and the lamps joins the ambient light
vec3 env_reflection = fragcolor * env_spec * ambient_share
+ envcolor * cubemap_spec * (cubemap_share * cubemap_shadow);
+ (scenecolor.rgb + envcolor * ((1.0 - scenecolor.a) * cubemap_shadow)) * cubemap_spec * cubemap_share;
// the shadow leaves the share of the sun Shadowtone tells (the old shadow colour of the scenery) to the diffuse light,
// nothing to the highlight

View File

@@ -0,0 +1,147 @@
// the ray traced reflections of the vulkan renderer (gfx.raytracing, vk14::ray_tracing), included by light_common.glsl in the
// fragment shaders of the scene when the renderer puts REFLECTION_RAYS before the sources (with the extensions: GL_EXT_ray_query for
// the rays, GL_EXT_buffer_reference2 and _uvec2 for the vertices read by their device addresses, GL_EXT_nonuniform_qualifier for the
// textures picked by the instance hit). what the screen space reflections don't find is traced through the scenery around the camera
// the renderer gathered for the frame (the walk of the scene in its reflection mode: the sections, the models and the vehicles within
// gfx.raytracing.range, as gfx.reflections.fidelity says), and the point hit is lit by the sky, the sun with a ray of its own towards
// it and its own emission. the space of the rays is the world around the camera: the axes of the world, the camera at the origin
layout(set = RT_SET, binding = RT_SCENE) uniform accelerationStructureEXT rt_scene;
// what the renderer wrote for an instance (vk14::ray_tracing::record), picked by its custom index
struct rt_record
{
uvec2 vertices; // the device address of the first vertex of the chunk (gfx::basic_vertex, 12 floats)
uvec2 indices; // of its first index, zero: the vertices go one after another
uint texindex; // in rt_textures, 0: none (white)
uint flags; // 1: the texture is alpha tested with cutoff
float cutoff; // the alpha test, as opacity of the model block
float glow; // the light of its own, as emission of the model block
vec4 colour; // the diffuse colour of the material, linear, as material_diffuse() makes it
};
layout(std430, set = RT_SET, binding = RT_RECORDS) readonly buffer rt_record_ssbo
{
rt_record rt_records[];
};
layout(set = RT_SET, binding = RT_TEXTURES) uniform sampler2D rt_textures[];
layout(buffer_reference, std430, buffer_reference_align = 4) readonly buffer rt_vertex_data
{
float rt_vertex[];
};
layout(buffer_reference, std430, buffer_reference_align = 4) readonly buffer rt_index_data
{
uint rt_index[];
};
const uint RT_FLAG_ALPHA = 1U;
const uint RT_VERTEX_FLOATS = 12U;
// the vertices of a triangle of the record
uvec3 rt_triangle(rt_record record, uint primitive)
{
if ((record.indices.x == 0U) && (record.indices.y == 0U))
return uvec3(primitive * 3U) + uvec3(0U, 1U, 2U);
rt_index_data indices = rt_index_data(record.indices);
return uvec3(indices.rt_index[primitive * 3U], indices.rt_index[primitive * 3U + 1U], indices.rt_index[primitive * 3U + 2U]);
}
// the texture coordinates of a point of a triangle, its barycentric coordinates Weights
vec2 rt_coords(rt_record record, uvec3 triangle, vec2 weights)
{
rt_vertex_data vertices = rt_vertex_data(record.vertices);
vec2 a = vec2(vertices.rt_vertex[triangle.x * RT_VERTEX_FLOATS + 6U], vertices.rt_vertex[triangle.x * RT_VERTEX_FLOATS + 7U]);
vec2 b = vec2(vertices.rt_vertex[triangle.y * RT_VERTEX_FLOATS + 6U], vertices.rt_vertex[triangle.y * RT_VERTEX_FLOATS + 7U]);
vec2 c = vec2(vertices.rt_vertex[triangle.z * RT_VERTEX_FLOATS + 6U], vertices.rt_vertex[triangle.z * RT_VERTEX_FLOATS + 7U]);
return a * (1.0 - weights.x - weights.y) + b * weights.x + c * weights.y;
}
vec3 rt_normal(rt_record record, uvec3 triangle, vec2 weights)
{
rt_vertex_data vertices = rt_vertex_data(record.vertices);
vec3 a = vec3(vertices.rt_vertex[triangle.x * RT_VERTEX_FLOATS + 3U], vertices.rt_vertex[triangle.x * RT_VERTEX_FLOATS + 4U], vertices.rt_vertex[triangle.x * RT_VERTEX_FLOATS + 5U]);
vec3 b = vec3(vertices.rt_vertex[triangle.y * RT_VERTEX_FLOATS + 3U], vertices.rt_vertex[triangle.y * RT_VERTEX_FLOATS + 4U], vertices.rt_vertex[triangle.y * RT_VERTEX_FLOATS + 5U]);
vec3 c = vec3(vertices.rt_vertex[triangle.z * RT_VERTEX_FLOATS + 3U], vertices.rt_vertex[triangle.z * RT_VERTEX_FLOATS + 4U], vertices.rt_vertex[triangle.z * RT_VERTEX_FLOATS + 5U]);
return a * (1.0 - weights.x - weights.y) + b * weights.x + c * weights.y;
}
// the reflection the scenery gives, of a surface at Position (view space) with the geometric normal Normal (view space, either side),
// for a ray along Direction (view space, normalized) and the perceptual roughness Roughness: the colour (rgb, times a) and whether
// the ray hit something (a)
vec4 rt_trace(vec3 position, vec3 normal, vec3 direction, float roughness)
{
mat3 toworld = mat3(inv_view);
vec3 point = toworld * position;
vec3 face = normalize(toworld * normal);
// the side of the surface the camera sees, the ray starting a little off it
if (dot(face, point) > 0.0)
face = -face;
vec3 ray = normalize(toworld * direction);
vec3 origin = point + face * (0.02 + length(point) * 0.0005);
rayQueryEXT query;
rayQueryInitializeEXT(query, rt_scene, gl_RayFlagsNoneEXT, 0xFF, origin, 0.0, ray, ssr_params2.w);
while (rayQueryProceedEXT(query))
{
// the instances with alpha tested textures aren't opaque: the texture tells whether the triangle is hit there
if (rayQueryGetIntersectionTypeEXT(query, false) == gl_RayQueryCandidateIntersectionTriangleEXT)
{
rt_record record = rt_records[rayQueryGetIntersectionInstanceCustomIndexEXT(query, false)];
if ((record.flags & RT_FLAG_ALPHA) == 0U)
{
rayQueryConfirmIntersectionEXT(query);
continue;
}
uvec3 triangle = rt_triangle(record, uint(rayQueryGetIntersectionPrimitiveIndexEXT(query, false)));
vec2 coords = rt_coords(record, triangle, rayQueryGetIntersectionBarycentricsEXT(query, false));
float alpha = textureLod(rt_textures[nonuniformEXT(record.texindex)], coords, 0.0).a;
if (alpha >= record.cutoff)
rayQueryConfirmIntersectionEXT(query);
}
}
if (rayQueryGetIntersectionTypeEXT(query, true) != gl_RayQueryCommittedIntersectionTriangleEXT)
return vec4(0.0);
float distance = rayQueryGetIntersectionTEXT(query, true);
rt_record record = rt_records[rayQueryGetIntersectionInstanceCustomIndexEXT(query, true)];
uvec3 triangle = rt_triangle(record, uint(rayQueryGetIntersectionPrimitiveIndexEXT(query, true)));
vec2 weights = rayQueryGetIntersectionBarycentricsEXT(query, true);
mat4x3 objecttoworld = rayQueryGetIntersectionObjectToWorldEXT(query, true);
vec3 hitnormal = normalize(transpose(inverse(mat3(objecttoworld))) * rt_normal(record, triangle, weights));
if (dot(hitnormal, ray) > 0.0)
hitnormal = -hitnormal;
vec3 hit = origin + ray * distance;
// the colour of the surface: the farther and the rougher, the blurrier the level of the texture
vec3 albedo = record.colour.rgb;
if (record.texindex != 0U)
{
vec2 coords = rt_coords(record, triangle, weights);
float lod = clamp(log2(1.0 + distance * 0.05) * 2.0 + roughness * 6.0, 0.0, 12.0);
albedo *= textureLod(rt_textures[nonuniformEXT(record.texindex)], coords, lod).rgb;
}
// the light of the sky, the sun where nothing stands between the point and it, and the light of its own
vec3 light = max(ambient + sky_ambient_directional(hitnormal) * sky_ambient_factor, vec3(0.0)) * AMBIENT_SCALE;
if (lights_count > 0U)
{
vec3 sun = -normalize(toworld * lights[0].dir);
float facing = dot(hitnormal, sun);
if (facing > 0.0)
{
rayQueryEXT shadow;
rayQueryInitializeEXT(shadow, rt_scene, gl_RayFlagsTerminateOnFirstHitEXT | gl_RayFlagsOpaqueEXT, 0xFF, hit + hitnormal * (0.02 + distance * 0.0005), 0.0, sun, ssr_params2.w);
while (rayQueryProceedEXT(shadow))
{
}
if (rayQueryGetIntersectionTypeEXT(shadow, true) == gl_RayQueryCommittedIntersectionNoneEXT)
light += lights[0].color * (SUN_DIFFUSE_SCALE * lights[0].intensity * cloud_shadow(hit) * pow(facing, SUN_NDOTL_SHARPNESS));
}
}
vec3 color = albedo * (light + vec3(record.glow));
if (any(isnan(color)) || any(isinf(color)))
return vec4(0.0);
return vec4(max(color, vec3(0.0)), 1.0);
}

155
shaders/vk14/ssr.glsl Normal file
View File

@@ -0,0 +1,155 @@
// screen space reflections (gfx.ssr) of the colour pass of the main view, included by light_common.glsl. the reflection of a
// surface is traced as a ray through the depth of the opaque scene drawn before the colour (scene_depth: the reverse z of a
// projection with the far plane at infinity, a depth d is a surface n / d away along the axis of the view), and its colour is
// taken from the image of the last frame (ssr_history) at the place the point hit had in that frame. what the screen doesn't
// hold (off the image, behind something nearer, too far, too rough) is left to the caller: the alpha of the result says how much
// of the reflection the screen gave. scene_ubs.ssr_params is set only for the main view, zero elsewhere
layout(set = PASS_SET, binding = SSR_HISTORY_TEX) uniform sampler2D ssr_history;
// the steps of a ray that found something are refined this many times, halving the step
const int SSR_REFINE_STEPS = 5;
// a value 0 .. 1 different for the neighbouring pixels (interleaved gradient noise), and from frame to frame with the temporal
// anti-aliasing, which blends the steps of the rays into a smooth image
float ssr_noise()
{
vec2 pixel = gl_FragCoord.xy + 5.588238 * ssr_params.w;
return fract(52.9829189 * fract(dot(pixel, vec2(0.06711056, 0.00583715))));
}
// the distance along the axis of the view of the surface in the depth at a place of the image (0 .. 1), 0: nothing there (the sky)
float ssr_surface(vec2 coords, vec2 size, float near)
{
float depth = texelFetch(scene_depth, clamp(ivec2(coords * size), ivec2(0), ivec2(size) - 1), 0).r;
return (depth > 0.0 ? near / depth : 0.0);
}
// the reflection the screen holds, of a surface at Position (view space) with the geometric normal Normal (view space, either
// side), for a ray along Direction (view space, normalized) and the perceptual roughness Roughness: the colour (rgb) times how
// much of it the screen holds (a)
vec4 ssr_trace(vec3 position, vec3 normal, vec3 direction, float roughness)
{
float steps = ssr_params.x;
if ((steps < 1.0) || (roughness > ssr_params.z))
return vec4(0.0);
// the side of the surface the camera sees; a ray going into it (a normal map turned far from the face) finds nothing
if (dot(normal, position) > 0.0)
normal = -normal;
if (dot(normal, direction) <= 0.0)
return vec4(0.0);
vec2 size = vec2(textureSize(scene_depth, 0));
float near = projection[3][2];
float pixelscale = 2.0 / (abs(projection[1][1]) * size.y);
// the ray starts two pixels off the surface, so the depth of the surface itself doesn't stop it
vec4 start = projection * vec4(position, 1.0);
vec3 origin = position + normal * (start.w * pixelscale * 2.0);
vec4 c0 = projection * vec4(origin, 1.0);
// the distance along the axis of the view changes with the ray: it ends in front of the near plane
float dw = (projection * vec4(direction, 0.0)).w;
float raylength = ssr_params.y;
if (dw < 0.0)
raylength = min(raylength, (c0.w - near * 2.0) / -dw);
if (raylength <= 0.0)
return vec4(0.0);
vec3 end = origin + direction * raylength;
vec4 c1 = projection * vec4(end, 1.0);
// the points of the ray are interpolated across the image (u) with 1 / w, which is linear there
float k0 = 1.0 / c0.w;
float k1 = 1.0 / c1.w;
vec2 s0 = c0.xy * k0;
vec2 s1 = c1.xy * k1;
vec2 delta = s1 - s0;
// the part of the ray over the image
float umax = 1.0;
if (abs(delta.x) > 1e-6)
umax = min(umax, ((delta.x > 0.0 ? 1.0 : -1.0) - s0.x) / delta.x);
if (abs(delta.y) > 1e-6)
umax = min(umax, ((delta.y > 0.0 ? 1.0 : -1.0) - s0.y) / delta.y);
if (umax <= 0.0)
return vec4(0.0);
// not more steps than the pixels the ray crosses
float pixels = length(delta * umax * size * 0.5);
float count = clamp(floor(pixels), 1.0, steps);
float jitter = ssr_noise();
float uprev = 0.0;
float wprev = c0.w;
float uhit = -1.0;
for (float i = 0.0; i < count; i += 1.0)
{
float u = umax * min((i + jitter) / count, 1.0);
float w = 1.0 / mix(k0, k1, u);
vec2 coords = mix(s0, s1, u) * 0.5 + 0.5;
float surface = ssr_surface(coords, size, near);
float bias = w * pixelscale * 0.5;
// the things in the depth are taken to be this thick behind their faces, with the length of the step on top
float thickness = max(0.25, surface * 0.02) + abs(w - wprev);
if ((surface > 0.0) && (w - surface > bias) && (w - surface < thickness))
{
// refined between the last step in front of the surface and this one behind it
float lo = uprev;
float hi = u;
for (int r = 0; r < SSR_REFINE_STEPS; ++r)
{
float mid = 0.5 * (lo + hi);
float wmid = 1.0 / mix(k0, k1, mid);
float smid = ssr_surface(mix(s0, s1, mid) * 0.5 + 0.5, size, near);
if ((smid > 0.0) && (wmid - smid > 0.0))
hi = mid;
else
lo = mid;
}
// what the ray went behind has to be close to it there: otherwise the ray went behind something thin, and goes on
float whit = 1.0 / mix(k0, k1, hi);
float shit = ssr_surface(mix(s0, s1, hi) * 0.5 + 0.5, size, near);
if ((shit > 0.0) && (abs(whit - shit) < max(0.25, shit * 0.02) + whit * pixelscale * 2.0))
{
uhit = hi;
break;
}
}
uprev = u;
wprev = w;
}
if (uhit < 0.0)
return vec4(0.0);
// the point hit, view space
vec3 hit = mix(origin * k0, end * k1, uhit) / mix(k0, k1, uhit);
vec2 hitcoords = mix(s0, s1, uhit) * 0.5 + 0.5;
// where it was in the last frame: the cab moves with the camera, the world stands still
mat4 reprojection = ssr_reprojection;
if (ssr_params2.y > 0.0)
{
vec3 box = (ssr_cab_box * vec4(hit, 1.0)).xyz;
if (all(lessThanEqual(abs(box), vec3(1.0))))
reprojection = ssr_reprojection_cab;
}
vec4 previous = reprojection * vec4(hit, 1.0);
if (previous.w <= 0.0)
return vec4(0.0);
vec2 coords = (previous.xy / previous.w) * 0.5 + 0.5;
float share = 1.0;
// faded towards the edges of the image, where it was hit and where it was in the last frame
vec2 edges = min(min(hitcoords, 1.0 - hitcoords), min(coords, 1.0 - coords));
share *= smoothstep(0.0, 0.04, min(edges.x, edges.y));
// and towards the end of the ray
float travelled = length(hit - origin);
share *= 1.0 - smoothstep(0.75, 1.0, travelled / ssr_params.y);
// the rougher surfaces hand over to the environment map
share *= 1.0 - smoothstep(ssr_params.z * 0.7, ssr_params.z, roughness);
// a ray towards the camera meets the backs of what the image shows
share *= 1.0 - smoothstep(0.4, 0.9, direction.z);
if (share <= 0.0)
return vec4(0.0);
// the lobe of a rough surface covers more of the image the farther the point hit is: a blurrier level of the history
float alpha = roughness * roughness;
float hitpixel = max((projection * vec4(hit, 1.0)).w, near) * pixelscale;
float lod = clamp(log2(max(2.0 * travelled * alpha / hitpixel, 1.0)), 0.0, ssr_params2.x);
vec3 color = textureLod(ssr_history, coords, lod).rgb;
if (any(isnan(color)) || any(isinf(color)))
return vec4(0.0);
return vec4(max(color, vec3(0.0)) * share, share);
}