shader_type spatial; render_mode blend_mix, cull_back, depth_draw_opaque; global uniform float global_snow_start_time = -1.0; global uniform float global_snow_accumulation_speed = 0.005; global uniform float global_snow_melt_time = -1.0; global uniform float global_snow_melt_speed = 0.1; global uniform float global_snow_amount = 0.0; global uniform vec4 global_snow_color = vec4(0.92, 0.96, 1.0, 1.0); uniform float max_height : hint_range(0.02, 1.0) = 0.2; uniform float half_width : hint_range(0.01, 20.0) = 1.0; uniform float half_depth : hint_range(0.01, 20.0) = 1.0; uniform float noise_scale : hint_range(0.05, 2.0) = 0.35; uniform float noise_strength : hint_range(0.0, 0.4) = 0.18; uniform float edge_rounding : hint_range(0.0, 0.3) = 0.12; uniform float side_shade : hint_range(0.2, 1.0) = 0.82; uniform float sparkle_strength : hint_range(0.0, 0.1) = 0.03; varying float v_snow_amount; varying float v_noise; float hash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123); } float value_noise(vec2 p) { vec2 i = floor(p); vec2 f = fract(p); vec2 u = f * f * (3.0 - 2.0 * f); float a = hash(i); float b = hash(i + vec2(1.0, 0.0)); float c = hash(i + vec2(0.0, 1.0)); float d = hash(i + vec2(1.0, 1.0)); return mix(mix(a, b, u.x), mix(c, d, u.x), u.y); } float fbm(vec2 p) { float value = 0.0; float amplitude = 0.5; float frequency = 1.0; for (int i = 0; i < 4; i++) { value += value_noise(p * frequency) * amplitude; frequency *= 2.0; amplitude *= 0.5; } return clamp(value, 0.0, 1.0); } float get_snow_amount() { float snow_amount = clamp(global_snow_amount, 0.0, 1.0); if (global_snow_start_time >= 0.0) { float timed_amount = clamp( (TIME - global_snow_start_time) * global_snow_accumulation_speed, 0.0, 1.0 ); snow_amount = max(snow_amount, timed_amount); } 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 = min(snow_amount, 1.0 - melt); } return snow_amount; } void vertex() { vec3 base_vertex = VERTEX; vec3 world_pos = (MODEL_MATRIX * vec4(base_vertex, 1.0)).xyz; float snow_amount = get_snow_amount(); float noise_val = fbm(world_pos.xz * noise_scale); float thickness = max_height * snow_amount * mix( 1.0 - noise_strength, 1.0 + noise_strength, noise_val ); float footprint_growth = smoothstep(0.0, 0.35, snow_amount); float from_bottom = clamp((base_vertex.y + max_height * 0.5) / max_height, 0.0, 1.0); VERTEX.y = -max_height * 0.5 + thickness * from_bottom; VERTEX.x *= mix(0.92, 1.0, footprint_growth); VERTEX.z *= mix(0.94, 1.0, footprint_growth); float edge_x = abs(base_vertex.x) / max(half_width, 0.001); float edge_z = abs(base_vertex.z) / max(half_depth, 0.001); float edge_mask = smoothstep(0.55, 1.0, max(edge_x, edge_z)); float top_mask = smoothstep(0.6, 1.0, from_bottom) * snow_amount; float round_factor = edge_rounding * edge_mask * top_mask; VERTEX.x *= 1.0 - round_factor; VERTEX.z *= 1.0 - round_factor; v_snow_amount = snow_amount; v_noise = noise_val; } void fragment() { if (v_snow_amount <= 0.001) { ALPHA = 0.0; ALBEDO = global_snow_color.rgb; ROUGHNESS = 1.0; SPECULAR = 0.0; } else { float facing_up = clamp(NORMAL.y, 0.0, 1.0); float shade = mix(-sparkle_strength, sparkle_strength, v_noise); vec3 snow_albedo = clamp(global_snow_color.rgb + vec3(shade), 0.0, 1.0); ALBEDO = mix(snow_albedo * side_shade, snow_albedo, facing_up); ROUGHNESS = mix(0.95, 0.72, facing_up); SPECULAR = 0.18; ALPHA = smoothstep(0.0, 0.28, v_snow_amount); } }