feat(environment): add painterly skies and living meadows

This commit is contained in:
Rijad Zuzo
2026-09-05 20:27:09 +02:00
parent 7899d36f03
commit ec16e2f2ca
71 changed files with 2280 additions and 213 deletions
+66 -27
View File
@@ -1,42 +1,81 @@
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.055;
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.46;
uniform vec3 deep_color : source_color = vec3(0.03, 0.39, 0.45);
uniform vec3 shallow_color : source_color = vec3(0.07, 0.67, 0.64);
uniform vec3 sunlit_color : source_color = vec3(0.55, 0.95, 0.78);
uniform vec3 foam_color : source_color = vec3(0.82, 0.94, 0.84);
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() {
float broad_wave = sin(VERTEX.x * 0.42 + VERTEX.z * 0.18 + TIME * wave_speed);
float crossing_wave = cos(VERTEX.x * 0.7 - VERTEX.z * 0.31 + TIME * wave_speed * 0.73);
// 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() {
vec2 flowing_uv = UV * vec2(7.0, 10.0);
flowing_uv.y -= TIME * flow_speed;
float ribbon_a = sin(flowing_uv.y + sin(flowing_uv.x * 0.72) * 1.25) * 0.5 + 0.5;
float ribbon_b = sin(flowing_uv.y * 1.7 - flowing_uv.x * 0.55 + TIME * 0.23) * 0.5 + 0.5;
float quiet_ripple = smoothstep(0.72, 0.98, ribbon_a * 0.62 + ribbon_b * 0.38);
float fresnel = pow(1.0 - clamp(dot(normalize(NORMAL), normalize(VIEW)), 0.0, 1.0), 2.4);
float bank_distance = min(UV.x, 1.0 - UV.x);
float bank_foam = 1.0 - smoothstep(0.0, 0.07, bank_distance);
bank_foam *= 0.72 + sin(UV.y * 52.0 + TIME * 0.35) * 0.12;
vec3 water_color = mix(deep_color, shallow_color, ribbon_a * 0.2 + 0.48);
water_color = mix(water_color, sunlit_color, fresnel * 0.42);
water_color = mix(water_color, foam_color, quiet_ripple * 0.05);
water_color = mix(water_color, foam_color, bank_foam * 0.3);
// 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 = mix(0.34, 0.5, ribbon_b);
SPECULAR = 0.38;
EMISSION = water_color * 0.12 + sunlit_color * (quiet_ripple * 0.05 + fresnel * 0.03);
EMISSION += foam_color * bank_foam * 0.025;
ALPHA = mix(0.94, 0.99, fresnel);
ROUGHNESS = 0.38 + broad_patch * 0.08;
SPECULAR = 0.14;
ALPHA = smoothstep(0.0, 0.018, bank_distance) * 0.97;
}