// painterly_water.gdshader — Godot 4.x // Flat, gouache-style water for a straight river + cascade. // // RIVER preset: uv_scale = vec2(2.0, 6.0), flow_speed = 0.10, wobble = 0.05 // FALL preset: uv_scale = vec2(3.0, 1.0), flow_speed = 0.55, wobble = 0.02, // edge_foam_top = 0.25, edge_foam_bottom = 0.35, foam_amount = 0.75 // POOL preset: uv_scale = vec2(3.0, 3.0), flow_speed = 0.03, wobble = 0.08 shader_type spatial; render_mode blend_mix, cull_disabled, diffuse_lambert, specular_disabled, shadows_disabled; // --- palette: keep it to 3 tones + foam, like mixing real paint --- uniform vec3 color_deep : source_color = vec3(0.07, 0.26, 0.40); uniform vec3 color_mid : source_color = vec3(0.16, 0.50, 0.58); uniform vec3 color_light : source_color = vec3(0.48, 0.79, 0.78); uniform vec3 color_foam : source_color = vec3(0.95, 0.97, 0.94); uniform sampler2D noise_a : repeat_enable, filter_linear; uniform sampler2D noise_b : repeat_enable, filter_linear; uniform sampler2D canvas : repeat_enable, filter_linear; uniform vec2 uv_scale = vec2(2.0, 6.0); uniform vec2 flow_dir = vec2(0.0, 1.0); // +Y = "downstream" along the plane's V axis uniform float flow_speed = 0.10; uniform float wobble : hint_range(0.0, 0.25) = 0.05; // brush wander uniform float bands : hint_range(2.0, 8.0) = 4.0; // fewer = more poster-like uniform float foam_amount: hint_range(0.0, 1.0) = 0.55; // foam strips along the top / bottom edge of the mesh (for the lip and the splash base) uniform float edge_foam_top : hint_range(0.0, 0.6) = 0.0; uniform float edge_foam_bottom : hint_range(0.0, 0.6) = 0.0; uniform float canvas_scale = 4.0; uniform float canvas_strength : hint_range(0.0, 1.0) = 0.25; void fragment() { vec2 uv = UV * uv_scale; vec2 flow = flow_dir * TIME * flow_speed; // slow low-frequency warp — this is what sells the "hand-painted" wobble vec2 warp = (texture(noise_b, uv * 0.3 - flow * 0.25).rg - 0.5) * wobble * 2.0; // two layers at different speeds so the motion never looks like a sliding texture float n1 = texture(noise_a, uv + warp - flow).r; float n2 = texture(noise_a, uv * 1.9 + warp.yx - flow * 1.7 + vec2(0.37)).r; float v = n1 * 0.65 + n2 * 0.35; // posterize into flat steps — the single most important line here float q = floor(v * bands) / max(bands - 1.0, 1.0); // edge foam strips float top = 1.0 - smoothstep(0.0, max(edge_foam_top, 0.001), UV.y); float bottom = smoothstep(1.0 - max(edge_foam_bottom, 0.001), 1.0, UV.y); float edge = clamp(top + bottom, 0.0, 1.0) * step(0.35, v); // let noise break it up vec3 col = mix(color_deep, color_mid, smoothstep(0.00, 0.50, q)); col = mix(col, color_light, smoothstep(0.50, 0.85, q)); col = mix(col, color_foam, step(1.0 - foam_amount * 0.4, q)); col = mix(col, color_foam, edge); // canvas grain in screen space so the paper stays put while the water moves float grain = texture(canvas, SCREEN_UV * canvas_scale).r; col = mix(col, col * (0.75 + grain * 0.5), canvas_strength); ALBEDO = col; ROUGHNESS = 1.0; SPECULAR = 0.0; METALLIC = 0.0; }