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

Improve rain shader

This commit is contained in:
2026-10-26 15:51:56 +01:00
parent a6bdeeaa2b
commit 719c729929
43 changed files with 5620 additions and 355 deletions

View File

@@ -49,6 +49,16 @@
#define PARTICLE_OWNER_SSBO_BINDING 11
#define PARTICLE_OUTPUT_SSBO_BINDING 12
#define PARTICLE_COMMAND_SSBO_BINDING 13
#define WATER_SLOTS 4
#define WATER_DROPS 65536
#define WATER_GRID 128
#define WATER_CELL_DROPS 32
#define WATER_TRAIL 1024
#define WATER_MAP 256
#define WATER_GROUP_SIZE 64
#define WATER_SSBO_BINDING 16
#define WATER_UBO_BINDING 6
#define WATER_MASK_TEX 25
#define INSTANCE_COUNTER_HEADER 8
#define INSTANCE_JOB_SLEEPERS 1U
#define LIGHT_SSBO_BINDING 0
@@ -297,6 +307,8 @@ layout(std140, binding = SCENE_UBO_BINDING) uniform scene_ubo
vec4 ssr_params;
vec4 ssr_params2;
vec4 env_params;
// the air against the vehicle of the camera, see gl::scene_ubs
vec4 rain_wind;
};
#ifdef STAGE_VERTEX

File diff suppressed because it is too large Load Diff

113
shaders/ogl46/water.glsl Normal file
View File

@@ -0,0 +1,113 @@
// the water on the windscreens (gfx::windscreen_water, gl::WATER_* in gl/ubo.h): what the passes of the simulation (water_*.comp)
// keep and the glasses (mat_rain_windscreen.frag) read. a glass is a material of mat_rain_windscreen in the cab of the train, a
// slot of the storage; its drops lie in the coordinates of its texture (uv), as its mask of the wipers does, so they stay where
// they lie on the glass whatever the vehicle and the camera do. the map of the glass tells what a step of the uv is along the
// glass (metres, in the space of the model of the cab) and where the blades go over it; the drops are looked up through a grid
// of cells over the uv, each listing the drops which reach into it; the wetted paths are marked on a map of their own with the
// time the water went there
#ifndef WATER_GLSL
#define WATER_GLSL
struct water_drop_s
{
// xy: the place, uv; z: the water, mm3 (0: no drop there); w: seconds it lies on the glass
vec4 place;
// xy: the velocity, uv per second; z: random 0 .. 1, its shape; w: 0 .. 1 how far it melted from a flake of snow (1: water)
vec4 motion;
};
#if defined(STAGE_COMPUTE)
layout(std430, binding = WATER_SSBO_BINDING) buffer water_ssbo
#else
layout(std430, binding = WATER_SSBO_BINDING) readonly buffer water_ssbo
#endif
{
// every glass: x 1 when it's simulated, y seconds its simulation runs (the time of the wetted paths), zw unused
vec4 water_header[WATER_SLOTS];
// every glass: x the drops on it, yzw unused
uvec4 water_counts_of[WATER_SLOTS];
water_drop_s water_drops[WATER_SLOTS * WATER_DROPS];
// the map, two for a texel: xyz the step along the glass for a step of u (metres), w 1 on the glass, 0.5 next to it (the edges
// spread out, for the filtering), 0 elsewhere; xyz the same for v, w 1 when the cross product of the two points out of the cab,
// -1 when it points in
vec4 water_axes[WATER_SLOTS * WATER_MAP * WATER_MAP * 2];
// the mask of the wipers baked into the map: the place of a blade over its sweep at which it goes over the texel, 0 where it
// doesn't reach
vec4 water_blades[WATER_SLOTS * WATER_MAP * WATER_MAP];
// every drop: 0 the place is free, else bit 0 set, bits 1 .. 3 the blade carrying it (1 + its number, 0 none), bit 4 set when
// the blade carries it towards the far end of its sweep
uint water_state[WATER_SLOTS * WATER_DROPS];
// the drop it flows into (itself when none)
uint water_target[WATER_SLOTS * WATER_DROPS];
// what flowed into it: the water (1e-4 mm3) and the moment of the places of the drops about its own (1e-6 uv mm3)
int water_gather[WATER_SLOTS * WATER_DROPS * 4];
// x: the way it went since it left a droplet behind, metres; y: the way it goes on before it leaves the next one, metres
vec4 water_extra[WATER_SLOTS * WATER_DROPS];
// the grids of the glasses, WATER_GRID x WATER_GRID cells each: the counts of the drops of the cells, then the drops of each
// cell (WATER_CELL_DROPS for a cell, the ones over the count aren't there). then the wetted paths of the glasses,
// WATER_TRAIL x WATER_TRAIL each: in the upper 24 bits 1 + the time the water went over the texel last, 1/2 s (0: never),
// in the lower 8 how near the middle of the path the texel is, 0 .. 255 (filtered between the texels the edges of the paths
// come out smooth wherever they go across the texels; the time is coarse, so a drop going along leaves the nearness of its
// whole passage, not of the step which happened to come last)
uint water_cells[];
};
// the radius of the footprint of a drop holding Volume mm3 of water, metres: a cap of a sphere meeting the glass at 40 degrees
float water_radius(float volume)
{
return 0.001 * pow(1.675 * max(volume, 0.0), 1.0 / 3.0);
}
// the diameter of a flake of snow holding Volume mm3 of water, metres
float water_flake(float volume)
{
return 0.006 * pow(max(volume, 0.0), 1.0 / 3.0);
}
// where the grid of a glass begins in water_cells: the count of a cell at the base + the cell, its drops from the base +
// WATER_GRID * WATER_GRID + the cell * WATER_CELL_DROPS
uint water_grid_base(uint slot)
{
return slot * uint(WATER_GRID * WATER_GRID * (1 + WATER_CELL_DROPS));
}
uint water_trail_base(uint slot)
{
return uint(WATER_SLOTS * WATER_GRID * WATER_GRID * (1 + WATER_CELL_DROPS)) + slot * uint(WATER_TRAIL * WATER_TRAIL);
}
// the cell of the grid holding a place of the glass
uint water_cell(vec2 uv)
{
ivec2 cell = clamp(ivec2(floor(uv * float(WATER_GRID))), ivec2(0), ivec2(WATER_GRID - 1));
return uint(cell.y * WATER_GRID + cell.x);
}
// how fresh the wetted path is at a texel of the map of the paths, 1 just wetted in its middle .. 0 dry long ago or never; Now:
// seconds the simulation of the glass runs, Lasting: seconds in which the freshness falls to 1/e
float water_wetted(uint slot, ivec2 texel, float now, float lasting)
{
texel = clamp(texel, ivec2(0), ivec2(WATER_TRAIL - 1));
uint value = water_cells[water_trail_base(slot) + uint(texel.y * WATER_TRAIL + texel.x)];
uint stamp = value >> 8u;
if (stamp == 0u)
return 0.0;
float age = max(now - float(stamp - 1u) / 2.0, 0.0);
return exp(-age / lasting) * (float(value & 255u) / 255.0);
}
// the same filtered between the texels at a place of the glass
float water_wetted_at(uint slot, vec2 uv, float now, float lasting)
{
vec2 p = uv * float(WATER_TRAIL) - 0.5;
ivec2 i = ivec2(floor(p));
vec2 f = p - vec2(i);
float a = water_wetted(slot, i, now, lasting);
float b = water_wetted(slot, i + ivec2(1, 0), now, lasting);
float c = water_wetted(slot, i + ivec2(0, 1), now, lasting);
float d = water_wetted(slot, i + ivec2(1, 1), now, lasting);
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
}
#endif

View File

@@ -0,0 +1,24 @@
#version 460 core
#define STAGE_COMPUTE 1
// the water on the windscreens (water_sim.glsl): the mask of the wipers of a glass baked into its map, once, when the glass is
// set up. a thread per texel of the map; the glass is water_counts.z
#include "common.glsl"
#include "water_sim.glsl"
layout(local_size_x = WATER_GROUP_SIZE) in;
// the mask of the wipers of the material of the glass (wipermask)
layout(binding = WATER_MASK_TEX) uniform sampler2D water_mask;
void main()
{
uint slot = water_counts.z;
uint texel = gl_GlobalInvocationID.x;
if ((slot >= uint(WATER_SLOTS)) || (texel >= uint(WATER_MAP * WATER_MAP)))
return;
vec2 uv = (vec2(float(texel % uint(WATER_MAP)), float(texel / uint(WATER_MAP))) + 0.5) / float(WATER_MAP);
// as much of the mask as a texel of the map covers
float lod = log2(max(float(textureSize(water_mask, 0).x) / float(WATER_MAP), 1.0));
water_blades[slot * uint(WATER_MAP * WATER_MAP) + texel] = textureLod(water_mask, uv, lod);
}

View File

@@ -0,0 +1,18 @@
#version 460 core
#define STAGE_COMPUTE 1
// the water on the windscreens (water_sim.glsl): the counts of the cells of the grids emptied before the drops are put in them
// (water_grid.comp). a thread per cell, a row of groups per glass
#include "common.glsl"
#include "water_sim.glsl"
layout(local_size_x = WATER_GROUP_SIZE) in;
void main()
{
uint slot = gl_WorkGroupID.y;
uint cell = gl_GlobalInvocationID.x;
if ((water_slots[slot].z < 0.5) || (cell >= uint(WATER_GRID * WATER_GRID)))
return;
water_cells[water_grid_base(slot) + cell] = 0u;
}

View File

@@ -0,0 +1,40 @@
#version 460 core
#define STAGE_COMPUTE 1
// the water on the windscreens (water_sim.glsl): the drops flow into the ones water_merge.comp found for them, where those don't
// flow on themselves (they do in a later step): the water and the moment of the places of the drops gathered by the one they
// flow into (water_settle.comp moves it to the middle of the water), the drops gone. a thread per drop, a row of groups per glass
#include "common.glsl"
#include "water_sim.glsl"
layout(local_size_x = WATER_GROUP_SIZE) in;
void main()
{
uint slot = gl_WorkGroupID.y;
uint place = gl_GlobalInvocationID.x;
if ((water_slots[slot].z < 0.5) || (place >= uint(WATER_DROPS)))
return;
uint base = slot * uint(WATER_DROPS);
uint index = base + place;
uint target = water_target[index];
if ((target == place) || (target >= uint(WATER_DROPS)) || (water_target[base + target] != target))
return;
water_drop_s drop = water_drops[index];
water_drop_s into = water_drops[base + target];
if ((drop.place.z <= 0.0) || (into.place.z <= 0.0))
return;
// with what flowed into the drop itself in the step (the landings)
float landed = float(water_gather[index * 4u]) * 1e-4;
vec2 moment = vec2(float(water_gather[index * 4u + 1u]), float(water_gather[index * 4u + 2u])) * 1e-6;
float volume = drop.place.z + landed;
vec2 offset = drop.place.xy - into.place.xy;
atomicAdd(water_gather[(base + target) * 4u], int(volume * 1e4));
atomicAdd(water_gather[(base + target) * 4u + 1u], int((offset.x * volume + moment.x) * 1e6));
atomicAdd(water_gather[(base + target) * 4u + 2u], int((offset.y * volume + moment.y) * 1e6));
// the blade carrying the drop carries what it flows into
uint carried = water_state[index] & 0x1eu;
if ((carried != 0u) && ((water_state[base + target] & 0x0eu) == 0u))
atomicOr(water_state[base + target], carried);
water_remove(index);
}

View File

@@ -0,0 +1,52 @@
#version 460 core
#define STAGE_COMPUTE 1
// the water on the windscreens (water_sim.glsl): every drop put in the cells of the grid it reaches into, as far as the glasses
// draw it (a sliding one is drawn out behind itself) and as far as the drops it may flow together with are. a thread per drop,
// a row of groups per glass
#include "common.glsl"
#include "water_sim.glsl"
layout(local_size_x = WATER_GROUP_SIZE) in;
void main()
{
uint slot = gl_WorkGroupID.y;
uint place = gl_GlobalInvocationID.x;
if ((water_slots[slot].z < 0.5) || (place >= uint(WATER_DROPS)))
return;
water_drop_s drop = water_drops[slot * uint(WATER_DROPS) + place];
if (drop.place.z <= 0.0)
return;
water_frame_s frame = water_frame(slot, drop.place.xy);
float lu = length(frame.tu);
float lv = length(frame.tv);
if ((lu < 1e-6) || (lv < 1e-6))
return;
float reach;
if (drop.motion.w < 1.0)
{
reach = 0.6 * water_flake(drop.place.z);
}
else
{
bool sliding = length(frame.tu * drop.motion.x + frame.tv * drop.motion.y) > 0.002;
reach = water_radius(drop.place.z) * (sliding ? 3.6 : 2.2);
}
vec2 extent = vec2(reach / lu, reach / lv);
ivec2 lower = clamp(ivec2(floor((drop.place.xy - extent) * float(WATER_GRID))), ivec2(0), ivec2(WATER_GRID - 1));
ivec2 upper = clamp(ivec2(floor((drop.place.xy + extent) * float(WATER_GRID))), ivec2(0), ivec2(WATER_GRID - 1));
// a drop can't be larger than this many cells, it would only flood the lists
upper = min(upper, lower + ivec2(6));
uint base = water_grid_base(slot);
for (int y = lower.y; y <= upper.y; ++y)
{
for (int x = lower.x; x <= upper.x; ++x)
{
uint cell = uint(y * WATER_GRID + x);
uint count = atomicAdd(water_cells[base + cell], 1u);
if (count < uint(WATER_CELL_DROPS))
water_cells[base + uint(WATER_GRID * WATER_GRID) + cell * uint(WATER_CELL_DROPS) + count] = place;
}
}
}

View File

@@ -0,0 +1,63 @@
#version 460 core
#define STAGE_COMPUTE 1
// the water on the windscreens (water_sim.glsl): the drops which touch flow together, the way surface tension pulls them. every
// drop finds the largest of the drops it touches which are larger than itself (by the cell of the grid its middle is in, which
// lists every drop reaching into it); the water goes over to that one in water_gather.comp. flakes of snow don't flow, but the
// ones a blade pushes pile up. a thread per drop, a row of groups per glass
#include "common.glsl"
#include "water_sim.glsl"
layout(local_size_x = WATER_GROUP_SIZE) in;
void main()
{
uint slot = gl_WorkGroupID.y;
uint place = gl_GlobalInvocationID.x;
if ((water_slots[slot].z < 0.5) || (place >= uint(WATER_DROPS)))
return;
uint index = slot * uint(WATER_DROPS) + place;
water_drop_s drop = water_drops[index];
water_target[index] = place;
if (drop.place.z <= 0.0)
return;
bool flake = drop.motion.w < 1.0;
bool carried = ((water_state[index] >> 1u) & 7u) != 0u;
if (flake && (false == carried))
return;
water_frame_s frame = water_frame(slot, drop.place.xy);
float size = flake ? 0.4 * water_flake(drop.place.z) : water_radius(drop.place.z);
uint cell = water_cell(drop.place.xy);
uint base = water_grid_base(slot);
uint count = min(water_cells[base + cell], uint(WATER_CELL_DROPS));
uint best = place;
float most = drop.place.z;
for (uint k = 0u; k < count; ++k)
{
uint other = water_cells[base + uint(WATER_GRID * WATER_GRID) + cell * uint(WATER_CELL_DROPS) + k];
if (other == place)
continue;
uint otherindex = slot * uint(WATER_DROPS) + other;
water_drop_s neighbour = water_drops[otherindex];
float volume = neighbour.place.z;
if ((volume < most) || ((volume == most) && (other > best)) || (volume <= 0.0))
continue;
if ((volume == drop.place.z) && (other > place))
continue;
bool otherflake = neighbour.motion.w < 1.0;
if (otherflake != flake)
continue;
// the snow a blade pushes piles up into lumps a few centimetres across, not more
if (otherflake && ((((water_state[otherindex] >> 1u) & 7u) == 0u) || (volume + drop.place.z > 20.0)))
continue;
vec2 step = neighbour.place.xy - drop.place.xy;
float apart = length(frame.tu * step.x + frame.tv * step.y);
float reach = size + (otherflake ? 0.4 * water_flake(volume) : water_radius(volume));
if (apart < 0.92 * reach)
{
best = other;
most = volume;
}
}
water_target[index] = best;
}

View File

@@ -0,0 +1,254 @@
#version 460 core
#define STAGE_COMPUTE 1
// the water on the windscreens (water_sim.glsl): every drop through the step. a thread per drop, a row of groups per glass.
// a blade going over the drop takes it along in front of itself until it turns back at the end of its sweep or stops, or until
// the drop is pushed out of the sweep; a drop of its own slides when gravity and the air pull it harder than its line of
// contact holds it, wetting its path and leaving droplets behind; a flake melts into a drop
#include "common.glsl"
#include "water_sim.glsl"
layout(local_size_x = WATER_GROUP_SIZE) in;
// how fresh the wetted path is at a place of the glass
float water_fresh(uint slot, vec2 uv)
{
return water_wetted_at(slot, uv, water_step.y, 8.0);
}
// the path from one place to another marked wetted, Halfwidth (metres) to the sides: the texels around it get the time and how
// near they are to the middle of the path
void water_wet_path(uint slot, water_frame_s frame, vec2 from, vec2 to, float halfwidth)
{
uint stamp = (uint(water_step.y * 2.0) + 1u) << 8u;
uint base = water_trail_base(slot);
float texels = float(WATER_TRAIL);
// the half width in texels, a texel and a half more for the filtering
float reach = halfwidth / max(min(length(frame.tu), length(frame.tv)), 1e-6) * texels + 1.5;
int spread = clamp(int(ceil(reach)), 1, 3);
vec2 a = from * texels - 0.5;
vec2 b = to * texels - 0.5;
int steps = clamp(int(ceil(length(b - a) * 2.0)), 1, 24);
for (int s = 1; s <= steps; ++s)
{
vec2 point = mix(a, b, float(s) / float(steps));
ivec2 centre = ivec2(floor(point + 0.5));
for (int y = -spread; y <= spread; ++y)
{
for (int x = -spread; x <= spread; ++x)
{
ivec2 texel = centre + ivec2(x, y);
if (any(lessThan(texel, ivec2(0))) || any(greaterThanEqual(texel, ivec2(WATER_TRAIL))))
continue;
float near = 1.0 - length(vec2(texel) - point) / reach;
if (near <= 0.0)
continue;
atomicMax(water_cells[base + uint(texel.y * WATER_TRAIL + texel.x)], stamp | uint(near * 255.0));
}
}
}
}
void main()
{
uint slot = gl_WorkGroupID.y;
uint place = gl_GlobalInvocationID.x;
if ((water_slots[slot].z < 0.5) || (place >= uint(WATER_DROPS)))
return;
uint index = slot * uint(WATER_DROPS) + place;
water_drop_s drop = water_drops[index];
if (drop.place.z <= 0.0)
return;
float seconds = water_step.x;
uint state = water_state[index];
vec4 extra = water_extra[index];
vec2 uv = drop.place.xy;
water_frame_s frame = water_frame(slot, uv);
if (frame.valid < 0.25)
{
water_remove(index);
return;
}
uint seed = water_pcg4d(uvec4(water_counts.x, slot, place, 9u)).x;
// a flake melts into a drop: the glass is warmed by the cab, the faster the warmer the air
bool flake = drop.motion.w < 1.0;
if (flake)
{
float warm = water_up.w;
float melt = (warm > 0.5) ? mix(3.0, 10.0, drop.motion.z) / (1.0 + 0.25 * warm) : mix(15.0, 45.0, drop.motion.z);
drop.motion.w = min(drop.motion.w + seconds / melt, 1.0);
flake = drop.motion.w < 1.0;
}
// the blades: one going over the drop in the step takes it along
uint carried = (state >> 1u) & 7u;
float way = (((state >> 4u) & 1u) != 0u) ? 1.0 : -1.0;
vec4 mask = water_blades_at(slot, uv);
vec4 reached = water_blades_texel(slot, uv);
for (int k = 0; k < 4; ++k)
{
float before = water_blades_before[k];
float now = water_blades_now[k];
if ((before < 0.0) || (now < 0.0))
{
if (carried == uint(k + 1))
carried = 0u;
continue;
}
float moved = now - before;
if (carried == uint(k + 1))
{
// the blade turned back at the end of its sweep or stopped: the water stays where it brought it
if ((moved * way <= 0.0) || (now >= 0.995) || (now <= 0.005))
carried = 0u;
}
// a blade goes over a drop which was ahead of it (by a little more than nothing: the water it left where it turned back at
// the end of its sweep isn't taken back) and is behind it now
else if ((carried == 0u) && (moved != 0.0) && (reached[k] >= 0.001) && ((mask[k] - before) * sign(moved) > 0.003) && ((mask[k] - now) * sign(moved) <= 0.0))
{
carried = uint(k + 1);
way = (moved > 0.0) ? 1.0 : -1.0;
// the blade doesn't take all of it: now and then a droplet stays behind
vec4 r = water_random(seed, 5u, place, slot);
if ((false == flake) && (drop.place.z > 1.0) && (r.x < 0.35))
{
float left = mix(0.02, 0.08, r.y);
if (water_add(slot, seed ^ 0x9e3779b9u, uv, left, 1.0, r.z, false))
drop.place.z -= left;
}
}
}
vec3 velocity = frame.tu * drop.motion.x + frame.tv * drop.motion.y;
if (carried != 0u)
{
// in front of the blade by its own size and the lip of the bead: the place where the mask is as far ahead of the blade,
// found along the gradient of the mask (along the arc of the blade)
int k = int(carried) - 1;
float radius = flake ? 0.5 * water_flake(drop.place.z) : water_radius(drop.place.z);
float a = dot(frame.tu, frame.tu);
float b = dot(frame.tu, frame.tv);
float c = dot(frame.tv, frame.tv);
float det = max(a * c - b * b, 1e-20);
float h = 1.0 / float(WATER_MAP);
bool edge = false;
for (int step = 0; step < 4; ++step)
{
// at the edge of the sweep (the end of the blade) the water squeezes out and stays
if ((water_blades_texel(slot, uv + vec2(h, 0.0))[k] < 0.001) || (water_blades_texel(slot, uv - vec2(h, 0.0))[k] < 0.001) ||
(water_blades_texel(slot, uv + vec2(0.0, h))[k] < 0.001) || (water_blades_texel(slot, uv - vec2(0.0, h))[k] < 0.001))
{
edge = true;
break;
}
float m = water_blades_at(slot, uv)[k];
vec2 gradient = vec2(water_blades_at(slot, uv + vec2(h, 0.0))[k] - water_blades_at(slot, uv - vec2(h, 0.0))[k],
water_blades_at(slot, uv + vec2(0.0, h))[k] - water_blades_at(slot, uv - vec2(0.0, h))[k]) / (2.0 * h);
float g2 = dot(gradient, gradient);
if (g2 < 1e-6)
break;
// how fast the mask changes along the glass, per metre
float permetre = sqrt(max((c * gradient.x * gradient.x - 2.0 * b * gradient.x * gradient.y + a * gradient.y * gradient.y) / det, 0.0));
float target = water_blades_now[k] + way * (0.0015 + radius) * permetre;
vec2 delta = gradient * ((target - m) / g2);
float length_delta = length(delta);
if (length_delta > 0.02)
delta *= 0.02 / length_delta;
uv += delta;
if (length_delta < 1e-5)
break;
}
// pushed out of the sweep it stays at its edge
if (edge || (water_blades_texel(slot, uv)[k] < 0.001))
carried = 0u;
velocity = vec3(0.0);
}
else if (false == flake)
{
float volume = drop.place.z;
float radius = water_radius(volume);
vec3 n = frame.normal;
vec3 up = water_up.xyz;
// gravity along the glass
float weight = 1000.0 * WATER_GRAVITY * volume * 1e-9;
vec3 force = -(up - n * dot(up, n)) * weight;
// the drag of the air going along the glass, with a share of the air meeting it head on turned up along it
vec3 air = water_air.xyz;
vec3 rise = up - n * dot(up, n);
rise = (length(rise) > 0.1) ? normalize(rise) : vec3(0.0);
vec3 flow = (air - n * dot(air, n) + rise * (max(-dot(air, n), 0.0) * WATER_HEAD_ON)) * WATER_BOUNDARY;
float area = 2.0 * radius * (0.364 * radius) * 0.8;
force += flow * (0.6 * area * length(flow));
// the hold of the line of contact, uneven over the glass, much less on a path wetted lately
vec2 spread = uv * vec2(length(frame.tu), length(frame.tv));
float uneven = mix(0.55, 1.45, water_noise(spread / 0.003, 3u + slot));
float hold = WATER_TENSION * 2.0 * radius * WATER_HOLD * uneven * mix(1.0, 0.3, water_fresh(slot, uv)) * mix(0.85, 1.15, drop.motion.z);
float pull = length(force);
vec3 target = vec3(0.0);
if (pull > hold)
{
vec3 dir = force / pull;
// the way bent towards the side which holds the drop less: the wetter one (the water keeps to the wetted paths) and the
// one where the glass is cleaner, so it meanders
vec3 side = cross(n, dir);
float offset = max(radius, 0.0015);
vec2 leftuv = uv + water_to_uv(frame, side * offset);
vec2 rightuv = uv - water_to_uv(frame, side * offset);
float wetter = water_fresh(slot, leftuv) - water_fresh(slot, rightuv);
float cleaner = water_noise(rightuv * vec2(length(frame.tu), length(frame.tv)) / 0.003, 3u + slot) - water_noise(leftuv * vec2(length(frame.tu), length(frame.tv)) / 0.003, 3u + slot);
float wander = water_noise(spread / 0.02 + vec2(17.0 * drop.motion.z), 11u + slot) - 0.5;
dir = normalize(dir + side * (wetter * 0.6 + cleaner * 0.5 + wander * 0.35));
target = dir * min((pull - hold) / (WATER_VISCOUS * radius), 1.5);
}
velocity += (target - velocity) * (1.0 - exp(-seconds / ((pull > hold) ? 0.05 : 0.015)));
velocity -= n * dot(velocity, n);
vec3 moved = velocity * seconds;
float travel = length(moved);
if (travel > 1e-7)
{
vec2 next = uv + water_to_uv(frame, moved);
water_wet_path(slot, frame, uv, next, 0.4 * radius);
// a sliding drop leaves droplets behind as it goes, and thins; a large one breaks up the faster (a lot of water runs as
// a stream of beads rather than as one big drop)
extra.x += travel;
if ((extra.x >= extra.y) && (volume > 1.5))
{
vec4 r = water_random(seed, 6u, place, slot);
float left = clamp(volume * mix(0.01, 0.03, r.x) * (1.0 + volume / 40.0), 0.01, 0.25 * volume);
vec3 behind = -(velocity / max(length(velocity), 1e-6)) * ((radius + 1.2 * water_radius(left)) * 1.05);
if (water_add(slot, seed ^ 0x85ebca6bu, uv + water_to_uv(frame, behind), left, 1.0, r.y, false))
drop.place.z -= left;
extra.x = 0.0;
extra.y = mix(0.002, 0.005, r.z) / (1.0 + volume / 100.0);
}
uv = next;
}
// without the rain the water dries, slowly
if (water_step.z <= 0.0)
drop.place.z -= seconds * 0.004 * radius * 1000.0;
}
else
{
velocity = vec3(0.0);
}
if ((drop.place.z < WATER_LEAST) || any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0))))
{
water_remove(index);
return;
}
water_frame_s there = water_frame(slot, uv);
if (there.valid < 0.25)
{
// off the glass, it falls off
water_remove(index);
return;
}
drop.place.xy = uv;
drop.place.w += seconds;
drop.motion.xy = water_to_uv(there, velocity);
water_drops[index] = drop;
water_extra[index] = extra;
water_state[index] = 1u | (carried << 1u) | ((way > 0.0) ? 16u : 0u);
}

View File

@@ -0,0 +1,36 @@
#version 460 core
#define STAGE_COMPUTE 1
// the water on the windscreens (water_sim.glsl): the drops which gathered others take their water, move to the middle of it and
// slow down as much as the still water slows them; the header of the glass tells the glasses it's simulated. a thread per drop,
// a row of groups per glass
#include "common.glsl"
#include "water_sim.glsl"
layout(local_size_x = WATER_GROUP_SIZE) in;
void main()
{
uint slot = gl_WorkGroupID.y;
uint place = gl_GlobalInvocationID.x;
if ((water_slots[slot].z < 0.5) || (place >= uint(WATER_DROPS)))
return;
if (place == 0u)
water_header[slot] = vec4(1.0, water_step.y, 0.0, 0.0);
uint index = slot * uint(WATER_DROPS) + place;
int gathered = water_gather[index * 4u];
if (gathered == 0)
return;
vec2 moment = vec2(float(water_gather[index * 4u + 1u]), float(water_gather[index * 4u + 2u])) * 1e-6;
water_gather[index * 4u] = 0;
water_gather[index * 4u + 1u] = 0;
water_gather[index * 4u + 2u] = 0;
water_drop_s drop = water_drops[index];
if (drop.place.z <= 0.0)
return;
float volume = drop.place.z + float(gathered) * 1e-4;
drop.place.xy += moment / volume;
drop.motion.xy *= drop.place.z / volume;
drop.place.z = volume;
water_drops[index] = drop;
}

View File

@@ -0,0 +1,279 @@
// what the passes of the simulation of the water on the windscreens share (water_*.comp): the block of the step, the random
// numbers, the map of the glass, the forces on a drop
//
// the physics (the drops are caps of spheres flat on the glass, 40 degrees at their edge, untreated weathered glass):
// - a drop lies held by its line of contact: the hold is the surface tension times the width of the drop times the difference
// of the cosines of its angles at the front and the rear (WATER_HOLD), so it grows with the radius, while the weight grows
// with the cube of it. on a vertical glass a drop slides once its footprint is about 7 mm across (25 mm3), on a glass leaning
// back later. the hold is uneven over the glass (dirt, scratches: the drops stop and go) and much less where the water went
// lately (a wetted path: the next drops follow it and it becomes a rivulet)
// - the air going along the glass drags the drops (with the area they stand up into it, within the layer of slow air at the
// glass), and of the air meeting the glass head on a share turns up along it: at speed the water goes up the glass, the sooner
// the more it leans back
// - a sliding drop is held back by the viscosity in proportion to its speed and radius; it leaves droplets behind as it goes
// (it thins and stops in the end) and wets its path
// - drops which touch flow together into one (surface tension), the water and the momentum of the places kept
// - a raindrop hits the glass far above the speed at which it splashes: it spreads and throws droplets around, which stay
// - a blade going over a drop takes it along in front of itself; the water it gathers flows together into a bead along it, left
// at the ends of its sweep (where it runs down as streams) and pushed off where the glass ends
#ifndef WATER_SIM_GLSL
#define WATER_SIM_GLSL
#include "water.glsl"
// gl::water_ubs, every vector in the space of the model of the cab
layout(std140, binding = WATER_UBO_BINDING) uniform water_ubo
{
// x: seconds of the step, y: seconds the simulation runs, z: how heavy the rain or the snow is 0 .. 1, w: 1 for snow
vec4 water_step;
// xyz: up, w: the temperature of the air, degrees Celsius
vec4 water_up;
// xyz: the air against the cab, m/s; w: how fast the rain or the snow falls, m/s
vec4 water_air;
// the blades now and at the step before: 0 .. 1 across their sweep, below 0 where there's none
vec4 water_blades_now;
vec4 water_blades_before;
// x: the smallest diameter of a raindrop landing as a drop of its own, mm, y: 1 / the mean of the diameters above it, 1 / mm
vec4 water_rain;
// x: random seed of the step, y: the most landings tried on a glass, z: the glass a single glass pass works on
uvec4 water_counts;
// every glass: x the landings tried in the step, y 1 / the most metres squared a unit of its uv holds, z 1 when it's simulated,
// w the size of its drops (rain_scale of its material)
vec4 water_slots[WATER_SLOTS];
};
const float WATER_GRAVITY = 9.81;
// the surface tension of water, N/m, and the share of it a drop is held by for the width of its footprint
const float WATER_TENSION = 0.072;
const float WATER_HOLD = 0.5;
// the viscous drag of a sliding drop for its radius and speed, N s / m2
const float WATER_VISCOUS = 1.3;
// the share of the speed of the air the drops feel in the slow layer at the glass, and of the air meeting the glass head on
// which turns up along it
const float WATER_BOUNDARY = 0.3;
const float WATER_HEAD_ON = 0.35;
// the drops below this much water (mm3) dry at once; a raindrop or a droplet of a splash smaller than WATER_SEEN (0.5 mm across
// its footprint, hardly a pixel) landing where there's no drop is left to the mist of the glass
const float WATER_LEAST = 0.004;
const float WATER_SEEN = 0.075;
uvec4 water_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 random numbers 0 .. 1
vec4 water_random(uint a, uint b, uint c, uint d)
{
return vec4(water_pcg4d(uvec4(a, b, c, d))) * (1.0 / 4294967296.0);
}
// value noise 0 .. 1
float water_noise(vec2 p, uint layer)
{
vec2 i = floor(p);
vec2 f = p - i;
vec2 u = f * f * (3.0 - 2.0 * f);
uvec2 c = uvec2(ivec2(i));
float a = water_random(c.x, c.y, layer, 0u).x;
float b = water_random(c.x + 1u, c.y, layer, 0u).x;
float d = water_random(c.x, c.y + 1u, layer, 0u).x;
float e = water_random(c.x + 1u, c.y + 1u, layer, 0u).x;
return mix(mix(a, b, u.x), mix(d, e, u.x), u.y);
}
// the glass at a place of the uv: the steps along it for the steps of u and v (metres), the way out of the cab and whether the
// place is on the glass (1), next to it (0.5) or off it (0)
struct water_frame_s
{
vec3 tu;
vec3 tv;
vec3 normal;
float valid;
};
water_frame_s water_frame(uint slot, vec2 uv)
{
water_frame_s frame = water_frame_s(vec3(0.0), vec3(0.0), vec3(0.0, 0.0, 1.0), 0.0);
if (any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0))))
return frame;
uint base = slot * uint(WATER_MAP * WATER_MAP);
vec2 p = uv * float(WATER_MAP) - 0.5;
ivec2 i = ivec2(floor(p));
vec2 f = p - vec2(i);
float total = 0.0;
float side = 0.0;
for (int k = 0; k < 4; ++k)
{
ivec2 texel = clamp(i + ivec2(k & 1, k >> 1), ivec2(0), ivec2(WATER_MAP - 1));
uint index = base + uint(texel.y * WATER_MAP + texel.x);
vec4 a = water_axes[index * 2u];
vec4 b = water_axes[index * 2u + 1u];
if (a.w <= 0.0)
continue;
float w = (((k & 1) != 0) ? f.x : 1.0 - f.x) * (((k >> 1) != 0) ? f.y : 1.0 - f.y) + 1e-4;
frame.tu += a.xyz * w;
frame.tv += b.xyz * w;
side += b.w * w;
total += w;
}
if (total <= 0.0)
return frame;
frame.tu /= total;
frame.tv /= total;
vec3 n = cross(frame.tu, frame.tv);
float length_n = length(n);
if (length_n < 1e-12)
return frame;
frame.normal = n * (((side < 0.0) ? -1.0 : 1.0) / length_n);
// on the glass by the texel the place is in
ivec2 nearest = clamp(ivec2(floor(uv * float(WATER_MAP))), ivec2(0), ivec2(WATER_MAP - 1));
frame.valid = water_axes[(base + uint(nearest.y * WATER_MAP + nearest.x)) * 2u].w;
return frame;
}
// a step along the glass (metres, the space of the cab) as a step of the uv
vec2 water_to_uv(water_frame_s frame, vec3 step)
{
float a = dot(frame.tu, frame.tu);
float b = dot(frame.tu, frame.tv);
float c = dot(frame.tv, frame.tv);
float det = a * c - b * b;
if (abs(det) < 1e-20)
return vec2(0.0);
vec2 r = vec2(dot(frame.tu, step), dot(frame.tv, step));
return vec2(c * r.x - b * r.y, a * r.y - b * r.x) / det;
}
// the blades at a place of the uv, filtered between the texels of the map
vec4 water_blades_at(uint slot, vec2 uv)
{
uint base = slot * uint(WATER_MAP * WATER_MAP);
vec2 p = clamp(uv, vec2(0.0), vec2(1.0)) * float(WATER_MAP) - 0.5;
ivec2 i = ivec2(floor(p));
vec2 f = p - vec2(i);
vec4 result = vec4(0.0);
for (int k = 0; k < 4; ++k)
{
ivec2 texel = clamp(i + ivec2(k & 1, k >> 1), ivec2(0), ivec2(WATER_MAP - 1));
float w = (((k & 1) != 0) ? f.x : 1.0 - f.x) * (((k >> 1) != 0) ? f.y : 1.0 - f.y);
result += water_blades[base + uint(texel.y * WATER_MAP + texel.x)] * w;
}
return result;
}
// the blades at the texel of the map holding a place of the uv, unfiltered: where a blade reaches it's at least 0.001 (the
// filtered values fall off across a texel at the edge of the sweep)
vec4 water_blades_texel(uint slot, vec2 uv)
{
ivec2 texel = clamp(ivec2(floor(uv * float(WATER_MAP))), ivec2(0), ivec2(WATER_MAP - 1));
return water_blades[slot * uint(WATER_MAP * WATER_MAP) + uint(texel.y * WATER_MAP + texel.x)];
}
// a free place for a drop of the glass, taken; ~0u when none was found
uint water_claim(uint slot, uint seed)
{
for (uint k = 0u; k < 8u; ++k)
{
uint place = water_pcg4d(uvec4(seed, k, slot, 77u)).x % uint(WATER_DROPS);
if (atomicCompSwap(water_state[slot * uint(WATER_DROPS) + place], 0u, 1u) == 0u)
return place;
}
return ~0u;
}
// a new drop of the glass: Volume mm3 at a place, a flake of snow when Melted is below 1. false when the glass is full. the
// landing rain and snow get only nine tenths of the places, so there are always some for the droplets the moving drops leave
bool water_add(uint slot, uint seed, vec2 uv, float volume, float melted, float shape, bool landing)
{
if ((volume < WATER_LEAST) || any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0))))
return false;
if (landing && (water_counts_of[slot].x >= uint(WATER_DROPS) / 10u * 9u))
return false;
uint place = water_claim(slot, seed);
if (place == ~0u)
return false;
atomicAdd(water_counts_of[slot].x, 1u);
uint index = slot * uint(WATER_DROPS) + place;
water_drops[index].place = vec4(uv, volume, 0.0);
water_drops[index].motion = vec4(0.0, 0.0, shape, melted);
water_extra[index] = vec4(0.0, 0.004 * (0.6 + 0.8 * shape), 0.0, 0.0);
water_target[index] = place;
for (uint k = 0u; k < 4u; ++k)
water_gather[index * 4u + k] = 0;
return true;
}
// the slot of the drop on the glass freed, with what was flowing into it
void water_remove(uint index)
{
water_drops[index].place.z = 0.0;
water_state[index] = 0u;
atomicAdd(water_counts_of[index / uint(WATER_DROPS)].x, ~0u);
for (uint k = 0u; k < 4u; ++k)
water_gather[index * 4u + k] = 0;
}
// Volume mm3 of water flowing into a drop (index in the storage) from Offset (uv) away from its middle, taken by
// water_settle.comp
void water_pour(uint index, float volume, vec2 offset)
{
atomicAdd(water_gather[index * 4u], int(volume * 1e4));
atomicAdd(water_gather[index * 4u + 1u], int(offset.x * volume * 1e6));
atomicAdd(water_gather[index * 4u + 2u], int(offset.y * volume * 1e6));
}
// the drop lying at a place of the glass which water of Radius (metres) landing there touches, the largest of them, by the grid
// of the step before (~0u: none)
uint water_landing_on(uint slot, water_frame_s frame, vec2 uv, float radius)
{
uint cell = water_cell(uv);
uint base = water_grid_base(slot);
uint count = min(water_cells[base + cell], uint(WATER_CELL_DROPS));
uint best = ~0u;
float most = 0.0;
for (uint k = 0u; k < count; ++k)
{
uint other = water_cells[base + uint(WATER_GRID * WATER_GRID) + cell * uint(WATER_CELL_DROPS) + k];
water_drop_s drop = water_drops[slot * uint(WATER_DROPS) + other];
if ((drop.place.z <= most) || (drop.motion.w < 1.0))
continue;
vec2 step = drop.place.xy - uv;
if (length(frame.tu * step.x + frame.tv * step.y) < 0.9 * (radius + water_radius(drop.place.z)))
{
best = other;
most = drop.place.z;
}
}
return best;
}
// water landing on the glass: it flows into a drop it touches, else it's a new drop where it's large enough to be seen
void water_land(uint slot, uint seed, water_frame_s frame, vec2 uv, float volume, float shape)
{
if (any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0))))
return;
uint onto = water_landing_on(slot, frame, uv, water_radius(volume));
if (onto != ~0u)
{
uint index = slot * uint(WATER_DROPS) + onto;
water_pour(index, volume, uv - water_drops[index].place.xy);
}
else if (volume >= WATER_SEEN)
{
water_add(slot, seed, uv, volume, 1.0, shape, true);
}
}
#endif

View File

@@ -0,0 +1,65 @@
#version 460 core
#define STAGE_COMPUTE 1
// the water on the windscreens (water_sim.glsl): the rain or the snow landing on the glasses in the step. a thread per landing
// tried, a row of groups per glass: a place of the uv picked at random is taken as often as the glass there gets hit (the
// metres squared the place holds, how much it faces the rain coming at it), so the landings go evenly over the glass. a raindrop
// spreads and throws a splash of droplets around, along the way it came, which flow into the drops they land on (water_settle.comp
// takes the water); a flake sticks where it lands
#include "common.glsl"
#include "water_sim.glsl"
layout(local_size_x = WATER_GROUP_SIZE) in;
void main()
{
uint slot = gl_WorkGroupID.y;
uint landing = gl_GlobalInvocationID.x;
if ((water_slots[slot].z < 0.5) || (float(landing) >= water_slots[slot].x))
return;
vec4 r = water_random(water_counts.x, slot, landing, 0u);
vec2 uv = r.xy;
water_frame_s frame = water_frame(slot, uv);
if (frame.valid < 0.75)
return;
// the rain coming at the glass, against the cab
vec3 coming = water_air.xyz - water_up.xyz * water_air.w;
float speed = length(coming);
if (speed < 1e-3)
return;
float facing = max(-dot(coming, frame.normal), 0.0) / speed;
float density = length(cross(frame.tu, frame.tv));
if (r.z >= density * water_slots[slot].y * facing)
return;
float scale = max(water_slots[slot].w, 0.05);
vec4 s = water_random(water_counts.x, slot, landing, 1u);
uint seed = water_pcg4d(uvec4(water_counts.x, slot, landing, 2u)).x;
if (water_step.w > 0.5)
{
// a flake, a few mm of snow holding a little water
water_add(slot, seed, uv, mix(0.3, 2.5, s.x) * scale * scale * scale, 0.0, s.y, true);
return;
}
// the diameter of the raindrop, mm: above the least landing as a drop of its own they go as Marshall-Palmer (exponential)
float diameter = min(water_rain.x - log(max(r.w, 1e-6)) / max(water_rain.y, 0.1), 5.0) * scale;
float drop = 0.5236 * diameter * diameter * diameter;
// most of it stays where it hit, the rest is thrown around as droplets, forward along the glass
float kept = mix(0.55, 0.85, s.x);
water_land(slot, seed, frame, uv, drop * kept, s.y);
vec3 along = coming - frame.normal * dot(coming, frame.normal);
float glancing = length(along) / speed;
vec3 forward = (glancing > 1e-3) ? normalize(along) : normalize(frame.tu);
vec3 across = cross(frame.normal, forward);
float radius = water_radius(drop * kept);
int splash = 2 + int(s.z * 4.0);
for (int k = 0; k < splash; ++k)
{
vec4 e = water_random(water_counts.x, slot, landing, 3u + uint(k));
float angle = e.x * 6.2831853;
vec3 way = cos(angle) * forward + sin(angle) * across + forward * (1.5 * glancing);
way = (length(way) > 1e-3) ? normalize(way) : forward;
float distance = radius * (1.3 + 2.5 * e.y) * (1.0 + glancing);
float volume = drop * (1.0 - kept) / float(splash) * mix(0.4, 1.6, e.z);
water_land(slot, seed + 101u * uint(k + 1), frame, uv + water_to_uv(frame, way * distance), volume, e.w);
}
}

View File

@@ -0,0 +1,34 @@
#version 460 core
#define STAGE_COMPUTE 1
// the water on the windscreens (water_sim.glsl): the blades going over the wetted paths in the step wipe them. a thread per texel
// of the map of the paths, a row of groups per glass
#include "common.glsl"
#include "water_sim.glsl"
layout(local_size_x = WATER_GROUP_SIZE) in;
void main()
{
uint slot = gl_WorkGroupID.y;
uint texel = gl_GlobalInvocationID.x;
if ((water_slots[slot].z < 0.5) || (texel >= uint(WATER_TRAIL * WATER_TRAIL)))
return;
uint index = water_trail_base(slot) + texel;
if (water_cells[index] == 0u)
return;
vec2 uv = (vec2(float(texel % uint(WATER_TRAIL)), float(texel / uint(WATER_TRAIL))) + 0.5) / float(WATER_TRAIL);
vec4 mask = water_blades_at(slot, uv);
for (int k = 0; k < 4; ++k)
{
float before = water_blades_before[k];
float now = water_blades_now[k];
if ((mask[k] < 0.001) || (before < 0.0) || (now < 0.0) || (before == now))
continue;
if ((mask[k] > min(before, now)) && (mask[k] <= max(before, now)))
{
water_cells[index] = 0u;
return;
}
}
}

View File

@@ -60,7 +60,8 @@
#define HEADLIGHT_SHADOW_TEX 23
#define SCENE_DEPTH_TEX 24
#define SSR_HISTORY_TEX 25
#define PASS_BINDING_COUNT 26
#define WATER_SSBO 26
#define PASS_BINDING_COUNT 27
#define MATERIAL_TEXTURE_FIRST 0
#define CL_CLUSTER_UBO 0
#define CL_AABB_SSBO 1
@@ -121,6 +122,13 @@
#define SHADOW_FIT_GROUP_SIZE 16
#define LOCAL_SHADOW_NEAR 0.150000
#define PARTICLE_GROUP_SIZE 64
#define WATER_SLOTS 4
#define WATER_DROPS 65536
#define WATER_GRID 128
#define WATER_CELL_DROPS 32
#define WATER_TRAIL 1024
#define WATER_MAP 256
#define WATER_GROUP_SIZE 64
#endif
#include "settings.glsl"
@@ -271,6 +279,8 @@ layout(std140, set = PASS_SET, binding = SCENE_UBO) uniform scene_ubo
vec4 ssr_params;
vec4 ssr_params2;
vec4 env_params;
// the air against the vehicle of the camera, see gl::scene_ubs
vec4 rain_wind;
};
#ifndef LAYOUT_COMPUTE

File diff suppressed because it is too large Load Diff

114
shaders/vk14/water.glsl Normal file
View File

@@ -0,0 +1,114 @@
// the water on the windscreens (gfx::windscreen_water, gl::WATER_* in gl/ubo.h, the vulkan renderer: vk14::windscreen_water): what the passes of the simulation (water_*.comp)
// keep and the glasses (mat_rain_windscreen.frag) read. a glass is a material of mat_rain_windscreen in the cab of the train, a
// slot of the storage; its drops lie in the coordinates of its texture (uv), as its mask of the wipers does, so they stay where
// they lie on the glass whatever the vehicle and the camera do. the map of the glass tells what a step of the uv is along the
// glass (metres, in the space of the model of the cab) and where the blades go over it; the drops are looked up through a grid
// of cells over the uv, each listing the drops which reach into it; the wetted paths are marked on a map of their own with the
// time the water went there
#ifndef WATER_GLSL
#define WATER_GLSL
struct water_drop_s
{
// xy: the place, uv; z: the water, mm3 (0: no drop there); w: seconds it lies on the glass
vec4 place;
// xy: the velocity, uv per second; z: random 0 .. 1, its shape; w: 0 .. 1 how far it melted from a flake of snow (1: water)
vec4 motion;
};
#if defined(STAGE_COMPUTE)
// the compute kernels: set COMPUTE_SET pushed with the dispatch, the storage at 0, the block of the step at 1 (water_sim.glsl)
layout(std430, set = COMPUTE_SET, binding = 0) buffer water_ssbo
#else
layout(std430, set = PASS_SET, binding = WATER_SSBO) readonly buffer water_ssbo
#endif
{
// every glass: x 1 when it's simulated, y seconds its simulation runs (the time of the wetted paths), zw unused
vec4 water_header[WATER_SLOTS];
// every glass: x the drops on it, yzw unused
uvec4 water_counts_of[WATER_SLOTS];
water_drop_s water_drops[WATER_SLOTS * WATER_DROPS];
// the map, two for a texel: xyz the step along the glass for a step of u (metres), w 1 on the glass, 0.5 next to it (the edges
// spread out, for the filtering), 0 elsewhere; xyz the same for v, w 1 when the cross product of the two points out of the cab,
// -1 when it points in
vec4 water_axes[WATER_SLOTS * WATER_MAP * WATER_MAP * 2];
// the mask of the wipers baked into the map: the place of a blade over its sweep at which it goes over the texel, 0 where it
// doesn't reach
vec4 water_blades[WATER_SLOTS * WATER_MAP * WATER_MAP];
// every drop: 0 the place is free, else bit 0 set, bits 1 .. 3 the blade carrying it (1 + its number, 0 none), bit 4 set when
// the blade carries it towards the far end of its sweep
uint water_state[WATER_SLOTS * WATER_DROPS];
// the drop it flows into (itself when none)
uint water_target[WATER_SLOTS * WATER_DROPS];
// what flowed into it: the water (1e-4 mm3) and the moment of the places of the drops about its own (1e-6 uv mm3)
int water_gather[WATER_SLOTS * WATER_DROPS * 4];
// x: the way it went since it left a droplet behind, metres; y: the way it goes on before it leaves the next one, metres
vec4 water_extra[WATER_SLOTS * WATER_DROPS];
// the grids of the glasses, WATER_GRID x WATER_GRID cells each: the counts of the drops of the cells, then the drops of each
// cell (WATER_CELL_DROPS for a cell, the ones over the count aren't there). then the wetted paths of the glasses,
// WATER_TRAIL x WATER_TRAIL each: in the upper 24 bits 1 + the time the water went over the texel last, 1/2 s (0: never),
// in the lower 8 how near the middle of the path the texel is, 0 .. 255 (filtered between the texels the edges of the paths
// come out smooth wherever they go across the texels; the time is coarse, so a drop going along leaves the nearness of its
// whole passage, not of the step which happened to come last)
uint water_cells[];
};
// the radius of the footprint of a drop holding Volume mm3 of water, metres: a cap of a sphere meeting the glass at 40 degrees
float water_radius(float volume)
{
return 0.001 * pow(1.675 * max(volume, 0.0), 1.0 / 3.0);
}
// the diameter of a flake of snow holding Volume mm3 of water, metres
float water_flake(float volume)
{
return 0.006 * pow(max(volume, 0.0), 1.0 / 3.0);
}
// where the grid of a glass begins in water_cells: the count of a cell at the base + the cell, its drops from the base +
// WATER_GRID * WATER_GRID + the cell * WATER_CELL_DROPS
uint water_grid_base(uint slot)
{
return slot * uint(WATER_GRID * WATER_GRID * (1 + WATER_CELL_DROPS));
}
uint water_trail_base(uint slot)
{
return uint(WATER_SLOTS * WATER_GRID * WATER_GRID * (1 + WATER_CELL_DROPS)) + slot * uint(WATER_TRAIL * WATER_TRAIL);
}
// the cell of the grid holding a place of the glass
uint water_cell(vec2 uv)
{
ivec2 cell = clamp(ivec2(floor(uv * float(WATER_GRID))), ivec2(0), ivec2(WATER_GRID - 1));
return uint(cell.y * WATER_GRID + cell.x);
}
// how fresh the wetted path is at a texel of the map of the paths, 1 just wetted in its middle .. 0 dry long ago or never; Now:
// seconds the simulation of the glass runs, Lasting: seconds in which the freshness falls to 1/e
float water_wetted(uint slot, ivec2 texel, float now, float lasting)
{
texel = clamp(texel, ivec2(0), ivec2(WATER_TRAIL - 1));
uint value = water_cells[water_trail_base(slot) + uint(texel.y * WATER_TRAIL + texel.x)];
uint stamp = value >> 8u;
if (stamp == 0u)
return 0.0;
float age = max(now - float(stamp - 1u) / 2.0, 0.0);
return exp(-age / lasting) * (float(value & 255u) / 255.0);
}
// the same filtered between the texels at a place of the glass
float water_wetted_at(uint slot, vec2 uv, float now, float lasting)
{
vec2 p = uv * float(WATER_TRAIL) - 0.5;
ivec2 i = ivec2(floor(p));
vec2 f = p - vec2(i);
float a = water_wetted(slot, i, now, lasting);
float b = water_wetted(slot, i + ivec2(1, 0), now, lasting);
float c = water_wetted(slot, i + ivec2(0, 1), now, lasting);
float d = water_wetted(slot, i + ivec2(1, 1), now, lasting);
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
}
#endif

View File

@@ -0,0 +1,27 @@
#version 460 core
#define STAGE_COMPUTE 1
// the water on the windscreens (water_sim.glsl): the mask of the wipers of a glass baked into its map, once, when the glass is
// set up. a thread per texel of the map; the glass is water_counts.z
// none of the pass set: everything the kernel reads is pushed with its dispatch (set COMPUTE_SET, water_sim.glsl), so it runs
// before the passes of the frame set theirs up
#define LAYOUT_UI 1
#include "common.glsl"
#include "water_sim.glsl"
layout(local_size_x = WATER_GROUP_SIZE) in;
// the mask of the wipers of the material of the glass (wipermask)
layout(set = COMPUTE_SET, binding = 2) uniform sampler2D water_mask;
void main()
{
uint slot = water_counts.z;
uint texel = gl_GlobalInvocationID.x;
if ((slot >= uint(WATER_SLOTS)) || (texel >= uint(WATER_MAP * WATER_MAP)))
return;
vec2 uv = (vec2(float(texel % uint(WATER_MAP)), float(texel / uint(WATER_MAP))) + 0.5) / float(WATER_MAP);
// as much of the mask as a texel of the map covers
float lod = log2(max(float(textureSize(water_mask, 0).x) / float(WATER_MAP), 1.0));
water_blades[slot * uint(WATER_MAP * WATER_MAP) + texel] = textureLod(water_mask, uv, lod);
}

View File

@@ -0,0 +1,21 @@
#version 460 core
#define STAGE_COMPUTE 1
// the water on the windscreens (water_sim.glsl): the counts of the cells of the grids emptied before the drops are put in them
// (water_grid.comp). a thread per cell, a row of groups per glass
// none of the pass set: everything the kernel reads is pushed with its dispatch (set COMPUTE_SET, water_sim.glsl), so it runs
// before the passes of the frame set theirs up
#define LAYOUT_UI 1
#include "common.glsl"
#include "water_sim.glsl"
layout(local_size_x = WATER_GROUP_SIZE) in;
void main()
{
uint slot = gl_WorkGroupID.y;
uint cell = gl_GlobalInvocationID.x;
if ((water_slots[slot].z < 0.5) || (cell >= uint(WATER_GRID * WATER_GRID)))
return;
water_cells[water_grid_base(slot) + cell] = 0u;
}

View File

@@ -0,0 +1,43 @@
#version 460 core
#define STAGE_COMPUTE 1
// the water on the windscreens (water_sim.glsl): the drops flow into the ones water_merge.comp found for them, where those don't
// flow on themselves (they do in a later step): the water and the moment of the places of the drops gathered by the one they
// flow into (water_settle.comp moves it to the middle of the water), the drops gone. a thread per drop, a row of groups per glass
// none of the pass set: everything the kernel reads is pushed with its dispatch (set COMPUTE_SET, water_sim.glsl), so it runs
// before the passes of the frame set theirs up
#define LAYOUT_UI 1
#include "common.glsl"
#include "water_sim.glsl"
layout(local_size_x = WATER_GROUP_SIZE) in;
void main()
{
uint slot = gl_WorkGroupID.y;
uint place = gl_GlobalInvocationID.x;
if ((water_slots[slot].z < 0.5) || (place >= uint(WATER_DROPS)))
return;
uint base = slot * uint(WATER_DROPS);
uint index = base + place;
uint target = water_target[index];
if ((target == place) || (target >= uint(WATER_DROPS)) || (water_target[base + target] != target))
return;
water_drop_s drop = water_drops[index];
water_drop_s into = water_drops[base + target];
if ((drop.place.z <= 0.0) || (into.place.z <= 0.0))
return;
// with what flowed into the drop itself in the step (the landings)
float landed = float(water_gather[index * 4u]) * 1e-4;
vec2 moment = vec2(float(water_gather[index * 4u + 1u]), float(water_gather[index * 4u + 2u])) * 1e-6;
float volume = drop.place.z + landed;
vec2 offset = drop.place.xy - into.place.xy;
atomicAdd(water_gather[(base + target) * 4u], int(volume * 1e4));
atomicAdd(water_gather[(base + target) * 4u + 1u], int((offset.x * volume + moment.x) * 1e6));
atomicAdd(water_gather[(base + target) * 4u + 2u], int((offset.y * volume + moment.y) * 1e6));
// the blade carrying the drop carries what it flows into
uint carried = water_state[index] & 0x1eu;
if ((carried != 0u) && ((water_state[base + target] & 0x0eu) == 0u))
atomicOr(water_state[base + target], carried);
water_remove(index);
}

View File

@@ -0,0 +1,55 @@
#version 460 core
#define STAGE_COMPUTE 1
// the water on the windscreens (water_sim.glsl): every drop put in the cells of the grid it reaches into, as far as the glasses
// draw it (a sliding one is drawn out behind itself) and as far as the drops it may flow together with are. a thread per drop,
// a row of groups per glass
// none of the pass set: everything the kernel reads is pushed with its dispatch (set COMPUTE_SET, water_sim.glsl), so it runs
// before the passes of the frame set theirs up
#define LAYOUT_UI 1
#include "common.glsl"
#include "water_sim.glsl"
layout(local_size_x = WATER_GROUP_SIZE) in;
void main()
{
uint slot = gl_WorkGroupID.y;
uint place = gl_GlobalInvocationID.x;
if ((water_slots[slot].z < 0.5) || (place >= uint(WATER_DROPS)))
return;
water_drop_s drop = water_drops[slot * uint(WATER_DROPS) + place];
if (drop.place.z <= 0.0)
return;
water_frame_s frame = water_frame(slot, drop.place.xy);
float lu = length(frame.tu);
float lv = length(frame.tv);
if ((lu < 1e-6) || (lv < 1e-6))
return;
float reach;
if (drop.motion.w < 1.0)
{
reach = 0.6 * water_flake(drop.place.z);
}
else
{
bool sliding = length(frame.tu * drop.motion.x + frame.tv * drop.motion.y) > 0.002;
reach = water_radius(drop.place.z) * (sliding ? 3.6 : 2.2);
}
vec2 extent = vec2(reach / lu, reach / lv);
ivec2 lower = clamp(ivec2(floor((drop.place.xy - extent) * float(WATER_GRID))), ivec2(0), ivec2(WATER_GRID - 1));
ivec2 upper = clamp(ivec2(floor((drop.place.xy + extent) * float(WATER_GRID))), ivec2(0), ivec2(WATER_GRID - 1));
// a drop can't be larger than this many cells, it would only flood the lists
upper = min(upper, lower + ivec2(6));
uint base = water_grid_base(slot);
for (int y = lower.y; y <= upper.y; ++y)
{
for (int x = lower.x; x <= upper.x; ++x)
{
uint cell = uint(y * WATER_GRID + x);
uint count = atomicAdd(water_cells[base + cell], 1u);
if (count < uint(WATER_CELL_DROPS))
water_cells[base + uint(WATER_GRID * WATER_GRID) + cell * uint(WATER_CELL_DROPS) + count] = place;
}
}
}

View File

@@ -0,0 +1,66 @@
#version 460 core
#define STAGE_COMPUTE 1
// the water on the windscreens (water_sim.glsl): the drops which touch flow together, the way surface tension pulls them. every
// drop finds the largest of the drops it touches which are larger than itself (by the cell of the grid its middle is in, which
// lists every drop reaching into it); the water goes over to that one in water_gather.comp. flakes of snow don't flow, but the
// ones a blade pushes pile up. a thread per drop, a row of groups per glass
// none of the pass set: everything the kernel reads is pushed with its dispatch (set COMPUTE_SET, water_sim.glsl), so it runs
// before the passes of the frame set theirs up
#define LAYOUT_UI 1
#include "common.glsl"
#include "water_sim.glsl"
layout(local_size_x = WATER_GROUP_SIZE) in;
void main()
{
uint slot = gl_WorkGroupID.y;
uint place = gl_GlobalInvocationID.x;
if ((water_slots[slot].z < 0.5) || (place >= uint(WATER_DROPS)))
return;
uint index = slot * uint(WATER_DROPS) + place;
water_drop_s drop = water_drops[index];
water_target[index] = place;
if (drop.place.z <= 0.0)
return;
bool flake = drop.motion.w < 1.0;
bool carried = ((water_state[index] >> 1u) & 7u) != 0u;
if (flake && (false == carried))
return;
water_frame_s frame = water_frame(slot, drop.place.xy);
float size = flake ? 0.4 * water_flake(drop.place.z) : water_radius(drop.place.z);
uint cell = water_cell(drop.place.xy);
uint base = water_grid_base(slot);
uint count = min(water_cells[base + cell], uint(WATER_CELL_DROPS));
uint best = place;
float most = drop.place.z;
for (uint k = 0u; k < count; ++k)
{
uint other = water_cells[base + uint(WATER_GRID * WATER_GRID) + cell * uint(WATER_CELL_DROPS) + k];
if (other == place)
continue;
uint otherindex = slot * uint(WATER_DROPS) + other;
water_drop_s neighbour = water_drops[otherindex];
float volume = neighbour.place.z;
if ((volume < most) || ((volume == most) && (other > best)) || (volume <= 0.0))
continue;
if ((volume == drop.place.z) && (other > place))
continue;
bool otherflake = neighbour.motion.w < 1.0;
if (otherflake != flake)
continue;
// the snow a blade pushes piles up into lumps a few centimetres across, not more
if (otherflake && ((((water_state[otherindex] >> 1u) & 7u) == 0u) || (volume + drop.place.z > 20.0)))
continue;
vec2 step = neighbour.place.xy - drop.place.xy;
float apart = length(frame.tu * step.x + frame.tv * step.y);
float reach = size + (otherflake ? 0.4 * water_flake(volume) : water_radius(volume));
if (apart < 0.92 * reach)
{
best = other;
most = volume;
}
}
water_target[index] = best;
}

View File

@@ -0,0 +1,257 @@
#version 460 core
#define STAGE_COMPUTE 1
// the water on the windscreens (water_sim.glsl): every drop through the step. a thread per drop, a row of groups per glass.
// a blade going over the drop takes it along in front of itself until it turns back at the end of its sweep or stops, or until
// the drop is pushed out of the sweep; a drop of its own slides when gravity and the air pull it harder than its line of
// contact holds it, wetting its path and leaving droplets behind; a flake melts into a drop
// none of the pass set: everything the kernel reads is pushed with its dispatch (set COMPUTE_SET, water_sim.glsl), so it runs
// before the passes of the frame set theirs up
#define LAYOUT_UI 1
#include "common.glsl"
#include "water_sim.glsl"
layout(local_size_x = WATER_GROUP_SIZE) in;
// how fresh the wetted path is at a place of the glass
float water_fresh(uint slot, vec2 uv)
{
return water_wetted_at(slot, uv, water_step.y, 8.0);
}
// the path from one place to another marked wetted, Halfwidth (metres) to the sides: the texels around it get the time and how
// near they are to the middle of the path
void water_wet_path(uint slot, water_frame_s frame, vec2 from, vec2 to, float halfwidth)
{
uint stamp = (uint(water_step.y * 2.0) + 1u) << 8u;
uint base = water_trail_base(slot);
float texels = float(WATER_TRAIL);
// the half width in texels, a texel and a half more for the filtering
float reach = halfwidth / max(min(length(frame.tu), length(frame.tv)), 1e-6) * texels + 1.5;
int spread = clamp(int(ceil(reach)), 1, 3);
vec2 a = from * texels - 0.5;
vec2 b = to * texels - 0.5;
int steps = clamp(int(ceil(length(b - a) * 2.0)), 1, 24);
for (int s = 1; s <= steps; ++s)
{
vec2 point = mix(a, b, float(s) / float(steps));
ivec2 centre = ivec2(floor(point + 0.5));
for (int y = -spread; y <= spread; ++y)
{
for (int x = -spread; x <= spread; ++x)
{
ivec2 texel = centre + ivec2(x, y);
if (any(lessThan(texel, ivec2(0))) || any(greaterThanEqual(texel, ivec2(WATER_TRAIL))))
continue;
float near = 1.0 - length(vec2(texel) - point) / reach;
if (near <= 0.0)
continue;
atomicMax(water_cells[base + uint(texel.y * WATER_TRAIL + texel.x)], stamp | uint(near * 255.0));
}
}
}
}
void main()
{
uint slot = gl_WorkGroupID.y;
uint place = gl_GlobalInvocationID.x;
if ((water_slots[slot].z < 0.5) || (place >= uint(WATER_DROPS)))
return;
uint index = slot * uint(WATER_DROPS) + place;
water_drop_s drop = water_drops[index];
if (drop.place.z <= 0.0)
return;
float seconds = water_step.x;
uint state = water_state[index];
vec4 extra = water_extra[index];
vec2 uv = drop.place.xy;
water_frame_s frame = water_frame(slot, uv);
if (frame.valid < 0.25)
{
water_remove(index);
return;
}
uint seed = water_pcg4d(uvec4(water_counts.x, slot, place, 9u)).x;
// a flake melts into a drop: the glass is warmed by the cab, the faster the warmer the air
bool flake = drop.motion.w < 1.0;
if (flake)
{
float warm = water_up.w;
float melt = (warm > 0.5) ? mix(3.0, 10.0, drop.motion.z) / (1.0 + 0.25 * warm) : mix(15.0, 45.0, drop.motion.z);
drop.motion.w = min(drop.motion.w + seconds / melt, 1.0);
flake = drop.motion.w < 1.0;
}
// the blades: one going over the drop in the step takes it along
uint carried = (state >> 1u) & 7u;
float way = (((state >> 4u) & 1u) != 0u) ? 1.0 : -1.0;
vec4 mask = water_blades_at(slot, uv);
vec4 reached = water_blades_texel(slot, uv);
for (int k = 0; k < 4; ++k)
{
float before = water_blades_before[k];
float now = water_blades_now[k];
if ((before < 0.0) || (now < 0.0))
{
if (carried == uint(k + 1))
carried = 0u;
continue;
}
float moved = now - before;
if (carried == uint(k + 1))
{
// the blade turned back at the end of its sweep or stopped: the water stays where it brought it
if ((moved * way <= 0.0) || (now >= 0.995) || (now <= 0.005))
carried = 0u;
}
// a blade goes over a drop which was ahead of it (by a little more than nothing: the water it left where it turned back at
// the end of its sweep isn't taken back) and is behind it now
else if ((carried == 0u) && (moved != 0.0) && (reached[k] >= 0.001) && ((mask[k] - before) * sign(moved) > 0.003) && ((mask[k] - now) * sign(moved) <= 0.0))
{
carried = uint(k + 1);
way = (moved > 0.0) ? 1.0 : -1.0;
// the blade doesn't take all of it: now and then a droplet stays behind
vec4 r = water_random(seed, 5u, place, slot);
if ((false == flake) && (drop.place.z > 1.0) && (r.x < 0.35))
{
float left = mix(0.02, 0.08, r.y);
if (water_add(slot, seed ^ 0x9e3779b9u, uv, left, 1.0, r.z, false))
drop.place.z -= left;
}
}
}
vec3 velocity = frame.tu * drop.motion.x + frame.tv * drop.motion.y;
if (carried != 0u)
{
// in front of the blade by its own size and the lip of the bead: the place where the mask is as far ahead of the blade,
// found along the gradient of the mask (along the arc of the blade)
int k = int(carried) - 1;
float radius = flake ? 0.5 * water_flake(drop.place.z) : water_radius(drop.place.z);
float a = dot(frame.tu, frame.tu);
float b = dot(frame.tu, frame.tv);
float c = dot(frame.tv, frame.tv);
float det = max(a * c - b * b, 1e-20);
float h = 1.0 / float(WATER_MAP);
bool edge = false;
for (int step = 0; step < 4; ++step)
{
// at the edge of the sweep (the end of the blade) the water squeezes out and stays
if ((water_blades_texel(slot, uv + vec2(h, 0.0))[k] < 0.001) || (water_blades_texel(slot, uv - vec2(h, 0.0))[k] < 0.001) ||
(water_blades_texel(slot, uv + vec2(0.0, h))[k] < 0.001) || (water_blades_texel(slot, uv - vec2(0.0, h))[k] < 0.001))
{
edge = true;
break;
}
float m = water_blades_at(slot, uv)[k];
vec2 gradient = vec2(water_blades_at(slot, uv + vec2(h, 0.0))[k] - water_blades_at(slot, uv - vec2(h, 0.0))[k],
water_blades_at(slot, uv + vec2(0.0, h))[k] - water_blades_at(slot, uv - vec2(0.0, h))[k]) / (2.0 * h);
float g2 = dot(gradient, gradient);
if (g2 < 1e-6)
break;
// how fast the mask changes along the glass, per metre
float permetre = sqrt(max((c * gradient.x * gradient.x - 2.0 * b * gradient.x * gradient.y + a * gradient.y * gradient.y) / det, 0.0));
float target = water_blades_now[k] + way * (0.0015 + radius) * permetre;
vec2 delta = gradient * ((target - m) / g2);
float length_delta = length(delta);
if (length_delta > 0.02)
delta *= 0.02 / length_delta;
uv += delta;
if (length_delta < 1e-5)
break;
}
// pushed out of the sweep it stays at its edge
if (edge || (water_blades_texel(slot, uv)[k] < 0.001))
carried = 0u;
velocity = vec3(0.0);
}
else if (false == flake)
{
float volume = drop.place.z;
float radius = water_radius(volume);
vec3 n = frame.normal;
vec3 up = water_up.xyz;
// gravity along the glass
float weight = 1000.0 * WATER_GRAVITY * volume * 1e-9;
vec3 force = -(up - n * dot(up, n)) * weight;
// the drag of the air going along the glass, with a share of the air meeting it head on turned up along it
vec3 air = water_air.xyz;
vec3 rise = up - n * dot(up, n);
rise = (length(rise) > 0.1) ? normalize(rise) : vec3(0.0);
vec3 flow = (air - n * dot(air, n) + rise * (max(-dot(air, n), 0.0) * WATER_HEAD_ON)) * WATER_BOUNDARY;
float area = 2.0 * radius * (0.364 * radius) * 0.8;
force += flow * (0.6 * area * length(flow));
// the hold of the line of contact, uneven over the glass, much less on a path wetted lately
vec2 spread = uv * vec2(length(frame.tu), length(frame.tv));
float uneven = mix(0.55, 1.45, water_noise(spread / 0.003, 3u + slot));
float hold = WATER_TENSION * 2.0 * radius * WATER_HOLD * uneven * mix(1.0, 0.3, water_fresh(slot, uv)) * mix(0.85, 1.15, drop.motion.z);
float pull = length(force);
vec3 target = vec3(0.0);
if (pull > hold)
{
vec3 dir = force / pull;
// the way bent towards the side which holds the drop less: the wetter one (the water keeps to the wetted paths) and the
// one where the glass is cleaner, so it meanders
vec3 side = cross(n, dir);
float offset = max(radius, 0.0015);
vec2 leftuv = uv + water_to_uv(frame, side * offset);
vec2 rightuv = uv - water_to_uv(frame, side * offset);
float wetter = water_fresh(slot, leftuv) - water_fresh(slot, rightuv);
float cleaner = water_noise(rightuv * vec2(length(frame.tu), length(frame.tv)) / 0.003, 3u + slot) - water_noise(leftuv * vec2(length(frame.tu), length(frame.tv)) / 0.003, 3u + slot);
float wander = water_noise(spread / 0.02 + vec2(17.0 * drop.motion.z), 11u + slot) - 0.5;
dir = normalize(dir + side * (wetter * 0.6 + cleaner * 0.5 + wander * 0.35));
target = dir * min((pull - hold) / (WATER_VISCOUS * radius), 1.5);
}
velocity += (target - velocity) * (1.0 - exp(-seconds / ((pull > hold) ? 0.05 : 0.015)));
velocity -= n * dot(velocity, n);
vec3 moved = velocity * seconds;
float travel = length(moved);
if (travel > 1e-7)
{
vec2 next = uv + water_to_uv(frame, moved);
water_wet_path(slot, frame, uv, next, 0.4 * radius);
// a sliding drop leaves droplets behind as it goes, and thins; a large one breaks up the faster (a lot of water runs as
// a stream of beads rather than as one big drop)
extra.x += travel;
if ((extra.x >= extra.y) && (volume > 1.5))
{
vec4 r = water_random(seed, 6u, place, slot);
float left = clamp(volume * mix(0.01, 0.03, r.x) * (1.0 + volume / 40.0), 0.01, 0.25 * volume);
vec3 behind = -(velocity / max(length(velocity), 1e-6)) * ((radius + 1.2 * water_radius(left)) * 1.05);
if (water_add(slot, seed ^ 0x85ebca6bu, uv + water_to_uv(frame, behind), left, 1.0, r.y, false))
drop.place.z -= left;
extra.x = 0.0;
extra.y = mix(0.002, 0.005, r.z) / (1.0 + volume / 100.0);
}
uv = next;
}
// without the rain the water dries, slowly
if (water_step.z <= 0.0)
drop.place.z -= seconds * 0.004 * radius * 1000.0;
}
else
{
velocity = vec3(0.0);
}
if ((drop.place.z < WATER_LEAST) || any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0))))
{
water_remove(index);
return;
}
water_frame_s there = water_frame(slot, uv);
if (there.valid < 0.25)
{
// off the glass, it falls off
water_remove(index);
return;
}
drop.place.xy = uv;
drop.place.w += seconds;
drop.motion.xy = water_to_uv(there, velocity);
water_drops[index] = drop;
water_extra[index] = extra;
water_state[index] = 1u | (carried << 1u) | ((way > 0.0) ? 16u : 0u);
}

View File

@@ -0,0 +1,39 @@
#version 460 core
#define STAGE_COMPUTE 1
// the water on the windscreens (water_sim.glsl): the drops which gathered others take their water, move to the middle of it and
// slow down as much as the still water slows them; the header of the glass tells the glasses it's simulated. a thread per drop,
// a row of groups per glass
// none of the pass set: everything the kernel reads is pushed with its dispatch (set COMPUTE_SET, water_sim.glsl), so it runs
// before the passes of the frame set theirs up
#define LAYOUT_UI 1
#include "common.glsl"
#include "water_sim.glsl"
layout(local_size_x = WATER_GROUP_SIZE) in;
void main()
{
uint slot = gl_WorkGroupID.y;
uint place = gl_GlobalInvocationID.x;
if ((water_slots[slot].z < 0.5) || (place >= uint(WATER_DROPS)))
return;
if (place == 0u)
water_header[slot] = vec4(1.0, water_step.y, 0.0, 0.0);
uint index = slot * uint(WATER_DROPS) + place;
int gathered = water_gather[index * 4u];
if (gathered == 0)
return;
vec2 moment = vec2(float(water_gather[index * 4u + 1u]), float(water_gather[index * 4u + 2u])) * 1e-6;
water_gather[index * 4u] = 0;
water_gather[index * 4u + 1u] = 0;
water_gather[index * 4u + 2u] = 0;
water_drop_s drop = water_drops[index];
if (drop.place.z <= 0.0)
return;
float volume = drop.place.z + float(gathered) * 1e-4;
drop.place.xy += moment / volume;
drop.motion.xy *= drop.place.z / volume;
drop.place.z = volume;
water_drops[index] = drop;
}

279
shaders/vk14/water_sim.glsl Normal file
View File

@@ -0,0 +1,279 @@
// what the passes of the simulation of the water on the windscreens share (water_*.comp): the block of the step, the random
// numbers, the map of the glass, the forces on a drop
//
// the physics (the drops are caps of spheres flat on the glass, 40 degrees at their edge, untreated weathered glass):
// - a drop lies held by its line of contact: the hold is the surface tension times the width of the drop times the difference
// of the cosines of its angles at the front and the rear (WATER_HOLD), so it grows with the radius, while the weight grows
// with the cube of it. on a vertical glass a drop slides once its footprint is about 7 mm across (25 mm3), on a glass leaning
// back later. the hold is uneven over the glass (dirt, scratches: the drops stop and go) and much less where the water went
// lately (a wetted path: the next drops follow it and it becomes a rivulet)
// - the air going along the glass drags the drops (with the area they stand up into it, within the layer of slow air at the
// glass), and of the air meeting the glass head on a share turns up along it: at speed the water goes up the glass, the sooner
// the more it leans back
// - a sliding drop is held back by the viscosity in proportion to its speed and radius; it leaves droplets behind as it goes
// (it thins and stops in the end) and wets its path
// - drops which touch flow together into one (surface tension), the water and the momentum of the places kept
// - a raindrop hits the glass far above the speed at which it splashes: it spreads and throws droplets around, which stay
// - a blade going over a drop takes it along in front of itself; the water it gathers flows together into a bead along it, left
// at the ends of its sweep (where it runs down as streams) and pushed off where the glass ends
#ifndef WATER_SIM_GLSL
#define WATER_SIM_GLSL
#include "water.glsl"
// gl::water_ubs, every vector in the space of the model of the cab
layout(std140, set = COMPUTE_SET, binding = 1) uniform water_ubo
{
// x: seconds of the step, y: seconds the simulation runs, z: how heavy the rain or the snow is 0 .. 1, w: 1 for snow
vec4 water_step;
// xyz: up, w: the temperature of the air, degrees Celsius
vec4 water_up;
// xyz: the air against the cab, m/s; w: how fast the rain or the snow falls, m/s
vec4 water_air;
// the blades now and at the step before: 0 .. 1 across their sweep, below 0 where there's none
vec4 water_blades_now;
vec4 water_blades_before;
// x: the smallest diameter of a raindrop landing as a drop of its own, mm, y: 1 / the mean of the diameters above it, 1 / mm
vec4 water_rain;
// x: random seed of the step, y: the most landings tried on a glass, z: the glass a single glass pass works on
uvec4 water_counts;
// every glass: x the landings tried in the step, y 1 / the most metres squared a unit of its uv holds, z 1 when it's simulated,
// w the size of its drops (rain_scale of its material)
vec4 water_slots[WATER_SLOTS];
};
const float WATER_GRAVITY = 9.81;
// the surface tension of water, N/m, and the share of it a drop is held by for the width of its footprint
const float WATER_TENSION = 0.072;
const float WATER_HOLD = 0.5;
// the viscous drag of a sliding drop for its radius and speed, N s / m2
const float WATER_VISCOUS = 1.3;
// the share of the speed of the air the drops feel in the slow layer at the glass, and of the air meeting the glass head on
// which turns up along it
const float WATER_BOUNDARY = 0.3;
const float WATER_HEAD_ON = 0.35;
// the drops below this much water (mm3) dry at once; a raindrop or a droplet of a splash smaller than WATER_SEEN (0.5 mm across
// its footprint, hardly a pixel) landing where there's no drop is left to the mist of the glass
const float WATER_LEAST = 0.004;
const float WATER_SEEN = 0.075;
uvec4 water_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 random numbers 0 .. 1
vec4 water_random(uint a, uint b, uint c, uint d)
{
return vec4(water_pcg4d(uvec4(a, b, c, d))) * (1.0 / 4294967296.0);
}
// value noise 0 .. 1
float water_noise(vec2 p, uint layer)
{
vec2 i = floor(p);
vec2 f = p - i;
vec2 u = f * f * (3.0 - 2.0 * f);
uvec2 c = uvec2(ivec2(i));
float a = water_random(c.x, c.y, layer, 0u).x;
float b = water_random(c.x + 1u, c.y, layer, 0u).x;
float d = water_random(c.x, c.y + 1u, layer, 0u).x;
float e = water_random(c.x + 1u, c.y + 1u, layer, 0u).x;
return mix(mix(a, b, u.x), mix(d, e, u.x), u.y);
}
// the glass at a place of the uv: the steps along it for the steps of u and v (metres), the way out of the cab and whether the
// place is on the glass (1), next to it (0.5) or off it (0)
struct water_frame_s
{
vec3 tu;
vec3 tv;
vec3 normal;
float valid;
};
water_frame_s water_frame(uint slot, vec2 uv)
{
water_frame_s frame = water_frame_s(vec3(0.0), vec3(0.0), vec3(0.0, 0.0, 1.0), 0.0);
if (any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0))))
return frame;
uint base = slot * uint(WATER_MAP * WATER_MAP);
vec2 p = uv * float(WATER_MAP) - 0.5;
ivec2 i = ivec2(floor(p));
vec2 f = p - vec2(i);
float total = 0.0;
float side = 0.0;
for (int k = 0; k < 4; ++k)
{
ivec2 texel = clamp(i + ivec2(k & 1, k >> 1), ivec2(0), ivec2(WATER_MAP - 1));
uint index = base + uint(texel.y * WATER_MAP + texel.x);
vec4 a = water_axes[index * 2u];
vec4 b = water_axes[index * 2u + 1u];
if (a.w <= 0.0)
continue;
float w = (((k & 1) != 0) ? f.x : 1.0 - f.x) * (((k >> 1) != 0) ? f.y : 1.0 - f.y) + 1e-4;
frame.tu += a.xyz * w;
frame.tv += b.xyz * w;
side += b.w * w;
total += w;
}
if (total <= 0.0)
return frame;
frame.tu /= total;
frame.tv /= total;
vec3 n = cross(frame.tu, frame.tv);
float length_n = length(n);
if (length_n < 1e-12)
return frame;
frame.normal = n * (((side < 0.0) ? -1.0 : 1.0) / length_n);
// on the glass by the texel the place is in
ivec2 nearest = clamp(ivec2(floor(uv * float(WATER_MAP))), ivec2(0), ivec2(WATER_MAP - 1));
frame.valid = water_axes[(base + uint(nearest.y * WATER_MAP + nearest.x)) * 2u].w;
return frame;
}
// a step along the glass (metres, the space of the cab) as a step of the uv
vec2 water_to_uv(water_frame_s frame, vec3 step)
{
float a = dot(frame.tu, frame.tu);
float b = dot(frame.tu, frame.tv);
float c = dot(frame.tv, frame.tv);
float det = a * c - b * b;
if (abs(det) < 1e-20)
return vec2(0.0);
vec2 r = vec2(dot(frame.tu, step), dot(frame.tv, step));
return vec2(c * r.x - b * r.y, a * r.y - b * r.x) / det;
}
// the blades at a place of the uv, filtered between the texels of the map
vec4 water_blades_at(uint slot, vec2 uv)
{
uint base = slot * uint(WATER_MAP * WATER_MAP);
vec2 p = clamp(uv, vec2(0.0), vec2(1.0)) * float(WATER_MAP) - 0.5;
ivec2 i = ivec2(floor(p));
vec2 f = p - vec2(i);
vec4 result = vec4(0.0);
for (int k = 0; k < 4; ++k)
{
ivec2 texel = clamp(i + ivec2(k & 1, k >> 1), ivec2(0), ivec2(WATER_MAP - 1));
float w = (((k & 1) != 0) ? f.x : 1.0 - f.x) * (((k >> 1) != 0) ? f.y : 1.0 - f.y);
result += water_blades[base + uint(texel.y * WATER_MAP + texel.x)] * w;
}
return result;
}
// the blades at the texel of the map holding a place of the uv, unfiltered: where a blade reaches it's at least 0.001 (the
// filtered values fall off across a texel at the edge of the sweep)
vec4 water_blades_texel(uint slot, vec2 uv)
{
ivec2 texel = clamp(ivec2(floor(uv * float(WATER_MAP))), ivec2(0), ivec2(WATER_MAP - 1));
return water_blades[slot * uint(WATER_MAP * WATER_MAP) + uint(texel.y * WATER_MAP + texel.x)];
}
// a free place for a drop of the glass, taken; ~0u when none was found
uint water_claim(uint slot, uint seed)
{
for (uint k = 0u; k < 8u; ++k)
{
uint place = water_pcg4d(uvec4(seed, k, slot, 77u)).x % uint(WATER_DROPS);
if (atomicCompSwap(water_state[slot * uint(WATER_DROPS) + place], 0u, 1u) == 0u)
return place;
}
return ~0u;
}
// a new drop of the glass: Volume mm3 at a place, a flake of snow when Melted is below 1. false when the glass is full. the
// landing rain and snow get only nine tenths of the places, so there are always some for the droplets the moving drops leave
bool water_add(uint slot, uint seed, vec2 uv, float volume, float melted, float shape, bool landing)
{
if ((volume < WATER_LEAST) || any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0))))
return false;
if (landing && (water_counts_of[slot].x >= uint(WATER_DROPS) / 10u * 9u))
return false;
uint place = water_claim(slot, seed);
if (place == ~0u)
return false;
atomicAdd(water_counts_of[slot].x, 1u);
uint index = slot * uint(WATER_DROPS) + place;
water_drops[index].place = vec4(uv, volume, 0.0);
water_drops[index].motion = vec4(0.0, 0.0, shape, melted);
water_extra[index] = vec4(0.0, 0.004 * (0.6 + 0.8 * shape), 0.0, 0.0);
water_target[index] = place;
for (uint k = 0u; k < 4u; ++k)
water_gather[index * 4u + k] = 0;
return true;
}
// the slot of the drop on the glass freed, with what was flowing into it
void water_remove(uint index)
{
water_drops[index].place.z = 0.0;
water_state[index] = 0u;
atomicAdd(water_counts_of[index / uint(WATER_DROPS)].x, ~0u);
for (uint k = 0u; k < 4u; ++k)
water_gather[index * 4u + k] = 0;
}
// Volume mm3 of water flowing into a drop (index in the storage) from Offset (uv) away from its middle, taken by
// water_settle.comp
void water_pour(uint index, float volume, vec2 offset)
{
atomicAdd(water_gather[index * 4u], int(volume * 1e4));
atomicAdd(water_gather[index * 4u + 1u], int(offset.x * volume * 1e6));
atomicAdd(water_gather[index * 4u + 2u], int(offset.y * volume * 1e6));
}
// the drop lying at a place of the glass which water of Radius (metres) landing there touches, the largest of them, by the grid
// of the step before (~0u: none)
uint water_landing_on(uint slot, water_frame_s frame, vec2 uv, float radius)
{
uint cell = water_cell(uv);
uint base = water_grid_base(slot);
uint count = min(water_cells[base + cell], uint(WATER_CELL_DROPS));
uint best = ~0u;
float most = 0.0;
for (uint k = 0u; k < count; ++k)
{
uint other = water_cells[base + uint(WATER_GRID * WATER_GRID) + cell * uint(WATER_CELL_DROPS) + k];
water_drop_s drop = water_drops[slot * uint(WATER_DROPS) + other];
if ((drop.place.z <= most) || (drop.motion.w < 1.0))
continue;
vec2 step = drop.place.xy - uv;
if (length(frame.tu * step.x + frame.tv * step.y) < 0.9 * (radius + water_radius(drop.place.z)))
{
best = other;
most = drop.place.z;
}
}
return best;
}
// water landing on the glass: it flows into a drop it touches, else it's a new drop where it's large enough to be seen
void water_land(uint slot, uint seed, water_frame_s frame, vec2 uv, float volume, float shape)
{
if (any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0))))
return;
uint onto = water_landing_on(slot, frame, uv, water_radius(volume));
if (onto != ~0u)
{
uint index = slot * uint(WATER_DROPS) + onto;
water_pour(index, volume, uv - water_drops[index].place.xy);
}
else if (volume >= WATER_SEEN)
{
water_add(slot, seed, uv, volume, 1.0, shape, true);
}
}
#endif

View File

@@ -0,0 +1,68 @@
#version 460 core
#define STAGE_COMPUTE 1
// the water on the windscreens (water_sim.glsl): the rain or the snow landing on the glasses in the step. a thread per landing
// tried, a row of groups per glass: a place of the uv picked at random is taken as often as the glass there gets hit (the
// metres squared the place holds, how much it faces the rain coming at it), so the landings go evenly over the glass. a raindrop
// spreads and throws a splash of droplets around, along the way it came, which flow into the drops they land on (water_settle.comp
// takes the water); a flake sticks where it lands
// none of the pass set: everything the kernel reads is pushed with its dispatch (set COMPUTE_SET, water_sim.glsl), so it runs
// before the passes of the frame set theirs up
#define LAYOUT_UI 1
#include "common.glsl"
#include "water_sim.glsl"
layout(local_size_x = WATER_GROUP_SIZE) in;
void main()
{
uint slot = gl_WorkGroupID.y;
uint landing = gl_GlobalInvocationID.x;
if ((water_slots[slot].z < 0.5) || (float(landing) >= water_slots[slot].x))
return;
vec4 r = water_random(water_counts.x, slot, landing, 0u);
vec2 uv = r.xy;
water_frame_s frame = water_frame(slot, uv);
if (frame.valid < 0.75)
return;
// the rain coming at the glass, against the cab
vec3 coming = water_air.xyz - water_up.xyz * water_air.w;
float speed = length(coming);
if (speed < 1e-3)
return;
float facing = max(-dot(coming, frame.normal), 0.0) / speed;
float density = length(cross(frame.tu, frame.tv));
if (r.z >= density * water_slots[slot].y * facing)
return;
float scale = max(water_slots[slot].w, 0.05);
vec4 s = water_random(water_counts.x, slot, landing, 1u);
uint seed = water_pcg4d(uvec4(water_counts.x, slot, landing, 2u)).x;
if (water_step.w > 0.5)
{
// a flake, a few mm of snow holding a little water
water_add(slot, seed, uv, mix(0.3, 2.5, s.x) * scale * scale * scale, 0.0, s.y, true);
return;
}
// the diameter of the raindrop, mm: above the least landing as a drop of its own they go as Marshall-Palmer (exponential)
float diameter = min(water_rain.x - log(max(r.w, 1e-6)) / max(water_rain.y, 0.1), 5.0) * scale;
float drop = 0.5236 * diameter * diameter * diameter;
// most of it stays where it hit, the rest is thrown around as droplets, forward along the glass
float kept = mix(0.55, 0.85, s.x);
water_land(slot, seed, frame, uv, drop * kept, s.y);
vec3 along = coming - frame.normal * dot(coming, frame.normal);
float glancing = length(along) / speed;
vec3 forward = (glancing > 1e-3) ? normalize(along) : normalize(frame.tu);
vec3 across = cross(frame.normal, forward);
float radius = water_radius(drop * kept);
int splash = 2 + int(s.z * 4.0);
for (int k = 0; k < splash; ++k)
{
vec4 e = water_random(water_counts.x, slot, landing, 3u + uint(k));
float angle = e.x * 6.2831853;
vec3 way = cos(angle) * forward + sin(angle) * across + forward * (1.5 * glancing);
way = (length(way) > 1e-3) ? normalize(way) : forward;
float distance = radius * (1.3 + 2.5 * e.y) * (1.0 + glancing);
float volume = drop * (1.0 - kept) / float(splash) * mix(0.4, 1.6, e.z);
water_land(slot, seed + 101u * uint(k + 1), frame, uv + water_to_uv(frame, way * distance), volume, e.w);
}
}

View File

@@ -0,0 +1,37 @@
#version 460 core
#define STAGE_COMPUTE 1
// the water on the windscreens (water_sim.glsl): the blades going over the wetted paths in the step wipe them. a thread per texel
// of the map of the paths, a row of groups per glass
// none of the pass set: everything the kernel reads is pushed with its dispatch (set COMPUTE_SET, water_sim.glsl), so it runs
// before the passes of the frame set theirs up
#define LAYOUT_UI 1
#include "common.glsl"
#include "water_sim.glsl"
layout(local_size_x = WATER_GROUP_SIZE) in;
void main()
{
uint slot = gl_WorkGroupID.y;
uint texel = gl_GlobalInvocationID.x;
if ((water_slots[slot].z < 0.5) || (texel >= uint(WATER_TRAIL * WATER_TRAIL)))
return;
uint index = water_trail_base(slot) + texel;
if (water_cells[index] == 0u)
return;
vec2 uv = (vec2(float(texel % uint(WATER_TRAIL)), float(texel / uint(WATER_TRAIL))) + 0.5) / float(WATER_TRAIL);
vec4 mask = water_blades_at(slot, uv);
for (int k = 0; k < 4; ++k)
{
float before = water_blades_before[k];
float now = water_blades_now[k];
if ((mask[k] < 0.001) || (before < 0.0) || (now < 0.0) || (before == now))
continue;
if ((mask[k] > min(before, now)) && (mask[k] <= max(before, now)))
{
water_cells[index] = 0u;
return;
}
}
}