17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-29 17:27:30 +01:00
Files
maszyna/shaders/ogl46/water_move.comp
2026-10-26 15:51:56 +01:00

255 lines
10 KiB
Plaintext

#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);
}