shader_type spatial; render_mode blend_mix, depth_draw_never; // --- Snow globals --- global uniform float global_snow_start_time = -1.0; global uniform float global_snow_accumulation_speed = 0.1; global uniform float global_snow_melt_time = -1.0; global uniform float global_snow_melt_speed = 0.1; global uniform float global_snow_threshold = 0.5; global uniform vec4 global_snow_color = vec4(0.92, 0.96, 1.0, 1.0); // --- Rain globals --- global uniform float global_rain_intensity; // --- Shared --- uniform sampler2D noise_texture : filter_linear_mipmap, repeat_enable; // --- Snow params --- uniform float snow_noise_scale : hint_range(0.01, 1.0) = 0.15; uniform float snow_edge_softness : hint_range(0.01, 0.5) = 0.2; uniform float snow_roughness_variation : hint_range(0.0, 0.3) = 0.15; uniform float snow_color_variation : hint_range(0.0, 0.15) = 0.05; // --- Rain params --- uniform float ripple_scale : hint_range(0.1, 5.0) = 1.5; uniform float ripple_speed : hint_range(0.5, 5.0) = 2.0; uniform float ripple_layers : hint_range(1.0, 4.0) = 3.0; uniform float streak_scale : hint_range(0.1, 5.0) = 2.0; uniform float streak_speed : hint_range(0.1, 3.0) = 0.8; uniform float wetness_darkening : hint_range(0.0, 0.5) = 0.25; uniform float wet_roughness : hint_range(0.0, 0.3) = 0.05; uniform float rain_normal_strength : hint_range(0.0, 1.0) = 0.4; float ripple_ring(vec2 uv, float time_offset) { float d = length(uv); float ring = sin(d * 30.0 - TIME * ripple_speed + time_offset) * 0.5 + 0.5; float fade = smoothstep(0.5, 0.0, d); return ring * fade; } void fragment() { vec3 world_pos = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz; vec3 world_normal = normalize((INV_VIEW_MATRIX * vec4(NORMAL, 0.0)).xyz); float facing_up = clamp(world_normal.y, 0.0, 1.0); float noise_val = texture(noise_texture, world_pos.xz * snow_noise_scale).r; // ===================== SNOW ===================== float snow_amount = 0.0; if (global_snow_start_time >= 0.0) { snow_amount = clamp((TIME - global_snow_start_time) * global_snow_accumulation_speed, 0.0, 1.0); } if (global_snow_melt_time >= 0.0) { float melt = clamp((TIME - global_snow_melt_time) * global_snow_melt_speed, 0.0, 1.0); snow_amount *= (1.0 - melt); } float snow_edge = smoothstep( global_snow_threshold - snow_edge_softness, global_snow_threshold + snow_edge_softness, facing_up + (noise_val - 0.5) * 0.4 ); float snow_coverage = smoothstep(0.0, 0.6, noise_val + snow_amount - 0.4); float snow_factor = snow_edge * snow_coverage * snow_amount; float shade = mix(-snow_color_variation, snow_color_variation, noise_val); vec3 snow_albedo = clamp(global_snow_color.rgb + vec3(shade), 0.0, 1.0); float snow_rough = mix(0.15 - snow_roughness_variation, 0.15 + snow_roughness_variation, noise_val); // ===================== RAIN ===================== float rain_int = clamp(global_rain_intensity, 0.0, 1.0); float rain_factor = 0.0; vec3 rain_albedo = vec3(0.0); float rain_rough = wet_roughness; float rain_metallic = 0.0; vec3 rain_normal = vec3(0.5, 0.5, 1.0); if (rain_int > 0.01 && facing_up > 0.3) { float surface_mask = smoothstep(0.3, 0.6, facing_up); // Slope detection: 0 = flat, 1 = peak at ~30-45 degrees float slope_angle = 1.0 - facing_up; float slope_mask = smoothstep(0.1, 0.25, slope_angle) * smoothstep(0.75, 0.5, slope_angle); // Ripples on horizontal surfaces float ripple = 0.0; for (float i = 0.0; i < ripple_layers; i += 1.0) { float phase = i * 2.17; vec2 cell_offset = vec2( texture(noise_texture, world_pos.xz * ripple_scale * 0.3 + vec2(phase)).r, texture(noise_texture, world_pos.xz * ripple_scale * 0.3 + vec2(0.0, phase)).r ); vec2 ripple_uv = fract(world_pos.xz * ripple_scale + cell_offset) - 0.5; ripple += ripple_ring(ripple_uv, phase * 3.7); } ripple /= ripple_layers; // Flowing water on inclined surfaces (30-45 degrees) vec3 down_dir = normalize(world_normal - vec3(0.0, 1.0, 0.0) * facing_up); float flow_speed = streak_speed * (1.0 + slope_angle * 2.0); // Main flow rivulets float flow_coord = dot(world_pos, down_dir); float lateral_coord = dot(world_pos.xz, vec2(-down_dir.z, down_dir.x)) * streak_scale; vec2 flow_uv = vec2(lateral_coord, flow_coord * streak_scale - TIME * flow_speed); float flow_noise = texture(noise_texture, flow_uv * 0.4).r; float flow2 = texture(noise_texture, flow_uv * 0.7 + vec2(0.3, TIME * flow_speed * 0.3)).r; float rivulets = smoothstep(0.3, 0.7, flow_noise) * smoothstep(0.25, 0.6, flow2); // Accumulation at bottom (lower world_pos.y gets more water) float height_factor = texture(noise_texture, world_pos.xz * 0.05).r; float accumulation = smoothstep(0.6, 0.2, fract(world_pos.y * 0.3 + height_factor * 0.5)); rivulets = mix(rivulets, rivulets + accumulation * 0.3, slope_mask); float streak = rivulets * slope_mask; // Blend ripples (flat) and streaks (inclined) float flat_weight = smoothstep(0.3, 0.15, slope_angle); float effect = mix(streak, ripple, flat_weight) * rain_int * surface_mask; float darkness = wetness_darkening * rain_int * surface_mask; vec2 normal_offset = vec2(dFdx(effect), dFdy(effect)) * rain_normal_strength; rain_normal = vec3(normal_offset.x + 0.5, normal_offset.y + 0.5, 1.0); rain_albedo = vec3(0.02, 0.02, 0.03); rain_metallic = 0.3 * rain_int; rain_factor = effect * 0.8 + darkness; } // ===================== COMBINE ===================== if (snow_factor > rain_factor) { ALBEDO = snow_albedo; ROUGHNESS = snow_rough; METALLIC = 0.0; ALPHA = snow_factor; } else { ALBEDO = rain_albedo; ROUGHNESS = rain_rough; METALLIC = rain_metallic; NORMAL_MAP = rain_normal; ALPHA = rain_factor; } }