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
@@ -0,0 +1,36 @@
// Original opaque petal geometry: soft wrap lighting without alpha overdraw.
shader_type spatial;
render_mode skip_vertex_transform, cull_disabled, specular_disabled;
uniform vec2 wind_direction = vec2(1.0, 0.7);
uniform float wind_amount : hint_range(0.0, 1.0) = 0.115;
uniform float fade_begin = 42.0;
uniform float fade_end = 56.0;
varying float palette_variation;
void vertex() {
vec3 anchor = MODEL_MATRIX[3].xyz;
float distance_to_camera = distance(anchor, CAMERA_POSITION_WORLD);
float presence = 1.0 - smoothstep(fade_begin, fade_end, distance_to_camera);
float height = max(VERTEX.y, 0.0);
float phase = dot(anchor.xz, vec2(0.38, 0.29));
float gust = sin(TIME * 1.15 + phase) * 0.65 + sin(TIME * 0.58 + phase * 0.42) * 0.35;
VERTEX *= presence;
vec3 world_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
world_vertex.xz += normalize(wind_direction) * wind_amount * gust * height * height * presence;
VERTEX = (VIEW_MATRIX * vec4(world_vertex, 1.0)).xyz;
vec3 local_normal = normalize(NORMAL + vec3(0.0, 1.6, 0.0));
NORMAL = normalize((MODELVIEW_MATRIX * vec4(local_normal, 0.0)).xyz);
palette_variation = mix(0.92, 1.06, INSTANCE_CUSTOM.r);
}
void fragment() {
ALBEDO = COLOR.rgb * palette_variation;
ROUGHNESS = 1.0;
}
void light() {
float sunlight = smoothstep(-0.35, 0.55, dot(NORMAL, LIGHT));
vec3 shade = mix(vec3(0.43, 0.58, 0.55), vec3(1.0, 0.98, 0.90), sunlight);
DIFFUSE_LIGHT += shade * LIGHT_COLOR * ATTENUATION / PI;
}
@@ -0,0 +1 @@
uid://b1r81tjhi1o0y
+18 -19
View File
@@ -1,27 +1,26 @@
shader_type sky;
uniform vec3 sky_top_color : source_color = vec3(0.27, 0.57, 0.74);
uniform vec3 sky_horizon_color : source_color = vec3(0.79, 0.88, 0.82);
// Original painted panorama; no volume or per-frame sky pass.
uniform sampler2D painted_panorama : source_color, filter_linear_mipmap, repeat_enable;
uniform vec3 sky_top_color : source_color = vec3(0.12, 0.49, 0.69);
uniform vec3 sky_horizon_color : source_color = vec3(0.65, 0.84, 0.84);
uniform vec3 ground_bottom_color : source_color = vec3(0.12, 0.17, 0.12);
uniform vec3 ground_horizon_color : source_color = vec3(0.56, 0.69, 0.57);
uniform vec3 ground_horizon_color : source_color = vec3(0.65, 0.84, 0.84);
uniform float daylight : hint_range(0.0, 1.0) = 1.0;
float hash21(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); }
float noise(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
f = f * f * (3.0 - 2.0 * f);
return mix(mix(hash21(i), hash21(i + vec2(1.0, 0.0)), f.x), mix(hash21(i + vec2(0.0, 1.0)), hash21(i + vec2(1.0)), f.x), f.y);
}
uniform float golden_hour : hint_range(0.0, 1.0) = 0.0;
void sky() {
float height = max(EYEDIR.y, 0.0);
vec3 sky_color = mix(sky_horizon_color, sky_top_color, pow(height, 0.48));
vec2 p = EYEDIR.xz / max(0.17, height) * 1.15;
float cloud = noise(p) * 0.65 + noise(p * 2.1 + 7.3) * 0.25 + noise(p * 4.3) * 0.10;
float mask = smoothstep(0.55, 0.64, cloud) * smoothstep(0.02, 0.20, height);
vec3 cloud_shadow = mix(vec3(0.08, 0.10, 0.19), vec3(0.70, 0.80, 0.80), daylight);
vec3 cloud_light = mix(vec3(0.15, 0.18, 0.29), vec3(1.0, 0.98, 0.87), daylight);
vec3 cloud_color = mix(cloud_shadow, cloud_light, smoothstep(0.61, 0.77, cloud));
sky_color = mix(sky_color, cloud_color, mask);
vec3 sky_gradient = mix(sky_horizon_color, sky_top_color, pow(height, 0.45));
// Art horizon is at 69% of the sheet. Remap only the upper hemisphere;
// the ground bounce uses the day/night palette and never paints terrain.
vec2 panorama_uv = vec2(fract(SKY_COORDS.x * 2.0 + 0.40), clamp((SKY_COORDS.y - 0.26) * 2.875, 0.0, 0.69));
vec3 painted = texture(painted_panorama, panorama_uv).rgb;
vec3 seam_color = (texture(painted_panorama, vec2(0.001, panorama_uv.y)).rgb + texture(painted_panorama, vec2(0.999, panorama_uv.y)).rgb) * 0.5;
painted = mix(seam_color, painted, smoothstep(0.0, 0.025, min(panorama_uv.x, 1.0 - panorama_uv.x)));
painted = mix(painted, painted * vec3(1.18, 0.79, 0.66), golden_hour * 0.50);
vec3 night_paint = painted * vec3(0.045, 0.07, 0.14) + sky_gradient * 0.12;
vec3 sky_color = mix(night_paint, painted, daylight);
// Dissolve the panorama edge into the haze at the actual horizon.
sky_color = mix(sky_gradient, sky_color, smoothstep(0.0, 0.075, height));
COLOR = EYEDIR.y >= 0.0 ? sky_color : mix(ground_horizon_color, ground_bottom_color, pow(-EYEDIR.y, 0.4));
}
+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;
}
+25
View File
@@ -0,0 +1,25 @@
shader_type spatial;
render_mode blend_mix, depth_prepass_alpha, cull_disabled, diffuse_burley;
uniform bool circular_wash = false;
uniform vec3 foam_color : source_color = vec3(0.83, 0.94, 0.93);
void fragment() {
vec2 p = UV * 2.0 - 1.0;
float wash = sin(p.x * 18.0 + sin(p.y * 12.0 + TIME * 0.8) * 1.5);
wash *= sin(p.y * 23.0 - TIME * 1.4 + p.x * 3.0);
float mask = (1.0 - smoothstep(0.45, 1.0, abs(p.x)));
mask *= 1.0 - smoothstep(0.2, 1.0, abs(p.y));
if (circular_wash) {
float radius = length(p);
float angle = atan(p.y, p.x);
float broken_arcs = sin(radius * 32.0 - TIME * 0.75 + sin(angle * 5.0) * 0.6);
mask = smoothstep(0.65, 0.94, broken_arcs);
mask *= smoothstep(0.1, 0.35, radius) * (1.0 - smoothstep(0.65, 0.98, radius));
mask *= smoothstep(-0.3, 0.8, sin(angle * 3.0 + radius * 9.0));
}
ALBEDO = foam_color;
ROUGHNESS = 0.65;
ALPHA = mask * smoothstep(-0.45, 0.6, wash) * 0.48;
}
@@ -0,0 +1 @@
uid://bcw0dj01f0m3h
+7 -8
View File
@@ -2,9 +2,9 @@ shader_type spatial;
render_mode blend_mix, depth_prepass_alpha, cull_disabled, diffuse_burley, specular_schlick_ggx;
uniform float scroll_speed : hint_range(0.0, 4.0) = 1.35;
uniform vec3 shadow_color : source_color = vec3(0.075, 0.33, 0.34);
uniform vec3 water_color : source_color = vec3(0.31, 0.68, 0.64);
uniform vec3 light_color : source_color = vec3(0.78, 0.94, 0.83);
uniform vec3 shadow_color : source_color = vec3(0.11, 0.39, 0.54);
uniform vec3 water_color : source_color = vec3(0.39, 0.73, 0.82);
uniform vec3 light_color : source_color = vec3(0.85, 0.96, 0.94);
uniform vec3 foam_color : source_color = vec3(0.91, 0.97, 0.9);
@@ -22,17 +22,16 @@ void fragment() {
float soft_thread = sin(flow_uv.x * 13.0 - flow_uv.y * 7.0) * 0.5 + 0.5;
float falling_detail = sin(flow_uv.y * 27.0 + flow_uv.x * 7.0) * 0.5 + 0.5;
float thread = smoothstep(0.36, 0.9, long_thread * 0.6 + soft_thread * 0.4);
float broken_foam = smoothstep(0.72, 0.98, falling_detail * thread);
float broken_foam = smoothstep(0.61, 0.94, falling_detail * thread);
float base_foam = smoothstep(0.58, 1.0, UV.y);
float edge_fade = smoothstep(0.0, 0.08, UV.x) * smoothstep(1.0, 0.92, UV.x);
float edge_fade = smoothstep(0.0, 0.08, UV.x) * (1.0 - smoothstep(0.92, 1.0, UV.x));
vec3 color = mix(shadow_color, water_color, 0.5 + thread * 0.35);
color = mix(color, light_color, broken_foam * 0.5);
color = mix(color, light_color, thread * 0.35 + broken_foam * 0.35);
color = mix(color, foam_color, base_foam * (0.38 + broken_foam * 0.42));
ALBEDO = color;
ROUGHNESS = mix(0.16, 0.32, broken_foam);
SPECULAR = 0.65;
EMISSION = light_color * (broken_foam * 0.07 + base_foam * 0.04);
ALPHA = edge_fade * mix(0.62, 0.9, thread * 0.56 + base_foam * 0.3);
ALPHA = edge_fade * mix(0.76, 0.95, thread * 0.56 + base_foam * 0.3);
}
+12 -5
View File
@@ -10,8 +10,11 @@ uniform float gust_speed : hint_range(0.0, 1.0) = 0.12;
uniform float gust_strength : hint_range(0.0, 0.8) = 0.35;
uniform float flutter_strength : hint_range(0.0, 0.05) = 0.012;
uniform float wind_phase : hint_range(0.0, 6.2832) = 0.0;
uniform float wind_base_height = -1.2;
uniform float wind_full_height = 1.2;
uniform vec3 albedo : source_color = vec3(0.19, 0.38, 0.17);
uniform float roughness : hint_range(0.0, 1.0) = 0.9;
uniform float painted_vertex_weight : hint_range(0.0, 1.0) = 0.0;
uniform vec3 contact_root = vec3(0.0);
uniform float contact_strength = 0.0;
uniform int interactor_count = 0;
@@ -20,8 +23,7 @@ varying vec3 paint_position;
void vertex() {
paint_position = VERTEX;
float half_height = 1.2;
float height_factor = clamp((VERTEX.y + half_height) / (half_height * 2.0), 0.0, 1.0);
float height_factor = clamp((VERTEX.y - wind_base_height) / max(wind_full_height - wind_base_height, 0.01), 0.0, 1.0);
height_factor = height_factor * height_factor;
vec2 direction = normalize(wind_direction);
@@ -32,8 +34,10 @@ void vertex() {
float bend = sin(TIME * wind_speed + world_phase) * wind_strength * gust;
float flutter = sin(TIME * 1.7 + world_phase * 1.9 + VERTEX.y * 2.0) * flutter_strength;
VERTEX.xz += direction * bend * height_factor;
VERTEX.xz += crosswind * flutter * height_factor;
// Crown orientation varies, but all foliage follows the same world breeze.
vec3 world_bend = vec3(direction.x, 0.0, direction.y) * bend;
world_bend += vec3(crosswind.x, 0.0, crosswind.y) * flutter;
VERTEX += (inverse(MODEL_MATRIX) * vec4(world_bend, 0.0)).xyz * height_factor;
if (foliage_trails_enabled && contact_strength > 0.0) {
// One bend for the whole bush, including fruit; its base remains planted.
vec3 contact = sample_foliage_trail(contact_root);
@@ -69,7 +73,10 @@ void vertex() {
void fragment() {
float patches = sin(paint_position.x * 6.0 + sin(paint_position.z * 7.0)) * sin(paint_position.y * 5.0);
float top = smoothstep(-0.65, 0.8, paint_position.y);
ALBEDO = albedo * mix(0.85, 1.17, top) * (1.0 + smoothstep(0.2, 0.4, patches) * 0.10);
vec3 pigment = mix(vec3(1.0), COLOR.rgb, painted_vertex_weight);
vec3 shade_tint = mix(vec3(0.77, 0.95, 1.01), vec3(1.10, 1.06, 0.87), top);
ALBEDO = albedo * pigment * shade_tint * mix(0.91, 1.13, top);
ALBEDO *= 1.0 + smoothstep(0.18, 0.65, patches) * 0.07;
ROUGHNESS = roughness;
SPECULAR = 0.0;
}