OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / addons / sky3d_extended / shaders / cloud_shadow_ground.gdshader

71 lines · gdshader ref:
// 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;
}