feat(render3d): the lake answers to a body standing in it

water.frag takes u_wade - a point and a strength - and puts spreading rings and a
patch of churn into the surface normals there, so a wader marks the water and a
swimmer works the whole of it. It is one uniform in a fragment stage that was
already running, applied after the distance flattening so a disturbance close to
the camera survives it, and it measures no frame cost at all.

Also records the two renderer features that shipped without a changeset.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-19 20:23:27 +03:00
parent 5ca14eeaec
commit 4975c60ac1
6 changed files with 62 additions and 7 deletions

View file

@ -3076,7 +3076,7 @@ U 46e2325c vert u_level 128 float 1 0
U 46e2325c vert u_center 136 vec2 1 0
U 46e2325c vert u_extent 144 vec2 1 0
I 46e2325c a_xz 0 vec2
B 46e2325c frag 1 996
B 46e2325c frag 1 1012
U 46e2325c frag u_cascade_vp 0 mat4 5 64
U 46e2325c frag u_cascade_split 320 float 5 16
U 46e2325c frag u_cascade_range 400 float 5 16
@ -3112,12 +3112,13 @@ U 46e2325c frag u_time 808 float 1 0
U 46e2325c frag u_fog_inscatter 812 float 1 0
U 46e2325c frag u_fog_desat 816 float 1 0
U 46e2325c frag u_view 832 mat4 1 0
U 46e2325c frag u_inv_vp 896 mat4 1 0
U 46e2325c frag u_screen 960 vec2 1 0
U 46e2325c frag u_refl_on 968 float 1 0
U 46e2325c frag u_center 976 vec2 1 0
U 46e2325c frag u_extent 984 vec2 1 0
U 46e2325c frag u_clip_ellipse 992 float 1 0
U 46e2325c frag u_wade 896 vec3 1 0
U 46e2325c frag u_inv_vp 912 mat4 1 0
U 46e2325c frag u_screen 976 vec2 1 0
U 46e2325c frag u_refl_on 984 float 1 0
U 46e2325c frag u_center 992 vec2 1 0
U 46e2325c frag u_extent 1000 vec2 1 0
U 46e2325c frag u_clip_ellipse 1008 float 1 0
T 46e2325c u_brdf 2
T 46e2325c u_depth 3
T 46e2325c u_irradiance 4

View file

@ -2,6 +2,7 @@
in vec3 v_wpos;
out vec4 o_color;
uniform mat4 u_view;
uniform vec3 u_wade; // x, z, strength: a body standing in the water
uniform sampler2D u_depth; // scene depth (resolved) for shore softness / depth tint
uniform mat4 u_inv_vp;
uniform vec2 u_screen;
@ -40,6 +41,24 @@ void main() {
float dist = length(u_cam_pos - v_wpos);
vec3 n = rippleNormal(v_wpos.xz, u_time);
n = normalize(mix(n, vec3(0, 1, 0), smoothstep(60.0, 400.0, dist))); // flat sooner: past a few tens of metres a still lake has no texture at all
// ---- A BODY IN THE WATER ------------------------------------------------------------
// You waded into a mountain lake and the surface did not notice. Rings spreading from the
// legs and a patch of churn around them is the whole of it, and it is the difference
// between standing in water and standing in a picture of water.
//
// Applied AFTER the distance flattening on purpose: the flattening is there because a
// sheltered lake really is glass at forty metres, but what you yourself are doing to the
// water is by definition within arm's reach, and it must not be flattened away.
if (u_wade.z > 0.0) {
float rd = length(v_wpos.xz - u_wade.xy);
// rings travelling outward, dying off within a few metres
float ring = sin(rd * 6.5 - u_time * 8.0) * exp(-rd * 0.5);
// and the disturbed patch right at the body, which is not a ring but a mess
float churn = exp(-rd * 1.8) * (sin(u_time * 13.0 + rd * 9.0) * 0.5 + 0.5);
vec2 away = (rd > 1e-3) ? (v_wpos.xz - u_wade.xy) / rd : vec2(1.0, 0.0);
float amp = u_wade.z * (ring * 0.9 + churn * 0.7);
n = normalize(n + vec3(away.x * amp, 0.0, away.y * amp) * 0.55);
}
// how deep the ground is under this pixel: from the scene depth
vec2 suv = gl_FragCoord.xy / u_screen;
float sd = texture(u_depth, suv).r;