mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-29 23:17:30 +01:00
1218 lines
54 KiB
GLSL
1218 lines
54 KiB
GLSL
#version 460 core
|
|
#define STAGE_FRAGMENT 1
|
|
#include "common.glsl"
|
|
// Windshield rain and snow
|
|
// by @lcddisplay; wiper movement @MichauSto; water on the glass rewritten as a model of drops lying on it
|
|
//
|
|
// the material: the textures diffuse (the glass) and wipermask (a blade per channel, r g b a: 0 where it doesn't reach, elsewhere
|
|
// the place of the blade over its sweep, 0 at rest .. 1 at the far end, at which it goes over the place). raindropsatlas and
|
|
// raindrop_grid_size aren't used any more, they stay so the materials which name them still load. rain_scale (1 when not given)
|
|
// scales the drops and the flakes. water_slot is given by the renderer (1 + the glass of the simulation, 0 none), not by the
|
|
// material. the block of the scene: rain_params (x: how heavy the rain or snow is, z: 1 for snow, w: temperature of the air),
|
|
// wiper_pos, wiper_timer_out, wiper_timer_return (the blades of the vehicle of the camera) and rain_wind (the air against it).
|
|
//
|
|
// the glasses of the cab of the train are simulated (gfx::windscreen_water, water_*.comp, water.glsl): the raindrops land as often
|
|
// as the rain hits the glass, splashing; the drops lie held by surface tension until gravity and the drag of the air pull them
|
|
// harder, then slide in stops and goes, wetting their paths (the next ones follow them into rivulets) and leaving droplets behind;
|
|
// the drops which touch flow together; the blades take the water along, gather it into a bead and leave it at the ends of their
|
|
// sweep, where it runs down; the flakes stick and melt. here the drops of the simulation are drawn where the fragment is, and the
|
|
// wetted paths as a thin film. elsewhere (the windows of other vehicles, a glass the simulation has no room for) the water is made
|
|
// up from the time alone: drops landing, growing and sliding along tracks, the blades pushing a ridge and leaving the water at the
|
|
// edge of their sweep.
|
|
//
|
|
// the water is a field of soft kernels (metaballs): drops coming close flow together, and a drop is a few kernels, so the drops
|
|
// are of many shapes. a drop is a lens holding a sharp picture of the place around it turned over (the image of the last frame
|
|
// where the screen holds it, the sky and the ground elsewhere), its edge shows the light of the sky reflected inside it; the lamps
|
|
// and the headlights light the water up; the film the blades leave breaks up behind them and the lights seen through it are drawn
|
|
// into lines towards their pivot; the wet glass is a little milky
|
|
|
|
layout(location = 0) in vec3 f_normal;
|
|
layout(location = 2) in vec2 f_coord;
|
|
layout(location = 3) in vec4 f_pos;
|
|
|
|
layout(location = 0) out vec4 out_color;
|
|
|
|
#ifdef NO_MOTION_OUTPUT
|
|
// a pass without the velocity target: the writes go nowhere
|
|
vec4 out_motion;
|
|
#else
|
|
layout(location = 1) out vec4 out_motion;
|
|
#endif
|
|
|
|
#pragma texture(diffuse, 0, sRGB_A)
|
|
#pragma texture(raindropsatlas, 1, sRGB_A)
|
|
#pragma texture(wipermask, 2, sRGB_A)
|
|
#pragma param(color, 0, 0, 4, diffuse)
|
|
#pragma param(diffuse, 1, 0, 1, diffuse)
|
|
#pragma param(specular, 1, 1, 1, specular)
|
|
#pragma param(reflection, 1, 2, 1, zero)
|
|
#pragma param(glossiness, 1, 3, 1, glossiness)
|
|
#pragma param(raindrop_grid_size, 2, 0, 1, one)
|
|
#pragma param(rain_scale, 2, 1, 1, one)
|
|
|
|
#include "light_common.glsl"
|
|
#include "apply_fog.glsl"
|
|
#include "tonemapping.glsl"
|
|
#include "water.glsl"
|
|
|
|
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D diffuse;
|
|
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 1) uniform sampler2D raindropsatlas;
|
|
layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 2) uniform sampler2D wipermask;
|
|
|
|
const float RW_PI = 3.14159265;
|
|
// refractive index of water
|
|
const float RW_IOR = 1.333;
|
|
// the falling particles (particles_precipitation.comp, rendering/common/gpuparticles.cpp): the diameters of the flakes, metres,
|
|
// and how fast the rain and the snow fall, m/s
|
|
const vec2 RW_SNOW_DIAMETER = vec2(0.004, 0.012);
|
|
const float RW_RAIN_FALL = 7.0;
|
|
const float RW_SNOW_FALL = 1.1;
|
|
// a drop is a lens: it holds a sharp picture of the place around it, turned over through its middle, RW_LENS times as far out
|
|
// as the fragment is from the middle (and as much smaller), with this share of the bending of the view by the surface on top
|
|
const float RW_LENS = 2.0;
|
|
const float RW_LENS_BEND = 0.15;
|
|
// the drag of the air going along the glass: the speed at which it equals the weight of a drop, m/s; and the share of the air
|
|
// meeting the glass head on which turns up along it. so the water goes up a glass the sooner the more it leans back: on a
|
|
// vertical glass at about 250 km/h, leaning 20 degrees at about 130 km/h, 45 degrees at about 80 km/h
|
|
const float RW_DRAG_SPEED = 24.0;
|
|
const float RW_HEAD_ON = 0.35;
|
|
// the water is a field summed from soft kernels (metaballs) and its surface is where the field is RW_SURFACE: drops which come
|
|
// close flow together the way surface tension pulls them. a kernel alone reaches the surface at RW_EDGE of its radius
|
|
const float RW_SURFACE = 0.25;
|
|
const float RW_EDGE = 0.61;
|
|
|
|
// the glass at the fragment: the view space steps along it for a step of the coordinates of the glass (metres along the axes
|
|
// of the model, see main()), and the size of a pixel on it, metres
|
|
vec3 rw_tu;
|
|
vec3 rw_tv;
|
|
float rw_pixel;
|
|
|
|
// how the water on the glass behaves here, set by main()
|
|
float rw_scale; // size of the drops (param rain_scale)
|
|
float rw_since; // seconds since a blade last cleaned the place
|
|
float rw_period; // seconds between the small drops landing on a place of one
|
|
float rw_grow; // seconds the drops need after a wipe to grow big enough to slide
|
|
float rw_rate; // how fast the glass gathers the rain against a standing vertical glass in the heaviest rain
|
|
float rw_push; // the force on a drop along the glass, in units of its weight
|
|
vec2 rw_down; // the way gravity pulls along the glass (coordinates of the glass, unit)
|
|
vec2 rw_flow; // the way the air goes along the glass (unit, 0 in still air)
|
|
|
|
// the field of the water: the sum of the kernels, its gradient along the glass (view space) and the sums for the mean radius of
|
|
// the drops it is made of
|
|
float rw_field = 0.0;
|
|
vec3 rw_fgrad = vec3(0.0);
|
|
float rw_fsize = 0.0;
|
|
float rw_fweight = 0.0;
|
|
|
|
// the water at the fragment: the thickness of the highest water there (metres), its gradient (view space), the radius of the
|
|
// curvature of its surface (metres: how much it bends the view across a pixel) and how much of the pixel it covers
|
|
float rw_height = 0.0;
|
|
vec3 rw_slope = vec3(0.0);
|
|
float rw_curve = 1.0;
|
|
float rw_cover = 0.0;
|
|
// from the middle of the drop which gives the water to the fragment (view space, metres), 0 for spread water
|
|
vec3 rw_offset = vec3(0.0);
|
|
// the last time the water a blade pushes went over the fragment, taking the drops lying there with it
|
|
float rw_cleared = -1.0e9;
|
|
|
|
uvec4 rw_pcg4d(uvec4 v)
|
|
{
|
|
v = v * 1664525u + 1013904223u;
|
|
v.x += v.y * v.w;
|
|
v.y += v.z * v.x;
|
|
v.z += v.x * v.y;
|
|
v.w += v.y * v.z;
|
|
v ^= v >> 16u;
|
|
v.x += v.y * v.w;
|
|
v.y += v.z * v.x;
|
|
v.z += v.x * v.y;
|
|
v.w += v.y * v.z;
|
|
return v;
|
|
}
|
|
|
|
// four numbers 0 .. 1 of a cell of a layer
|
|
vec4 rw_random(ivec2 cell, uint layer, uint index)
|
|
{
|
|
return vec4(rw_pcg4d(uvec4(uvec2(cell), layer, index))) * (1.0 / 4294967296.0);
|
|
}
|
|
|
|
float rw_noise(float x, uint layer)
|
|
{
|
|
float i = floor(x);
|
|
float f = x - i;
|
|
float a = rw_random(ivec2(int(i), 0), layer, 7u).x;
|
|
float b = rw_random(ivec2(int(i) + 1, 0), layer, 7u).x;
|
|
return mix(a, b, f * f * (3.0 - 2.0 * f));
|
|
}
|
|
|
|
// value noise 0 .. 1 (x) with its gradient (yz)
|
|
vec3 rw_noise2(vec2 p, uint layer)
|
|
{
|
|
vec2 i = floor(p);
|
|
vec2 f = p - i;
|
|
vec2 u = f * f * (3.0 - 2.0 * f);
|
|
vec2 du = 6.0 * f * (1.0 - f);
|
|
ivec2 c = ivec2(i);
|
|
float a = rw_random(c, layer, 8u).x;
|
|
float b = rw_random(c + ivec2(1, 0), layer, 8u).x;
|
|
float d = rw_random(c + ivec2(0, 1), layer, 8u).x;
|
|
float e = rw_random(c + ivec2(1, 1), layer, 8u).x;
|
|
float k1 = b - a;
|
|
float k2 = d - a;
|
|
float k4 = a - b - d + e;
|
|
return vec3(a + k1 * u.x + k2 * u.y + k4 * u.x * u.y, du * vec2(k1 + k4 * u.y, k2 + k4 * u.x));
|
|
}
|
|
|
|
// P turned by A (radians). every set of drops is laid on its own grid, turned and shifted against the others, so the drops line
|
|
// up neither with each other nor along the glass
|
|
vec2 rw_turn(vec2 p, float a)
|
|
{
|
|
float c = cos(a);
|
|
float s = sin(a);
|
|
return vec2(c * p.x - s * p.y, s * p.x + c * p.y);
|
|
}
|
|
|
|
// a gradient given along the coordinates of the glass, view space
|
|
vec3 rw_along(vec2 gradient)
|
|
{
|
|
return rw_tu * (gradient.x / dot(rw_tu, rw_tu)) + rw_tv * (gradient.y / dot(rw_tv, rw_tv));
|
|
}
|
|
|
|
// the gradient along the glass (view space) of a value of the fragments, from how it changes from pixel to pixel; Dpdx, Dpdy:
|
|
// the steps of the position, Normal: of the glass
|
|
vec3 rw_gradient(float value, vec3 dpdx, vec3 dpdy, vec3 normal)
|
|
{
|
|
vec3 r1 = cross(dpdy, normal);
|
|
vec3 r2 = cross(normal, dpdx);
|
|
float det = dot(dpdx, r1);
|
|
float dx = dFdx(value);
|
|
float dy = dFdy(value);
|
|
return (abs(det) > 1e-14) ? (dx * r1 + dy * r2) / det : vec3(0.0);
|
|
}
|
|
|
|
// a kernel of the water, Offset (view space) from its centre to the fragment: alone it makes water of the radius Visible,
|
|
// Weight 1 .. 0 as it goes. water smaller than a pixel would only flicker, it is left to the haze of the wet glass
|
|
void rw_kernel(vec3 offset, float visible, float weight)
|
|
{
|
|
if ((weight <= 0.0) || (visible < 0.5 * rw_pixel))
|
|
return;
|
|
// a drop a pixel or two across is grain rather than a lens: it fades into the haze of the wet glass
|
|
weight *= smoothstep(0.5 * rw_pixel, 2.0 * rw_pixel, visible);
|
|
float support = visible / RW_EDGE;
|
|
float q = dot(offset, offset) / (support * support);
|
|
if (q >= 1.0)
|
|
return;
|
|
float k = 1.0 - q;
|
|
float k3 = weight * k * k * k;
|
|
rw_field += k3;
|
|
rw_fgrad += offset * (-6.0 * weight * k * k / (support * support));
|
|
rw_fsize += k3 * visible;
|
|
rw_fweight += k3;
|
|
}
|
|
|
|
// a kernel at the Offset in the coordinates of the glass
|
|
void rw_blob(vec2 offset, float visible, float weight)
|
|
{
|
|
rw_kernel(offset.x * rw_tu + offset.y * rw_tv, visible, weight);
|
|
}
|
|
|
|
// a drop lying on the glass: a few kernels, sagging down the glass and lopsided as Shape (random 0 .. 1) says
|
|
void rw_drop(vec2 offset, float visible, float weight, vec4 shape)
|
|
{
|
|
rw_blob(offset, visible, weight);
|
|
if (visible < 1.5 * rw_pixel)
|
|
return;
|
|
vec2 side = vec2(-rw_down.y, rw_down.x);
|
|
vec2 sag = (rw_down * (0.2 + 0.35 * shape.x) + side * ((shape.y - 0.5) * 0.7)) * visible;
|
|
rw_blob(offset - sag, visible * mix(0.6, 0.85, shape.z), weight);
|
|
if (shape.w > 0.4)
|
|
{
|
|
float angle = shape.w * 40.0;
|
|
rw_blob(offset - vec2(cos(angle), sin(angle)) * (0.6 * visible), visible * mix(0.4, 0.6, shape.x), weight);
|
|
}
|
|
}
|
|
|
|
// water spread over the glass (a film, a ridge): its thickness, the gradient of it (view space), the radius of the curvature of
|
|
// its surface and how much of the pixel it covers
|
|
void rw_sheet(float thickness, vec3 slope, float curve, float cover)
|
|
{
|
|
if (cover <= 0.0)
|
|
return;
|
|
rw_cover = max(rw_cover, cover);
|
|
if (thickness > rw_height)
|
|
{
|
|
rw_height = thickness;
|
|
rw_slope = slope;
|
|
rw_curve = curve;
|
|
rw_offset = vec3(0.0);
|
|
}
|
|
}
|
|
|
|
// the surface of the water of the field: a drop is a cap of a sphere, flat lens of the contact angle Contact (radians) the water
|
|
// makes with the glass. how far across a drop the fragment is follows from the field (inverting the kernel), the height and
|
|
// the slope of the cap from that: the slope rises slowly from the middle and steeply only close to the edge, where it reaches
|
|
// the contact angle; where drops flowed together the field is above that of a drop alone and the water is flat. the edge is
|
|
// antialiased by how fast the field changes across a pixel
|
|
void rw_settle(float contact)
|
|
{
|
|
if (rw_field <= 0.5 * RW_SURFACE)
|
|
return;
|
|
float gradient = length(rw_fgrad);
|
|
float cover = clamp((rw_field - RW_SURFACE) / max(gradient * rw_pixel, 1e-3) + 0.5, 0.0, 1.0);
|
|
if (cover <= 0.0)
|
|
return;
|
|
float mean = rw_fsize / max(rw_fweight, 1e-12);
|
|
// 0 in the middle of a drop, 1 at its edge
|
|
float across = min(sqrt(max(1.0 - pow(min(rw_field, 1.0), 1.0 / 3.0), 0.0)) / RW_EDGE, 1.0);
|
|
float s = sin(contact);
|
|
float rs = across * s;
|
|
float root = sqrt(max(1.0 - rs * rs, 1e-4));
|
|
float sphere = mean / s;
|
|
vec3 slope = (gradient > 1e-9) ? rw_fgrad * ((rs / root) / gradient) : vec3(0.0);
|
|
float height = max(sphere * (root - cos(contact)), 0.0);
|
|
bool top = height > rw_height;
|
|
rw_sheet(height, slope, sphere, cover);
|
|
// the field rises towards the middle of the drop
|
|
if (top && (gradient > 1e-9))
|
|
rw_offset = rw_fgrad * (-across * mean / gradient);
|
|
}
|
|
|
|
// the blades. a channel of the mask of the wipers is a blade: 0 where it doesn't reach, elsewhere the place of the blade over its
|
|
// sweep (0 at rest .. 1 at the far end) at which it goes over the place. wiper_pos: where the blade is (0 .. 1 on its way out,
|
|
// 1 + where it is on its way back), wiper_timer_out / _return: the last time it lay at its start / was at its far end
|
|
struct rw_wipe_s
|
|
{
|
|
float since; // seconds since a blade last went over the place (large where none reaches)
|
|
float ahead; // 0 .. 1, how close in front of a moving blade the place is (the water the blade pushes)
|
|
float front; // how far in front of the nearest moving blade the place is, units of the mask (1 where none)
|
|
float swept; // 0 .. 1, how much of its way the blade in front did, the water it gathered on the way
|
|
float working; // 0 .. 1, the blades went lately
|
|
};
|
|
|
|
rw_wipe_s rw_wipe(vec4 mask)
|
|
{
|
|
rw_wipe_s wipe = rw_wipe_s(1.0e4, 0.0, 1.0, 0.0, 0.0);
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
float m = mask[i];
|
|
if (m < 0.001)
|
|
continue;
|
|
float pos = wiper_pos[i];
|
|
bool back = pos > 1.0;
|
|
float blade = back ? pos - 1.0 : pos;
|
|
float t0 = wiper_timer_out[i];
|
|
float t1 = wiper_timer_return[i];
|
|
// when the blade went over the place last: on this stroke, the blade moving at an even pace from its end, or on the
|
|
// stroke before, which took as long as the time between the ends (if the blade lay at its end longer, two seconds)
|
|
float pass;
|
|
float front; // how far in front of the blade the place is (units of the mask), below 0 behind it
|
|
if (back)
|
|
{
|
|
pass = (m >= blade) ? t1 + (time - t1) * ((1.0 - m) / max(1.0 - blade, 1e-3)) : t0 + m * clamp(t1 - t0, 0.0, 2.0);
|
|
front = blade - m;
|
|
}
|
|
else
|
|
{
|
|
pass = (m <= blade) ? t0 + (time - t0) * (m / max(blade, 1e-3)) : t1 + (1.0 - m) * clamp(t0 - t1, 0.0, 2.0);
|
|
front = m - blade;
|
|
}
|
|
wipe.since = min(wipe.since, max(time - pass, 0.0));
|
|
float moving = smoothstep(0.0, 0.03, blade) * smoothstep(1.0, 0.97, blade);
|
|
float ahead = (front > 0.0) ? (1.0 - smoothstep(0.0, 0.035, front)) * moving : 0.0;
|
|
wipe.ahead = max(wipe.ahead, ahead);
|
|
if ((front > 0.0) && (moving > 0.5))
|
|
wipe.front = min(wipe.front, front);
|
|
wipe.swept = max(wipe.swept, ahead * (back ? 1.0 - blade : blade));
|
|
float working = 1.0 - smoothstep(4.0, 30.0, time - t1);
|
|
wipe.working = max(wipe.working, working);
|
|
}
|
|
return wipe;
|
|
}
|
|
|
|
// a place of the glass a blade reaches
|
|
bool rw_reached(vec2 coords)
|
|
{
|
|
return any(greaterThanEqual(textureLod(wipermask, clamp(coords, vec2(0.0), vec2(1.0)), 0.0), vec4(0.001)));
|
|
}
|
|
|
|
// the edge of the sweep of the blades, where they push the water out to: 0 where a blade reaches, else the share of the places
|
|
// around at Reach (metres along the glass) which a blade reaches. found from where the blades reach and not from the values of
|
|
// the mask, which may stay at the end of the sweep over a wide part of the glass. Maskstep: the coordinates of the mask for a
|
|
// step along the glass
|
|
float rw_rim(vec2 coords, float reach, mat2 maskstep)
|
|
{
|
|
if (rw_reached(coords))
|
|
return 0.0;
|
|
float inside = 0.0;
|
|
for (int k = 0; k < 6; ++k)
|
|
{
|
|
float angle = float(k) * (RW_PI / 3.0);
|
|
inside += rw_reached(coords + maskstep * (vec2(cos(angle), sin(angle)) * reach)) ? 1.0 : 0.0;
|
|
}
|
|
return inside / 6.0;
|
|
}
|
|
|
|
// whether the edge of the sweep of the blades is within Reach of the place
|
|
bool rw_border(vec2 coords, float reach, mat2 maskstep)
|
|
{
|
|
bool here = rw_reached(coords);
|
|
for (int k = 0; k < 6; ++k)
|
|
{
|
|
float angle = float(k) * (RW_PI / 3.0);
|
|
if (rw_reached(coords + maskstep * (vec2(cos(angle), sin(angle)) * reach)) != here)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// how far along its way a sliding drop is at the share T of the time it takes: it stops and goes, the more smoothly the harder
|
|
// it is pushed
|
|
float rw_stickslip(float t, float seed)
|
|
{
|
|
t = clamp(t, 0.0, 1.0);
|
|
float s = t * 3.0;
|
|
float i = floor(s);
|
|
float stuck = (i + smoothstep(0.25 + 0.4 * seed, 1.0, s - i)) / 3.0;
|
|
return mix(stuck, t, smoothstep(0.8, 2.0, rw_push));
|
|
}
|
|
|
|
// the track of the water down a column of the glass (Column: its index, Width: of a column, Y: metres up the way): where it
|
|
// is across, metres. the water keeps to the way wetted before, so every drop of a column goes the same way, wavy, with kinks
|
|
// where the glass held it
|
|
float rw_path(int column, float y, float width, uint layer)
|
|
{
|
|
float slow = rw_noise(y / (0.05 * rw_scale) + float(column) * 7.31, layer) - 0.5;
|
|
float k = y / (0.008 * rw_scale);
|
|
float i = floor(k);
|
|
float a = rw_random(ivec2(column, int(i)), layer + 500u, 3u).x;
|
|
float b = rw_random(ivec2(column, int(i) + 1), layer + 500u, 3u).x;
|
|
float kinks = mix(a, b, smoothstep(0.15, 0.85, k - i)) - 0.5;
|
|
return (float(column) + 0.5 + 0.4 * slow + 0.2 * kinks) * width;
|
|
}
|
|
|
|
// the drops which grew big enough to slide, and the streams they leave: Q the place across and up the way they go (Side, Down:
|
|
// those directions in the coordinates of the glass), Share of them going this way. a column of the glass carries the water of
|
|
// a track; a drop starts somewhere in a cell of the column now and then, slides down with stops, thinning, and leaves a stream
|
|
// which drains into beads and dries
|
|
void rw_sliders(vec2 q, vec2 side, vec2 down, uint layer, float share)
|
|
{
|
|
float width = 0.04 * rw_scale;
|
|
float height = 0.15 * rw_scale;
|
|
int column = int(floor(q.x / width));
|
|
int row = int(floor(q.y / height));
|
|
vec4 a = rw_random(ivec2(column, 7), layer, 0u);
|
|
if (a.x > share * mix(0.35, 0.9, clamp(rw_rate / 3.0, 0.0, 1.0)))
|
|
return;
|
|
float speed = clamp(0.035 + 0.06 * (rw_push - 0.4) + 0.3 * max(rw_push - 1.5, 0.0), 0.01, 0.8);
|
|
float period = clamp(25.0 / rw_rate, 3.0, 60.0);
|
|
float trail = 0.0;
|
|
for (int r = 0; r < 3; ++r)
|
|
{
|
|
ivec2 cell = ivec2(column, row + r);
|
|
vec4 b = rw_random(cell, layer, 1u);
|
|
float span = period * mix(0.7, 1.3, b.x);
|
|
float now = floor(time / span + b.y);
|
|
for (int k = 0; k < 2; ++k)
|
|
{
|
|
float cycle = now - float(k);
|
|
float start = (cycle - b.y) * span;
|
|
float t = time - start;
|
|
// the drop gathered from the drops landed since the last wipe
|
|
if ((t < 0.0) || (rw_since - t < rw_grow))
|
|
continue;
|
|
vec4 c = rw_random(cell, layer + 1000u, uint(int(cycle) + 1000000));
|
|
float top = (float(cell.y) + 0.8 - 0.6 * c.x) * height;
|
|
float travel = mix(0.06, 0.28, c.y) * rw_scale;
|
|
float v = speed * mix(0.7, 1.3, c.z);
|
|
float gather = 0.6;
|
|
float moved = travel * rw_stickslip((t - gather) * v / travel, c.w);
|
|
float y = top - moved;
|
|
float radius = mix(0.003, 0.0042, c.w) * rw_scale * (1.0 - 0.35 * moved / travel) * mix(0.6, 1.0, smoothstep(0.0, gather, t));
|
|
// at the end of its way what is left of it stays and flows out into the stream
|
|
float body = 1.0 - smoothstep(0.0, 1.5, t - gather - travel / v);
|
|
if ((body > 0.0) && (abs(q.y - y) < 3.5 * radius))
|
|
{
|
|
vec2 o = vec2(q.x - rw_path(column, y, width, layer), q.y - y);
|
|
rw_blob(side * o.x - down * o.y, radius, body);
|
|
// its rear up the stream: a corner, drawn out into a cusp the faster it goes (the front stays round)
|
|
float drawn = clamp(v * 5.0, 0.0, 1.5);
|
|
for (int k = 1; k <= 2; ++k)
|
|
{
|
|
float yt = y + radius * (0.45 + 0.5 * drawn) * float(k);
|
|
vec2 ot = vec2(q.x - rw_path(column, yt, width, layer), q.y - yt);
|
|
rw_blob(side * ot.x - down * ot.y, radius * ((k == 1) ? 0.7 : 0.42), body);
|
|
}
|
|
}
|
|
if ((moved > 0.0) && (q.y > y) && (q.y < top))
|
|
{
|
|
float age = t - gather - (top - q.y) / v;
|
|
if ((age >= 0.0) && (age < rw_since))
|
|
{
|
|
// fresh, the stream is whole; draining, it breaks into beads, which dry
|
|
float beads = rw_noise(q.y / (0.004 * rw_scale), layer + 9u);
|
|
float wet = mix(1.15, 0.2 + 1.0 * beads, smoothstep(0.0, 15.0, age)) * (1.0 - smoothstep(30.0, 60.0, age));
|
|
trail = max(trail, wet);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (trail > 0.0)
|
|
{
|
|
float across = q.x - rw_path(column, q.y, width, layer);
|
|
float half_width = mix(0.0005, 0.0011, a.y) * rw_scale * (0.6 + 0.8 * rw_noise(q.y / (0.006 * rw_scale), layer + 11u));
|
|
rw_blob(side * across, half_width, trail);
|
|
}
|
|
}
|
|
|
|
// a drop lying on the glass, as rw_drop() with the directions along the glass given in the view space (Down: the way gravity
|
|
// pulls along the glass, Side across it)
|
|
void rw_drop_view(vec3 offset, float visible, float weight, vec4 shape, vec3 down, vec3 side)
|
|
{
|
|
rw_kernel(offset, visible, weight);
|
|
if (visible < 1.5 * rw_pixel)
|
|
return;
|
|
vec3 sag = (down * (0.2 + 0.35 * shape.x) + side * ((shape.y - 0.5) * 0.7)) * visible;
|
|
rw_kernel(offset - sag, visible * mix(0.6, 0.85, shape.z), weight);
|
|
if (shape.w > 0.4)
|
|
{
|
|
float angle = shape.w * 40.0;
|
|
rw_kernel(offset - (cos(angle) * side + sin(angle) * down) * (0.6 * visible), visible * mix(0.4, 0.6, shape.x), weight);
|
|
}
|
|
}
|
|
|
|
// the water the simulation keeps on the glass (gfx::windscreen_water, water.glsl), where the material is a glass of the cab of the
|
|
// train: the drops listed by the cell of the grid the fragment is in. Slot: of the glass, Uv: the coordinates of the fragment,
|
|
// Puv: the view space step along the glass for a step of the uv; Down, Side: along the glass. a sliding drop is drawn out behind
|
|
// itself into a cusp, the faster the longer; one lying is lopsided and sags down the glass; a flake of snow is lace, grey and
|
|
// clear as it melts (Snowcover, Wet: as the flakes of rw_snow())
|
|
void rw_simulated(uint slot, vec2 uv, mat2x3 puv, vec3 down, vec3 side, inout float snowcover, inout float wet)
|
|
{
|
|
uint base = water_grid_base(slot);
|
|
uint cell = water_cell(uv);
|
|
uint count = min(water_cells[base + cell], uint(WATER_CELL_DROPS));
|
|
for (uint k = 0u; k < count; ++k)
|
|
{
|
|
uint place = water_cells[base + uint(WATER_GRID * WATER_GRID) + cell * uint(WATER_CELL_DROPS) + k];
|
|
water_drop_s drop = water_drops[slot * uint(WATER_DROPS) + place];
|
|
float volume = drop.place.z;
|
|
if (volume <= 0.0)
|
|
continue;
|
|
vec3 offset = puv * (uv - drop.place.xy);
|
|
vec4 shape = vec4(rw_pcg4d(uvec4(place, slot, 41u, 0u))) * (1.0 / 4294967296.0);
|
|
if (drop.motion.w < 1.0)
|
|
{
|
|
// a flake: lobed lace, shrinking, grey and clear as it melts
|
|
float m = drop.motion.w;
|
|
vec2 flat2 = vec2(dot(offset, side), dot(offset, down));
|
|
float radius = 0.5 * water_flake(volume);
|
|
float dist = length(flat2);
|
|
if (dist < radius + rw_pixel)
|
|
{
|
|
float angle = atan(flat2.y, flat2.x);
|
|
float lobes = 0.55 + 0.45 * (0.6 * rw_noise(angle * 1.9 + shape.z * 40.0, 13u) + 0.4 * abs(cos(3.0 * angle + shape.w * 6.2831853)));
|
|
float rim = radius * lobes * (1.0 - 0.45 * m);
|
|
float body = smoothstep(rim + rw_pixel, 0.55 * rim - rw_pixel, dist);
|
|
float lace = rw_noise2(flat2 / 0.0007 + shape.zw * 100.0, 14u).x;
|
|
float flake = body * mix(0.55, 1.0, lace) * (1.0 - 0.7 * m * m);
|
|
snowcover = 1.0 - (1.0 - snowcover) * (1.0 - flake);
|
|
wet = max(wet, m * body);
|
|
}
|
|
continue;
|
|
}
|
|
float radius = water_radius(volume);
|
|
vec3 velocity = puv * drop.motion.xy;
|
|
float speed = length(velocity);
|
|
if (speed > 0.002)
|
|
{
|
|
// its rear up the way it came: a corner, drawn out into a cusp the faster it goes (the front stays round)
|
|
vec3 way = velocity / speed;
|
|
rw_kernel(offset, radius, 1.0);
|
|
float drawn = clamp(speed * 5.0, 0.0, 1.5);
|
|
for (int t = 1; t <= 2; ++t)
|
|
rw_kernel(offset + way * (radius * (0.45 + 0.5 * drawn) * float(t)), radius * ((t == 1) ? 0.7 : 0.42), 1.0);
|
|
}
|
|
else
|
|
{
|
|
rw_drop_view(offset, radius, 1.0, shape, down, side);
|
|
}
|
|
}
|
|
}
|
|
|
|
// how fresh the wetted paths of the simulation are at a place of the glass (x) and how that changes along u and v (yz): the
|
|
// texels of their map filtered by a cubic b-spline over 4 x 4 of them, so the edges of a path are smooth curves and its slopes
|
|
// change smoothly (a bilinear filter would leave steps at every texel in the bending of the view)
|
|
vec3 rw_trail(uint slot, vec2 uv, float now)
|
|
{
|
|
// a path stays wet (and seen) longer than the drops keep to it
|
|
float lasting = 25.0;
|
|
vec2 p = uv * float(WATER_TRAIL) - 0.5;
|
|
vec2 i = floor(p);
|
|
vec2 f = p - i;
|
|
// the weights of the b-spline and their derivatives, for the texels i - 1 .. i + 2
|
|
vec2 g = 1.0 - f;
|
|
vec4 wx = vec4(g.x * g.x * g.x, 3.0 * f.x * f.x * f.x - 6.0 * f.x * f.x + 4.0, -3.0 * f.x * f.x * f.x + 3.0 * f.x * f.x + 3.0 * f.x + 1.0, f.x * f.x * f.x) / 6.0;
|
|
vec4 wy = vec4(g.y * g.y * g.y, 3.0 * f.y * f.y * f.y - 6.0 * f.y * f.y + 4.0, -3.0 * f.y * f.y * f.y + 3.0 * f.y * f.y + 3.0 * f.y + 1.0, f.y * f.y * f.y) / 6.0;
|
|
vec4 dx = vec4(-g.x * g.x, 3.0 * f.x * f.x - 4.0 * f.x, -3.0 * f.x * f.x + 2.0 * f.x + 1.0, f.x * f.x) * 0.5;
|
|
vec4 dy = vec4(-g.y * g.y, 3.0 * f.y * f.y - 4.0 * f.y, -3.0 * f.y * f.y + 2.0 * f.y + 1.0, f.y * f.y) * 0.5;
|
|
vec3 result = vec3(0.0);
|
|
ivec2 base = ivec2(i) - 1;
|
|
for (int y = 0; y < 4; ++y)
|
|
{
|
|
for (int x = 0; x < 4; ++x)
|
|
{
|
|
float value = water_wetted(slot, base + ivec2(x, y), now, lasting);
|
|
result += value * vec3(wx[x] * wy[y], dx[x] * wy[y], wx[x] * dy[y]);
|
|
}
|
|
}
|
|
result.yz *= float(WATER_TRAIL);
|
|
return result;
|
|
}
|
|
|
|
// the surroundings in a view space direction where the screen doesn't hold them: the environment map above the horizon (the sky
|
|
// and the clouds), the ground below it, blurred by Lod
|
|
vec3 rw_surroundings(vec3 direction, float lod)
|
|
{
|
|
vec3 world = mat3(inv_view) * direction;
|
|
vec3 sky = envmap_sample_rough(normalize(vec3(world.x, max(world.y, 0.0), world.z)), clamp(lod / 8.0, 0.0, 1.0));
|
|
// the horizon as blurred as the view is
|
|
float below = smoothstep(-0.03 - 0.05 * lod, 0.25 + 0.05 * lod, -world.y);
|
|
if ((below <= 0.0) || (false == ENVMAP_ENABLED))
|
|
return sky;
|
|
return mix(sky, reflection_ground(), below);
|
|
}
|
|
|
|
// what is seen through the glass in a view space direction: the image of the last frame (the screen space reflections keep it)
|
|
// where it holds the place, Distance away along the direction (0: far), blurred by Lod; Share: how much of it the image gave.
|
|
// Fade: towards the edges of the image the surroundings take over (else the edges are stretched)
|
|
vec3 rw_behind_edges(vec3 direction, float lod, float distance, out float share, bool fade)
|
|
{
|
|
share = 0.0;
|
|
vec3 around = rw_surroundings(direction, lod);
|
|
if (ssr_params.x < 1.0)
|
|
return around;
|
|
vec4 previous = ssr_reprojection * ((distance > 0.0) ? vec4(direction * distance, 1.0) : vec4(direction, 0.0));
|
|
if (previous.w <= 1e-5)
|
|
return around;
|
|
vec2 coords = (previous.xy / previous.w) * 0.5 + 0.5;
|
|
vec2 edges = min(coords, 1.0 - coords);
|
|
share = fade ? smoothstep(0.0, 0.04, min(edges.x, edges.y)) : step(-0.02, min(edges.x, edges.y));
|
|
coords = clamp(coords, vec2(0.0), vec2(1.0));
|
|
if (share <= 0.0)
|
|
return around;
|
|
// the view out through the water goes on outside, where the image shows the inside of the cab around the windows the
|
|
// surroundings stand for it (else the drops show the dark walls and roof of the cab)
|
|
ivec2 size = textureSize(scene_depth, 0);
|
|
float depth = texelFetch(scene_depth, clamp(ivec2(coords * vec2(size)), ivec2(0), size - 1), 0).r;
|
|
float near = projection[3][2];
|
|
if ((depth > 0.0) && (near / depth < -f_pos.z + 2.5))
|
|
{
|
|
share = 0.0;
|
|
return around;
|
|
}
|
|
vec3 color = textureLod(ssr_history, coords, clamp(lod, 0.0, ssr_params2.x)).rgb;
|
|
if (any(isnan(color)) || any(isinf(color)))
|
|
{
|
|
share = 0.0;
|
|
return around;
|
|
}
|
|
return mix(around, max(color, vec3(0.0)), share);
|
|
}
|
|
|
|
vec3 rw_behind(vec3 direction, float lod, float distance, out float share)
|
|
{
|
|
return rw_behind_edges(direction, lod, distance, share, true);
|
|
}
|
|
|
|
// a layer Over put over Under, the colours not premultiplied
|
|
vec4 rw_over(vec4 over, vec4 under)
|
|
{
|
|
float alpha = over.a + under.a * (1.0 - over.a);
|
|
if (alpha <= 1e-5)
|
|
return vec4(0.0);
|
|
return vec4((over.rgb * over.a + under.rgb * under.a * (1.0 - over.a)) / alpha, alpha);
|
|
}
|
|
|
|
void main()
|
|
{
|
|
vec4 tex_color = texture(diffuse, f_coord);
|
|
vec4 mask = texture(wipermask, f_coord);
|
|
|
|
// the coordinates of the glass: metres along the axes of the model, so the water stays where it lies on the glass whatever the
|
|
// vehicle and the camera do. the axis of the model nearest to the vertical is up, the glass is projected along the
|
|
// horizontal one it faces most (along the vertical one if it lies flat)
|
|
mat3 linear = mat3(modelview);
|
|
mat3 rot = mat3(normalize(linear[0]), normalize(linear[1]), normalize(linear[2]));
|
|
mat3 unrot = transpose(rot);
|
|
vec3 local = unrot * (f_pos.xyz - modelview[3].xyz);
|
|
vec3 viewnormal = normalize(f_normal);
|
|
// the water is on the side away from the camera, the outside of the cab
|
|
vec3 outward = (dot(viewnormal, f_pos.xyz) < 0.0) ? -viewnormal : viewnormal;
|
|
vec3 normal = unrot * outward;
|
|
vec3 up = unrot * (vec3(0.0, 1.0, 0.0) * mat3(inv_view));
|
|
vec3 aup = abs(up);
|
|
int iu = (aup.x > aup.y) ? ((aup.x > aup.z) ? 0 : 2) : ((aup.y > aup.z) ? 1 : 2);
|
|
int ia = (iu + 1) % 3;
|
|
int ib = (iu + 2) % 3;
|
|
vec3 axis_up = vec3(0.0);
|
|
axis_up[iu] = (up[iu] < 0.0) ? -1.0 : 1.0;
|
|
vec3 axis_a = vec3(0.0);
|
|
axis_a[ia] = 1.0;
|
|
vec3 axis_b = vec3(0.0);
|
|
axis_b[ib] = 1.0;
|
|
vec3 an = abs(normal);
|
|
vec3 axis_u, axis_v, axis_n;
|
|
if (an[iu] > max(an[ia], an[ib]))
|
|
{
|
|
axis_n = axis_up;
|
|
axis_u = axis_a;
|
|
axis_v = axis_b;
|
|
}
|
|
else if (an[ia] >= an[ib])
|
|
{
|
|
axis_n = axis_a;
|
|
axis_u = axis_b;
|
|
axis_v = axis_up;
|
|
}
|
|
else
|
|
{
|
|
axis_n = axis_b;
|
|
axis_u = axis_a;
|
|
axis_v = axis_up;
|
|
}
|
|
vec2 glass = vec2(dot(local, axis_u), dot(local, axis_v));
|
|
float facing = dot(normal, axis_n);
|
|
facing = ((facing < 0.0) ? -1.0 : 1.0) * max(abs(facing), 0.35);
|
|
// a step of the coordinates is a step along the glass
|
|
rw_tu = rot * (axis_u - axis_n * (dot(normal, axis_u) / facing));
|
|
rw_tv = rot * (axis_v - axis_n * (dot(normal, axis_v) / facing));
|
|
|
|
// the derivatives, before anything is discarded: the size of a pixel on the glass and in the view, and how the coordinates of
|
|
// the mask follow the ones of the glass
|
|
vec3 view = normalize(f_pos.xyz);
|
|
vec3 dpdx = dFdx(f_pos.xyz);
|
|
vec3 dpdy = dFdy(f_pos.xyz);
|
|
rw_pixel = max(max(length(dpdx), length(dpdy)), 1e-6);
|
|
float pixelangle = max(max(length(dFdx(view)), length(dFdy(view))), 1e-6);
|
|
vec2 gdx = dFdx(glass);
|
|
vec2 gdy = dFdy(glass);
|
|
mat2 steps = mat2(gdx, gdy);
|
|
float det = determinant(steps);
|
|
mat2 maskstep = (abs(det) > 1e-14) ? mat2(dFdx(f_coord), dFdy(f_coord)) * inverse(steps) : mat2(0.0);
|
|
|
|
// the water the simulation keeps, where the material is a glass of the cab of the train (the param water_slot, given by the
|
|
// renderer: 1 + the glass): the view space step along the glass for a step of the uv, and how fresh the wetted path the water
|
|
// went along is, with how that changes along the glass (before anything is discarded)
|
|
int slot = int(param[2].z + 0.5) - 1;
|
|
mat2 uvsteps = mat2(dFdx(f_coord), dFdy(f_coord));
|
|
float uvdet = determinant(uvsteps);
|
|
bool simulated = (slot >= 0) && (slot < WATER_SLOTS) && (abs(uvdet) > 1e-24);
|
|
simulated = simulated && (water_header[clamp(slot, 0, WATER_SLOTS - 1)].x > 0.5);
|
|
mat2x3 puv = (abs(uvdet) > 1e-24) ? mat2x3(dpdx, dpdy) * inverse(uvsteps) : mat2x3(0.0);
|
|
vec3 trailfield = simulated ? rw_trail(uint(slot), f_coord, water_header[clamp(slot, 0, WATER_SLOTS - 1)].y) : vec3(0.0);
|
|
float trail = trailfield.x;
|
|
// its gradient along the glass, view space: the uv gradient taken through the steps of the uv along the glass
|
|
vec3 trailslope = vec3(0.0);
|
|
{
|
|
mat2 metric = transpose(puv) * puv;
|
|
if (abs(determinant(metric)) > 1e-30)
|
|
trailslope = puv * (inverse(metric) * trailfield.yz);
|
|
}
|
|
|
|
if (tex_color.a < 0.01)
|
|
discard;
|
|
|
|
vec4 layer = vec4(0.0);
|
|
float heavy = clamp(rain_params.x, 0.0, 1.0);
|
|
bool snow = rain_params.z > 0.5;
|
|
if ((heavy > 0.0) || simulated)
|
|
{
|
|
rw_scale = max(param[2].y, 0.05);
|
|
|
|
// what meets the glass. the air against the vehicle (rain_wind) drags the drops along the glass with what of it goes
|
|
// along the glass and with the share of what meets it head on which turns up it: the more the glass leans back the more
|
|
// of the air goes along it, and the less of their weight pulls the drops down it
|
|
vec3 air = unrot * (rain_wind.xyz * mat3(inv_view));
|
|
float fall = snow ? RW_SNOW_FALL : RW_RAIN_FALL;
|
|
float hits = max(-dot(air - up * fall, normal), 0.0);
|
|
rw_rate = heavy * clamp(0.7 + hits / fall, 0.7, 6.0);
|
|
rw_period = clamp((snow ? 10.0 : 5.0) / rw_rate, 0.4, 300.0);
|
|
rw_grow = 2.0 * rw_period;
|
|
vec3 gravity = -up - normal * dot(-up, normal);
|
|
vec3 rise = up - normal * dot(up, normal);
|
|
rise = (length(rise) > 0.1) ? normalize(rise) : vec3(0.0);
|
|
vec3 flow = air - normal * dot(air, normal) + rise * (max(-dot(air, normal), 0.0) * RW_HEAD_ON);
|
|
vec3 push = gravity + flow * (length(flow) / (RW_DRAG_SPEED * RW_DRAG_SPEED));
|
|
vec2 force = vec2(dot(push, axis_u), dot(push, axis_v));
|
|
rw_push = length(force);
|
|
vec2 fallway = vec2(dot(gravity, axis_u), dot(gravity, axis_v));
|
|
rw_down = (length(fallway) > 0.1) ? normalize(fallway) : vec2(0.0, -1.0);
|
|
vec2 flowway = vec2(dot(flow, axis_u), dot(flow, axis_v));
|
|
rw_flow = (length(flowway) > 1.0) ? normalize(flowway) : vec2(0.0);
|
|
|
|
rw_wipe_s wipe = rw_wipe(mask);
|
|
rw_since = wipe.since;
|
|
// the blades went lately (any of them, also where none of them reaches)
|
|
float working = 0.0;
|
|
for (int i = 0; i < 4; ++i)
|
|
working = max(working, 1.0 - smoothstep(4.0, 30.0, time - wiper_timer_return[i]));
|
|
// how much of the water the place gathers it has
|
|
float gathered = 1.0 - exp(-rw_since / (6.0 * rw_period));
|
|
// the film a blade leaves is drawn into fine streaks along its arcs, each a thin cylindrical lens which draws a light seen
|
|
// through it into a line across the streaks, towards the pivot of the blade: the way across the streaks on the screen
|
|
vec2 streakway = vec2(0.0);
|
|
if ((working > 0.0) && (false == snow) && (abs(det) > 1e-14))
|
|
{
|
|
int blade = 0;
|
|
for (int i = 1; i < 4; ++i)
|
|
blade = (mask[i] > mask[blade]) ? i : blade;
|
|
if (mask[blade] >= 0.001)
|
|
{
|
|
// the mask grows along the arcs; a step of a centimetre, so the steps of its values don't matter
|
|
float mu = textureLod(wipermask, f_coord + maskstep * vec2(0.01, 0.0), 0.0)[blade];
|
|
float mv = textureLod(wipermask, f_coord + maskstep * vec2(0.0, 0.01), 0.0)[blade];
|
|
vec2 arc = vec2(mu, mv) - mask[blade];
|
|
vec2 onscreen = inverse(steps) * vec2(-arc.y, arc.x);
|
|
if ((length(arc) > 1e-4) && (length(onscreen) > 1e-9))
|
|
streakway = normalize(onscreen);
|
|
}
|
|
}
|
|
vec3 ripple = rw_noise2(glass / (0.006 * rw_scale), 5u);
|
|
|
|
// the water a blade pushes: a bead in front of it while it moves, as wide as the water it gathered on its way, with a steep
|
|
// lumpy front (its edge shows dark, the view can't get out there). the drops the bead reaches flow into it
|
|
vec3 lumps = rw_noise2(glass / (0.009 * rw_scale), 7u);
|
|
float gatheredwater = snow ? 0.0 : min(gathered * 1.5, 1.0) * (0.35 + 0.65 * min(wipe.swept * 2.5, 1.0));
|
|
float reach = 0.004 + 0.018 * gatheredwater + 0.008 * (ripple.x - 0.5);
|
|
float ridge = gatheredwater * smoothstep(0.0, 0.003, wipe.front) * (1.0 - smoothstep(reach - 0.004, reach, wipe.front));
|
|
// the smooth shape from how it changes from pixel to pixel, the lumps on it as they are
|
|
float pooledbase = 0.004 * ridge;
|
|
float pooled = pooledbase * (0.75 + 0.5 * lumps.x);
|
|
vec3 pooledslope = rw_gradient(pooledbase, dpdx, dpdy, outward) * (0.75 + 0.5 * lumps.x) + rw_along(lumps.yz * (0.5 * pooledbase / (0.009 * rw_scale)));
|
|
if ((pooled > 0.0) && (false == simulated))
|
|
{
|
|
rw_sheet(pooled, pooledslope, 0.03, smoothstep(0.0, 0.1, ridge));
|
|
if (ridge > 0.05)
|
|
rw_cleared = time;
|
|
}
|
|
|
|
float snowcover = 0.0;
|
|
float wet = 0.0;
|
|
if (simulated)
|
|
{
|
|
// the directions along the glass, view space
|
|
vec3 upview = vec3(0.0, 1.0, 0.0) * mat3(inv_view);
|
|
vec3 downview = -(upview - outward * dot(upview, outward));
|
|
downview = (length(downview) > 0.05) ? normalize(downview) : normalize(cross(outward, normalize(rw_tu)));
|
|
vec3 sideview = cross(outward, downview);
|
|
rw_simulated(uint(slot), f_coord, puv, downview, sideview, snowcover, wet);
|
|
// the paths the water went along lately: a rivulet, a band of water rounded across (a cylinder lens: it holds a picture of
|
|
// the place around it turned over across the band, its edges dark as the edges of drops), thicker the fresher. the
|
|
// smoothed freshness falls off from the middle of a path to its edges
|
|
if (trail > 0.08)
|
|
{
|
|
float p = clamp((trail - 0.08) / 0.5, 0.0, 1.0);
|
|
float thickness = 0.0005 * (1.0 - (1.0 - p) * (1.0 - p));
|
|
vec3 rise = trailslope * (0.0005 * 2.0 * (1.0 - p) / 0.5);
|
|
float steep = length(rise);
|
|
if (steep > 0.7)
|
|
rise *= 0.7 / steep;
|
|
bool top = thickness > rw_height;
|
|
rw_sheet(thickness, rise, 0.004, smoothstep(0.0, 0.15, p) * 0.95);
|
|
// from the middle of the band to the fragment
|
|
float across = length(trailslope) / 0.5;
|
|
if (top && (across > 1.0))
|
|
rw_offset = -trailslope * ((1.0 - p) / (across * length(trailslope)));
|
|
}
|
|
}
|
|
else if (false == snow)
|
|
{
|
|
// the drops which grew big enough slide off, with gravity and the air: a set of tracks for each of the directions of the
|
|
// push nearest to the one now, each laid along its own way, so none of them moves when the push turns
|
|
float sliding = smoothstep(0.25, 0.6, rw_push);
|
|
if (sliding > 0.0)
|
|
{
|
|
float bin = atan(force.y, force.x) / (RW_PI / 6.0);
|
|
float lower = floor(bin);
|
|
for (int k = 0; k < 2; ++k)
|
|
{
|
|
float share = ((k == 0) ? 1.0 - (bin - lower) : bin - lower) * sliding;
|
|
if (share < 0.02)
|
|
continue;
|
|
float angle = (lower + float(k)) * (RW_PI / 6.0);
|
|
vec2 down = vec2(cos(angle), sin(angle));
|
|
vec2 side = vec2(-down.y, down.x);
|
|
rw_sliders(vec2(dot(glass, side), -dot(glass, down)), side, down, uint(int(lower) + k + 64) * 2u + 16u, share);
|
|
}
|
|
}
|
|
|
|
// the drops as they land, each time somewhere else in its cell, as often as the rain hits the glass: the drops of the
|
|
// rain spread on the glass, thrown with a fine splash which runs back into them
|
|
// each drop anywhere in its cell, the cell as large as a drop with its splash reaches
|
|
float cell = 0.007 * rw_scale;
|
|
vec2 laid = rw_turn(glass, 0.61) + vec2(0.31, 0.17);
|
|
ivec2 home = ivec2(floor(laid / cell));
|
|
float density = mix(0.08, 0.3, heavy);
|
|
for (int y = -1; y <= 1; ++y)
|
|
{
|
|
for (int x = -1; x <= 1; ++x)
|
|
{
|
|
ivec2 c = home + ivec2(x, y);
|
|
vec4 a = rw_random(c, 1u, 0u);
|
|
if (a.x > density)
|
|
continue;
|
|
vec4 b = rw_random(c, 1u, 1u);
|
|
float span = rw_period * mix(0.7, 1.3, a.w);
|
|
float cycle = floor(time / span + b.x);
|
|
float age = time - (cycle - b.x) * span;
|
|
float life = span * mix(0.5, 0.9, b.y);
|
|
if ((age > life) || (age > rw_since) || (time - age < rw_cleared))
|
|
continue;
|
|
vec4 p = rw_random(c, 2u, uint(cycle));
|
|
vec2 offset = rw_turn(laid - (vec2(c) + p.xy) * cell, -0.61);
|
|
float visible = mix(0.0011, 0.0019, b.z) * rw_scale;
|
|
// a raindrop hits the glass far above the speed at which it splashes (Weber number in the hundreds and thousands):
|
|
// it spreads, throws a spray of droplets which stay on the glass around it, and draws back
|
|
float splash = exp(-age / 0.06);
|
|
float fade = 1.0 - smoothstep(life - min(0.4, 0.3 * life), life, age);
|
|
if (visible * 0.3 > 0.5 * rw_pixel)
|
|
{
|
|
int spray = 3 + int(p.z * 4.0);
|
|
for (int s = 0; s < spray; ++s)
|
|
{
|
|
vec4 e = rw_random(c, 3u, uint(cycle) * 8u + uint(s));
|
|
vec2 way = normalize(vec2(cos(e.x * 6.2831853), sin(e.x * 6.2831853)) + rw_flow * 0.9);
|
|
rw_blob(offset - way * (visible * (1.4 + 1.6 * e.y)), visible * mix(0.18, 0.35, e.z), fade * (0.55 + 0.45 * splash));
|
|
}
|
|
}
|
|
rw_drop(offset, visible * (1.0 + 0.3 * splash), fade, p.wzyx);
|
|
}
|
|
}
|
|
// the drops the landing ones gathered into, growing until they are big enough to slide (the air blows them off sooner)
|
|
cell = 0.011 * rw_scale;
|
|
laid = rw_turn(glass, 1.07) + vec2(0.53, 0.29);
|
|
home = ivec2(floor(laid / cell));
|
|
density = mix(0.55, 1.0, heavy);
|
|
for (int y = -1; y <= 1; ++y)
|
|
{
|
|
for (int x = -1; x <= 1; ++x)
|
|
{
|
|
ivec2 c = home + ivec2(x, y);
|
|
vec4 a = rw_random(c, 4u, 0u);
|
|
if (a.x > density)
|
|
continue;
|
|
vec4 b = rw_random(c, 4u, 1u);
|
|
float span = rw_period * mix(2.5, 4.0, a.w);
|
|
float cycle = floor(time / span + b.x);
|
|
float age = time - (cycle - b.x) * span;
|
|
float life = span * mix(0.75, 0.98, b.y) / (1.0 + 2.0 * max(rw_push - 1.0, 0.0));
|
|
if ((age > life) || (age > rw_since) || (time - age < rw_cleared))
|
|
continue;
|
|
vec4 p = rw_random(c, 5u, uint(cycle));
|
|
// the big drops are much alike: they grow as they gather the small ones (the radius in step with the time) up to the
|
|
// size at which they slide off (on a vertical glass a footprint of about 9 mm, larger on a glass leaning back, smaller
|
|
// in a strong air flow), and go
|
|
float slides = min(0.0045 / sqrt(max(rw_push, 0.3)), 0.005);
|
|
float visible = slides * mix(0.6, 1.0, b.z) * rw_scale * mix(0.35, 1.0, clamp(age / (0.6 * life), 0.0, 1.0));
|
|
rw_drop(rw_turn(laid - (vec2(c) + p.xy) * cell, -1.07), visible, 1.0 - smoothstep(life - min(0.5, 0.3 * life), life, age), p.zwxy);
|
|
}
|
|
}
|
|
|
|
// the water the blades push out of their sweep: a broken row of drops flowed together along its edge, just outside it
|
|
float leave = working * min(heavy * 1.5, 1.0);
|
|
if ((leave > 0.0) && rw_border(f_coord, 0.009 * rw_scale, maskstep))
|
|
{
|
|
cell = 0.0065 * rw_scale;
|
|
laid = rw_turn(glass, 0.29) + vec2(0.11, 0.43);
|
|
home = ivec2(floor(laid / cell));
|
|
for (int y = -1; y <= 1; ++y)
|
|
{
|
|
for (int x = -1; x <= 1; ++x)
|
|
{
|
|
ivec2 c = home + ivec2(x, y);
|
|
vec4 a = rw_random(c, 20u, 0u);
|
|
if (a.x > 0.85)
|
|
continue;
|
|
// from the fragment to the middle of the drop, along the glass
|
|
vec2 centre = rw_turn((vec2(c) + a.yz) * cell - laid, -0.29);
|
|
float rim = rw_rim(f_coord + maskstep * centre, 0.004 * rw_scale, maskstep) * leave;
|
|
if (rim <= 0.05)
|
|
continue;
|
|
rw_drop(-centre, mix(0.0016, 0.003, a.w) * rw_scale * mix(0.6, 1.0, rim), smoothstep(0.05, 0.35, rim), rw_random(c, 21u, 0u));
|
|
}
|
|
}
|
|
}
|
|
|
|
// the streams running down from the water the blades leave at the ends of their sweep: a stream in some of the columns
|
|
// along the way down, where up the glass there is such water, as far down as the water had time to run since the blade
|
|
// went over the place
|
|
if ((working > 0.0) && (length(fallway) > 0.3))
|
|
{
|
|
vec2 side = vec2(-rw_down.y, rw_down.x);
|
|
float width = 0.035 * rw_scale;
|
|
float across = dot(glass, side);
|
|
float along = -dot(glass, rw_down);
|
|
int column = int(floor(across / width));
|
|
vec4 a = rw_random(ivec2(column, 0), 30u, 0u);
|
|
if (a.x < 0.6 * heavy * working)
|
|
{
|
|
float lateral = across - rw_path(column, along, width, 31u);
|
|
float half_width = mix(0.0006, 0.0012, a.z) * rw_scale * (0.6 + 0.8 * rw_noise(along / (0.006 * rw_scale), 32u));
|
|
if (abs(lateral) < 2.0 * half_width + rw_pixel)
|
|
{
|
|
// up the glass, the first place where the reach of the blades begins or ends
|
|
const float reach[6] = float[](0.01, 0.025, 0.05, 0.1, 0.18, 0.3);
|
|
bool here = rw_reached(f_coord);
|
|
float source = -1.0;
|
|
for (int s = 0; s < 6; ++s)
|
|
{
|
|
if (rw_reached(f_coord + maskstep * (-rw_down * reach[s])) != here)
|
|
{
|
|
source = reach[s];
|
|
break;
|
|
}
|
|
}
|
|
if ((source > 0.0) && (source < max(rw_since - 0.1, 0.0) * 0.2))
|
|
{
|
|
float beads = rw_noise(along / (0.004 * rw_scale), 33u);
|
|
float flowing = working * (1.0 - 0.5 * smoothstep(0.15, 0.3, source));
|
|
rw_blob(side * lateral, half_width, flowing * mix(0.6, 1.2, beads));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// the flakes stick where they land and melt into drops: the glass is warmed by the cab, faster in the air above zero
|
|
float warm = rain_params.w;
|
|
float cell = 0.016 * rw_scale;
|
|
vec2 laid = rw_turn(glass, 0.83) + vec2(0.07, 0.61);
|
|
ivec2 home = ivec2(floor(laid / cell));
|
|
float density = mix(0.5, 0.9, heavy);
|
|
for (int y = -1; y <= 1; ++y)
|
|
{
|
|
for (int x = -1; x <= 1; ++x)
|
|
{
|
|
ivec2 c = home + ivec2(x, y);
|
|
vec4 a = rw_random(c, 11u, 0u);
|
|
if (a.x > density)
|
|
continue;
|
|
vec4 b = rw_random(c, 11u, 1u);
|
|
float span = rw_period * mix(0.7, 1.3, a.w);
|
|
float cycle = floor(time / span + b.x);
|
|
float age = time - (cycle - b.x) * span;
|
|
float life = span * mix(0.6, 0.95, b.y);
|
|
if ((age > life) || (age > rw_since))
|
|
continue;
|
|
vec4 p = rw_random(c, 12u, uint(cycle));
|
|
vec2 offset = rw_turn(laid - (vec2(c) + p.xy) * cell, -0.83);
|
|
float diameter = mix(RW_SNOW_DIAMETER.x, RW_SNOW_DIAMETER.y, b.z) * rw_scale;
|
|
float melt = ((warm > 0.5) ? mix(3.0, 10.0, b.w) / (1.0 + 0.25 * warm) : mix(15.0, 45.0, b.w));
|
|
if (age < melt)
|
|
{
|
|
float m = age / melt;
|
|
float dist = length(offset.x * rw_tu + offset.y * rw_tv);
|
|
float radius = 0.5 * diameter;
|
|
if (dist < radius + rw_pixel)
|
|
{
|
|
float angle = atan(offset.y, offset.x);
|
|
float lobes = 0.55 + 0.45 * (0.6 * rw_noise(angle * 1.9 + p.z * 40.0, 13u) + 0.4 * abs(cos(3.0 * angle + p.w * 6.2831853)));
|
|
float rim = radius * lobes * (1.0 - 0.45 * m);
|
|
float body = smoothstep(rim + rw_pixel, 0.55 * rim - rw_pixel, dist);
|
|
float lace = rw_noise2(glass / (0.0007 * rw_scale) + p.zw * 100.0, 14u).x;
|
|
float flake = body * mix(0.55, 1.0, lace) * (1.0 - 0.7 * m * m);
|
|
snowcover = 1.0 - (1.0 - snowcover) * (1.0 - flake);
|
|
wet = max(wet, m * body);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// the water of the flake
|
|
rw_drop(offset, 0.0018 * pow(diameter / 0.008, 0.667) * rw_scale, 1.0 - smoothstep(life - min(0.5, 0.3 * life), life, age), p.wzyx);
|
|
}
|
|
}
|
|
}
|
|
// the snow the blades push in front of them and leave at the ends of their sweep
|
|
float pile = max(wipe.ahead * min(gathered * 2.0, 1.0) * (0.4 + 0.6 * min(wipe.swept * 2.5, 1.0)), rw_rim(f_coord, 0.006 * rw_scale, maskstep) * working * heavy);
|
|
float packed = rw_noise2(glass / (0.0015 * rw_scale), 15u).x;
|
|
snowcover = max(snowcover, smoothstep(0.15, 0.5, pile * 1.2 + 0.4 * (ripple.x - 0.5)) * mix(0.7, 1.0, packed));
|
|
}
|
|
// untreated glass, weathered and a little dirty: flat drops (a rain repellent coating would give beads above 90 degrees)
|
|
rw_settle(radians(mix(30.0, 48.0, rw_noise2(glass / (0.005 * rw_scale), 16u).x)));
|
|
|
|
// the lamps (of the cab, of the scenery) and the headlights light the water up, from both sides of the glass (worked out
|
|
// here, where the fragments of a quad go together)
|
|
vec3 lamps = vec3(0.0);
|
|
vec3 lampgloss = vec3(0.0);
|
|
add_local_lights(outward, lamps, lampgloss);
|
|
add_local_lights(-outward, lamps, lampgloss);
|
|
|
|
// the film just behind a blade, breaking up
|
|
float film = snow ? 0.0 : heavy * exp(-rw_since / 0.8) * smoothstep(0.02, 0.08, rw_since);
|
|
if (film > 0.01)
|
|
{
|
|
vec3 fine = rw_noise2(glass / (0.0021 * rw_scale), 6u);
|
|
vec2 tilt = fine.yz * (0.004 * film);
|
|
rw_sheet(0.00005 * film, rw_along(tilt), 0.3, 0.8 * film);
|
|
}
|
|
|
|
// what the water shows: the view goes through the glass (a plate: only the way from the air into the water counts) and out
|
|
// through the surface of the water, bent as much as it is curved. it is the world turned upside down in a drop, and the
|
|
// edges of a drop, where the view can't get out (total internal reflection), show the inside of the cab
|
|
float background = 0.0;
|
|
bool image = ssr_params.x >= 1.0;
|
|
if (image)
|
|
{
|
|
ivec2 size = textureSize(scene_depth, 0);
|
|
float depth = texelFetch(scene_depth, clamp(ivec2(gl_FragCoord.xy), ivec2(0), size - 1), 0).r;
|
|
if (depth > 0.0)
|
|
background = (projection[3][2] / depth) / max(-view.z, 0.05);
|
|
}
|
|
vec3 inside = ambient * (AMBIENT_SCALE * 0.5);
|
|
vec4 water = vec4(0.0);
|
|
if (rw_cover > 0.0)
|
|
{
|
|
vec3 surface = normalize(outward - rw_slope);
|
|
vec3 inwater = refract(view, -outward, 1.0 / RW_IOR);
|
|
vec3 through = refract(inwater, -surface, RW_IOR);
|
|
// what the surface reflects back inside the water: the light which came in at the other side of the drop, from the sky,
|
|
// and the inside of the cab. where the view can't get out (the edge of a drop) that is all it shows: a bright edge
|
|
vec3 bounced = reflect(inwater, surface);
|
|
if (dot(bounced, outward) < 0.0)
|
|
bounced = reflect(bounced, outward);
|
|
vec3 inner = mix(inside, rw_surroundings(normalize(bounced), 5.0), 0.3);
|
|
vec3 color = inner;
|
|
if (dot(through, through) > 0.5)
|
|
{
|
|
float fresnel = 0.02 + 0.98 * pow(1.0 - abs(dot(through, surface)), 5.0);
|
|
// the view through the water spreads over this much of the world across a pixel (radians)
|
|
float spread = (RW_IOR - 1.0) * rw_pixel / max(rw_curve, 1e-4);
|
|
// the picture the drop holds as a lens, and a share of the bending by the surface
|
|
vec3 lens = normalize(f_pos.xyz - (1.0 + RW_LENS) * rw_offset) + (through - view) * RW_LENS_BEND;
|
|
float lod = mix(log2(1.0 + RW_LENS) - 0.5, log2(max(spread / pixelangle, 1.0)), RW_LENS_BEND);
|
|
// a drop a few pixels across would hold a picture of single pixels, a grain: blurred more the smaller it looks
|
|
lod += clamp(2.5 - log2(max(rw_curve / rw_pixel, 1.0)), 0.0, 1.5);
|
|
float share;
|
|
vec3 seen = rw_behind(normalize(lens), lod, background, share);
|
|
// the sun through the drop, where the image doesn't hold it: as bright as the drop gathers its light. in heavier rain
|
|
// the sun is behind the clouds, its disc is not there to be focused
|
|
float sunshare = (1.0 - heavy) * (1.0 - heavy);
|
|
if ((lights_count > 0U) && (share < 1.0) && (sunshare > 0.01))
|
|
{
|
|
vec3 sun = normalize(-lights[0].dir);
|
|
float width = max(spread, 0.03);
|
|
float off = acos(clamp(dot(through, sun), -1.0, 1.0));
|
|
if (off < 4.0 * width)
|
|
{
|
|
float lit = 1.0 - clamp(calc_shadow(), 0.0, 1.0);
|
|
vec3 sunlight = lights[0].color * (SUN_DIFFUSE_SCALE * lights[0].intensity * cloud_shadow(mat3(inv_view) * f_pos.xyz));
|
|
// the light of the sun over the spread of the view: a pixel shows pi * irradiance * the share of a steradian
|
|
seen += sunlight * (sunshare * lit * (1.0 - share) * exp(-0.5 * off * off / (width * width)) / (2.0 * width * width));
|
|
}
|
|
}
|
|
color = mix(seen, inner, fresnel);
|
|
}
|
|
color += lamps * 0.6;
|
|
// without the image of the last frame the surroundings stand for what the water bends the view to; nearly flat water
|
|
// would show them in place of the scene behind it, so it is left mostly clear
|
|
float opaque = image ? 1.0 : mix(0.3, 1.0, smoothstep(0.02, 0.15, 1.0 - dot(through, view)));
|
|
water = vec4(color, rw_cover * opaque);
|
|
}
|
|
// the wet glass where the rain gathers is a little hazy: the tiniest drops spread the light
|
|
if (image && (false == snow))
|
|
{
|
|
float mist = heavy * gathered * (simulated ? 0.18 : 0.3);
|
|
if (mist > 0.01)
|
|
{
|
|
float share;
|
|
vec3 hazy = rw_behind_edges(view, 3.5, background, share, false);
|
|
// the tiniest drops spread the light of the sky coming through the glass
|
|
hazy = mix(hazy, rw_surroundings(outward, 7.0), 0.1);
|
|
layer = vec4(hazy, mist * max(share, 0.5));
|
|
}
|
|
}
|
|
layer = rw_over(water, layer);
|
|
// the lights seen through the streaks of the film the blades leave, drawn into lines towards their pivot
|
|
if (image && (length(streakway) > 0.5))
|
|
{
|
|
vec4 previous = ssr_reprojection * ((background > 0.0) ? vec4(view * background, 1.0) : vec4(view, 0.0));
|
|
if (previous.w > 1e-5)
|
|
{
|
|
vec2 coords = (previous.xy / previous.w) * 0.5 + 0.5;
|
|
vec2 texel = 1.0 / vec2(textureSize(ssr_history, 0));
|
|
ivec2 size = textureSize(scene_depth, 0);
|
|
float near = projection[3][2];
|
|
// a light: much brighter than the place around it (the bright sky is not)
|
|
vec3 around = textureLod(ssr_history, clamp(coords, vec2(0.0), vec2(1.0)), 6.0).rgb;
|
|
float threshold = max(1.0, 3.0 * max(around.r, max(around.g, around.b)));
|
|
vec3 glare = vec3(0.0);
|
|
float total = 0.0;
|
|
for (int k = -8; k <= 8; ++k)
|
|
{
|
|
if (k == 0)
|
|
continue;
|
|
float w = 1.0 - abs(float(k)) / 9.0;
|
|
total += w;
|
|
vec2 tap = coords + streakway * texel * (float(k) * 5.0);
|
|
if (any(lessThan(tap, vec2(0.0))) || any(greaterThan(tap, vec2(1.0))))
|
|
continue;
|
|
// only what is outside: the lamps of the cab are not behind the glass
|
|
float depth = texelFetch(scene_depth, clamp(ivec2(tap * vec2(size)), ivec2(0), size - 1), 0).r;
|
|
if ((depth > 0.0) && (near / depth < -f_pos.z + 2.5))
|
|
continue;
|
|
vec3 c = textureLod(ssr_history, tap, 1.5).rgb;
|
|
if (any(isnan(c)) || any(isinf(c)))
|
|
continue;
|
|
glare += max(c - vec3(threshold), vec3(0.0)) * w;
|
|
}
|
|
glare *= 0.6 * heavy * working / total;
|
|
if (any(greaterThan(glare, vec3(1e-4))))
|
|
{
|
|
// the scene itself under the water carries the light added (it changes nothing seen)
|
|
vec3 scene = textureLod(ssr_history, clamp(coords, vec2(0.0), vec2(1.0)), 0.0).rgb;
|
|
layer = rw_over(layer, vec4(max(scene, vec3(0.0)), 0.2));
|
|
layer.rgb += glare / layer.a;
|
|
}
|
|
}
|
|
}
|
|
if (snowcover > 0.0)
|
|
{
|
|
// the flakes are lit through from the outside, by the sky and the sun, and from the inside of the cab; melting they turn
|
|
// grey and clear
|
|
vec3 outside = rw_surroundings(outward, 6.0);
|
|
if (lights_count > 0U)
|
|
{
|
|
vec3 sun = normalize(-lights[0].dir);
|
|
outside += lights[0].color * (SUN_DIFFUSE_SCALE * lights[0].intensity * max(dot(outward, sun), 0.0) * (1.0 - clamp(calc_shadow(), 0.0, 1.0)));
|
|
}
|
|
vec3 flake = mix(vec3(0.92), vec3(0.62, 0.66, 0.7), wet);
|
|
vec3 color = flake * (outside * (0.35 * (1.0 - 0.5 * wet)) + ambient * (AMBIENT_SCALE * 0.7));
|
|
layer = rw_over(vec4(color, snowcover * mix(0.97, 0.6, wet)), layer);
|
|
}
|
|
}
|
|
out_color = layer;
|
|
|
|
{ // Overlay windshield texture with alpha
|
|
vec3 fragcolor = ambient;
|
|
vec3 fragnormal = normalize(f_normal);
|
|
float reflectivity = param[1].z;
|
|
float specularity = (tex_color.r + tex_color.g + tex_color.b) * 0.5;
|
|
legacy_gloss();
|
|
|
|
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
|
|
vec4 color = vec4(fragcolor, tex_color.a * alpha_mult);
|
|
|
|
out_color.xyz = apply_fog(mix(out_color.xyz, color.xyz, color.a));
|
|
out_color.a = mix(out_color.a, 1., color.a);
|
|
}
|
|
if (false == POSTFX_ENABLED)
|
|
{
|
|
out_color = tonemap(out_color);
|
|
}
|
|
|
|
if (MOTIONBLUR_ENABLED)
|
|
{
|
|
out_motion = vec4(0.0);
|
|
}
|
|
}
|