shader_type spatial; render_mode blend_mix, depth_prepass_alpha, cull_disabled, diffuse_burley, specular_schlick_ggx; uniform float wave_strength : hint_range(0.0, 0.3) = 0.025; uniform float wave_speed : hint_range(0.0, 3.0) = 0.72; uniform float flow_speed : hint_range(0.0, 2.0) = 0.28; uniform bool pool_surface = false; uniform float pool_outlet_z = -20.0; uniform vec3 deep_color : source_color = vec3(0.035, 0.34, 0.54); uniform vec3 shallow_color : source_color = vec3(0.12, 0.65, 0.69); uniform vec3 sunlit_color : source_color = vec3(0.57, 0.83, 0.88); uniform vec3 foam_color : source_color = vec3(0.9, 0.96, 0.91); varying vec3 water_position; float painted_noise(vec2 p) { vec2 cell = floor(p); vec2 blend = fract(p); blend = blend * blend * (3.0 - 2.0 * blend); vec4 corners = fract(sin(vec4( dot(cell, vec2(127.1, 311.7)), dot(cell + vec2(1.0, 0.0), vec2(127.1, 311.7)), dot(cell + vec2(0.0, 1.0), vec2(127.1, 311.7)), dot(cell + vec2(1.0), vec2(127.1, 311.7)) )) * 43758.5453); return mix(mix(corners.x, corners.y, blend.x), mix(corners.z, corners.w, blend.x), blend.y); } void vertex() { // World coordinates keep the pool and both ribbons at the same ripple scale. water_position = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; float broad_wave = sin(water_position.x * 0.42 + water_position.z * 0.18 + TIME * wave_speed); float crossing_wave = cos(water_position.x * 0.7 - water_position.z * 0.31 + TIME * wave_speed * 0.73); VERTEX.y += (broad_wave + crossing_wave * 0.45) * wave_strength; } void fragment() { // The ribbon owns the outlet so two transparent surfaces never overlap. if (pool_surface && water_position.z >= pool_outlet_z) { discard; } vec2 flow = water_position.xz - vec2(0.0, TIME * flow_speed); float broad_patch = painted_noise(flow * vec2(0.18, 0.3)); float small_patch = painted_noise(flow * vec2(0.64, 0.85)); float ripple_phase = flow.y * 3.2 + sin(flow.x * 0.83) * 1.2 + small_patch * 0.8; float ribbon = sin(ripple_phase); float stroke_width = max(fwidth(ribbon) * 0.7, 0.012); float strokes = smoothstep(0.975 - stroke_width, 0.975 + stroke_width, ribbon); strokes *= smoothstep(0.58, 0.8, painted_noise(flow * vec2(1.1, 1.8))) * 0.16; strokes *= 1.0 - smoothstep(0.28, 0.8, fwidth(ripple_phase)); vec3 ripple_normal = normalize(vec3( cos(flow.x * 0.83 + flow.y * 0.37 + broad_patch * 6.0) * 0.006, 1.0, cos(ripple_phase + small_patch * 5.0) * 0.008 )); NORMAL = normalize((VIEW_MATRIX * vec4(ripple_normal, 0.0)).xyz); float fresnel = pow(1.0 - clamp(dot(NORMAL, normalize(VIEW)), 0.0, 1.0), 2.6); // Painted sky patches need no screen/depth texture and work in Compatibility. vec3 world_view = normalize((INV_VIEW_MATRIX * vec4(VIEW, 0.0)).xyz); vec3 reflection = reflect(-world_view, ripple_normal); vec2 sky_uv = reflection.xz / max(reflection.y + 0.35, 0.25); float clouds = painted_noise(sky_uv * 2.0 + vec2(0.7, 2.4)); clouds = smoothstep(0.49, 0.77, clouds + painted_noise(sky_uv * 4.5) * 0.18); float bank_distance = min(UV.x, 1.0 - UV.x) * 2.0; if (pool_surface) { bank_distance = 1.0 - length(UV * 2.0 - 1.0); } float bank_foam = (1.0 - smoothstep(0.01, 0.045, bank_distance)); bank_foam *= smoothstep(0.4, 0.75, small_patch) * 0.48; vec3 water_color = mix(deep_color, shallow_color, 0.19 + broad_patch * 0.2); water_color = mix(water_color, sunlit_color, clouds * (0.13 + fresnel * 0.35)); water_color = mix(water_color, foam_color, strokes + bank_foam); ALBEDO = water_color; ROUGHNESS = 0.38 + broad_patch * 0.08; SPECULAR = 0.14; ALPHA = smoothstep(0.0, 0.018, bank_distance) * 0.97; }