// Ground-level shadow plane — samples the SAME 3D noise textures as the sky
// shader so shadows match overhead volumetric cloud shapes exactly.
shader_type spatial;
render_mode blend_mix, depth_draw_never, cull_back, unshaded;
uniform sampler3D cloud_shape_noise : filter_linear, repeat_enable;
uniform sampler3D cloud_detail_noise : filter_linear, repeat_enable;
uniform vec2 cloud_wind_offset = vec2(0.0);
uniform float cloud_coverage : hint_range(0.0, 1.0) = 0.4;
uniform float cloud_density : hint_range(0.1, 5.0) = 0.6;
uniform float cloud_scale : hint_range(0.1, 5.0) = 1.0;
uniform float cloud_base_altitude = 1500.0;
uniform float cloud_top_altitude = 3500.0;
uniform vec4 cloud_height_gradient = vec4(0.0, 0.1, 0.7, 1.0);
uniform float shadow_darkness : hint_range(0.0, 1.0) = 0.7;
uniform vec3 sun_direction = vec3(0.0, 1.0, 0.0);
// Mirrors sample_cloud_density() from SkyMaterial.gdshader exactly.
float sample_cloud_density_at(vec3 world_pos, float height_frac) {
vec3 wind = vec3(cloud_wind_offset.x, 0.0, cloud_wind_offset.y);
vec3 noise_pos = (world_pos + wind) * cloud_scale * 0.0005;
float shape = texture(cloud_shape_noise, noise_pos).r;
// Nubis height gradient
float hgrad = smoothstep(cloud_height_gradient.x, cloud_height_gradient.y, height_frac)
* (1.0 - smoothstep(cloud_height_gradient.z, cloud_height_gradient.w, height_frac));
float d = shape * hgrad;
float cov_edge = 1.0 - cloud_coverage;
d = smoothstep(cov_edge, cov_edge + 0.1, d);
// Detail erosion
if (d > 0.01) {
float detail = texture(cloud_detail_noise, noise_pos * 6.0).r;
d -= detail * 0.2 * (1.0 - height_frac);
d = max(d, 0.0);
}
return d * cloud_density;
}
void fragment() {
// Reconstruct world XZ from the shadow plane mesh.
// The plane is 10000x10000, centered on camera — UV maps to world position.
float plane_size = 10000.0;
// plane_center.xz = camera position (mesh follows camera in ShadowLayer.update)
// NODE_POSITION_WORLD gives the mesh's global position
vec2 world_xz = NODE_POSITION_WORLD.xz + (UV - 0.5) * plane_size;
// Project to cloud layer along sun direction
float sun_y = max(sun_direction.y, 0.05);
float mid_altitude = (cloud_base_altitude + cloud_top_altitude) * 0.5;
vec2 cloud_xz = world_xz + sun_direction.xz * (mid_altitude / sun_y);
// Sample at mid-cloud height as proxy for column optical depth
vec3 sample_pos = vec3(cloud_xz.x, mid_altitude, cloud_xz.y);
float density = sample_cloud_density_at(sample_pos, 0.5);
// Edge fade to avoid hard cutoff at plane boundaries
float edge_dist = max(abs(UV.x - 0.5), abs(UV.y - 0.5)) * 2.0;
float edge_fade = 1.0 - smoothstep(0.7, 0.98, edge_dist);
density *= edge_fade;
// Fade shadows when sun is below horizon
float sun_fade = clamp(sun_direction.y * 4.0, 0.0, 1.0);
float shadow_alpha = density * shadow_darkness * sun_fade;
if (shadow_alpha < 0.01) discard;
ALBEDO = vec3(0.0);
ALPHA = shadow_alpha;
}