OverviewFilesCommitsIssuesDocsReleasesPeers

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

321 lines · gdshader ref:
// Main cloud layer shader - curved plane with volumetric light marching
shader_type spatial;
render_mode blend_mix, depth_draw_never, cull_front, unshaded;

#include "noise.gdshaderinc"

// --- Vertex curving ---
uniform float curve_strength : hint_range(0.0, 0.01) = 0.003;
uniform float plane_half_size : hint_range(100.0, 100000.0) = 50000.0;

// --- Weather system ---
uniform float weather : hint_range(0.0, 1.0) = 0.3;
uniform float coverage : hint_range(0.0, 1.0) = 0.35;
uniform float bottom_softness : hint_range(0.01, 1.0) = 0.35;
uniform float top_softness : hint_range(0.01, 0.5) = 0.08;

// --- Noise parameters ---
uniform int octaves : hint_range(3, 6) = 5;
uniform float persistence : hint_range(0.2, 0.6) = 0.5;
uniform float lacunarity : hint_range(1.5, 3.0) = 2.02;
uniform float domain_warp : hint_range(0.0, 1.0) = 0.3;
uniform float worley_blend : hint_range(0.0, 1.0) = 0.35;
uniform int worley_start_octave : hint_range(0, 6) = 3;

// --- Wind animation ---
uniform vec2 wind_offset = vec2(0.0);
uniform float cloud_scale : hint_range(0.1, 10.0) = 3.0;

// --- Flow distortion (procedural curl-noise flow field) ---
uniform float flow_strength : hint_range(0.0, 1.0) = 0.25;
uniform float flow_scale : hint_range(0.1, 5.0) = 1.5;

// --- Parallax mapping (volumetric depth illusion on flat plane) ---
uniform bool parallax_enabled = false;
uniform float parallax_depth : hint_range(0.0, 0.3) = 0.1;
uniform int parallax_layers : hint_range(4, 24) = 12;

// --- Height gradient (Nubis constants) ---
uniform vec4 height_gradient = vec4(0.01, 0.0625, 0.78, 1.0);

// --- Special cloud types ---
uniform bool altocumulus_enabled = false;
uniform bool mammatus_enabled = false;
uniform bool lenticular_enabled = false;
uniform vec2 lenticular_center = vec2(0.5, 0.5);
uniform vec2 lenticular_size = vec2(0.15, 0.04);

// --- Lighting ---
uniform vec3 sun_direction = vec3(0.0, 1.0, 0.0);
uniform vec3 sun_color : source_color = vec3(1.0);
uniform float sun_energy = 1.0;
uniform vec3 moon_direction = vec3(0.0, -1.0, 0.0);
uniform float moon_energy = 0.0;

// --- Light march settings ---
uniform int light_steps : hint_range(2, 8) = 4;
uniform float light_step_size : hint_range(0.01, 0.1) = 0.035;
uniform float shadow_density : hint_range(0.5, 4.0) = 1.8;

// Cheap 3-octave noise for light marching (no domain warp)
float march_noise(vec2 uv) {
	float total = 0.0;
	float amp = 1.0;
	float freq = 1.0;
	float max_amp = 0.0;
	for (int i = 0; i < 3; i++) {
		total += amp * value_noise(uv * freq);
		max_amp += amp;
		amp *= 0.5;
		freq *= 2.02;
	}
	return total / max_amp;
}

// Full fBM with domain warp and worley blending
float cloud_fbm(vec2 uv) {
	vec2 q = uv;
	float warp_x = value_noise(q);
	float warp_y = value_noise(q + vec2(5.2, 1.3));
	q += domain_warp * vec2(warp_x, warp_y);

	float total = 0.0;
	float amplitude = 1.0;
	float frequency = 1.0;
	float max_amp = 0.0;

	for (int i = 0; i < octaves; i++) {
		vec2 octave_wind = wind_offset * (1.0 + float(i) * 0.3);
		vec2 p = (q + octave_wind) * frequency;
		float val = value_noise(p);
		if (i >= worley_start_octave) {
			float w = worley_noise(p);
			val = mix(val, 1.0 - w, worley_blend);
		}
		total += val * amplitude;
		max_amp += amplitude;
		amplitude *= persistence;
		frequency *= lacunarity;
	}
	return total / max_amp;
}

// Height gradient shaping
float apply_height_gradient(float density, vec4 grad) {
	float bottom = smoothstep(grad.x, grad.y, density);
	float top = 1.0 - smoothstep(grad.z, grad.w, density);
	return bottom * top;
}

// Special cloud type functions
float altocumulus_density(vec2 uv) {
	float cell = 1.0 - voronoi_noise(uv * 10.0);
	float perturb = value_noise(uv * 20.0) * 0.15;
	return clamp(cell + perturb, 0.0, 1.0);
}

float mammatus_density(vec2 uv) {
	float w = 1.0 - worley_noise(uv * 6.0);
	return pow(w, 2.0);
}

float lenticular_density(vec2 uv) {
	vec2 delta = (uv - lenticular_center) / lenticular_size;
	return 1.0 - smoothstep(0.8, 1.0, length(delta));
}

void vertex() {
	float dist_from_center = length(VERTEX.xz) / plane_half_size;
	float curve = dist_from_center * dist_from_center * curve_strength * plane_half_size;
	VERTEX.y -= curve;
}

void fragment() {
	// --- Flow distortion (2D curl noise) ---
	// Curl of a scalar noise potential = divergence-free flow field.
	// Produces smooth swirling without compression artifacts.
	// wind_offset * 0.3 slowly evolves the turbulence pattern.
	vec2 flow_pos = UV * flow_scale + wind_offset * 0.3;
	float curl_eps = 0.01;
	float dNdx = value_noise(flow_pos + vec2(curl_eps, 0.0))
		- value_noise(flow_pos - vec2(curl_eps, 0.0));
	float dNdy = value_noise(flow_pos + vec2(0.0, curl_eps))
		- value_noise(flow_pos - vec2(0.0, curl_eps));
	vec2 flow = vec2(dNdy, -dNdx) / (2.0 * curl_eps) * flow_strength;

	// UV in world-scale with flow distortion applied
	vec2 uv = (UV + wind_offset + flow) * cloud_scale;

	// --- Parallax occlusion mapping ---
	// March view ray through noise heightfield for volumetric depth illusion.
	// Uses march_noise as depth map (1.0 - noise = depth below surface).
	if (parallax_enabled) {
		// Tangent-space view direction (Godot convention: negate BINORMAL)
		vec3 n = FRONT_FACING ? NORMAL : -NORMAL;
		vec3 view_dir = normalize(normalize(-VERTEX) * mat3(TANGENT, -BINORMAL, n));

		// Adaptive layer count: more at grazing angles
		float num_layers_f = mix(float(parallax_layers),
			float(parallax_layers / 2), abs(view_dir.z));
		num_layers_f = max(num_layers_f, 4.0);
		float layer_depth = 1.0 / num_layers_f;
		vec2 delta_uv = view_dir.xy / max(abs(view_dir.z), 0.001)
			* parallax_depth / num_layers_f;

		// Steep parallax march
		float current_layer_depth = 0.0;
		vec2 current_uv = uv;
		float depth_value = 1.0 - march_noise(current_uv);
		for (int i = 0; i < parallax_layers; i++) {
			if (current_layer_depth >= depth_value) break;
			current_uv -= delta_uv;
			current_layer_depth += layer_depth;
			depth_value = 1.0 - march_noise(current_uv);
		}

		// POM refinement: interpolate between last two samples
		vec2 prev_uv = current_uv + delta_uv;
		float after = depth_value - current_layer_depth;
		float before = (1.0 - march_noise(prev_uv))
			- current_layer_depth + layer_depth;
		float weight = after / (after - before + 0.0001);
		uv = mix(current_uv, prev_uv, weight);
	}

	// --- Base cloud density ---
	float base_density = cloud_fbm(uv);
	// Bottom softness: soft fade at low density (prevents hard floor from below)
	// Top softness: crisp edge definition at high density
	float threshold = 1.0 - coverage;
	float density = smoothstep(threshold - bottom_softness, threshold + top_softness, base_density);
	density = apply_height_gradient(density, height_gradient);

	// --- Special cloud type overlays ---
	if (altocumulus_enabled) {
		float alto = smoothstep(0.3, 0.7, altocumulus_density(uv));
		density = max(density, alto * 0.7);
	}
	if (mammatus_enabled) {
		float mam = smoothstep(0.2, 0.6, mammatus_density(uv));
		density = max(density, mam * 0.6);
	}
	if (lenticular_enabled) {
		density = max(density, lenticular_density(uv) * 0.85);
	}

	// --- Normal from density gradient (cheap: uses march_noise instead of full fBM) ---
	// march_noise is 3 octaves vs cloud_fbm's 5 + domain warp, saving ~60% shader cost
	float eps = 0.01;
	float dx = march_noise(uv + vec2(eps, 0.0));
	float dy = march_noise(uv + vec2(0.0, eps));
	float center = march_noise(uv);
	vec3 cloud_normal = normalize(vec3(
		(center - dx) * 4.0,
		1.0,
		(center - dy) * 4.0
	));

	// --- Volumetric light march (sun) ---
	// March toward the sun through the density field to compute self-shadow
	vec2 sun_step = sun_direction.xz * light_step_size;
	float optical_depth = 0.0;
	for (int s = 1; s <= light_steps; s++) {
		vec2 march_uv = uv + sun_step * float(s);
		float sample_density = march_noise(march_uv);
		// Apply same coverage threshold for consistency
		sample_density = smoothstep(1.0 - coverage - 0.1, 1.0 - coverage + 0.1, sample_density);
		optical_depth += sample_density;
	}
	// Beer's law: transmittance decreases exponentially with optical depth
	float sun_transmittance = exp(-optical_depth * shadow_density);

	// --- Sun lighting ---
	// Wrapped diffuse: light wraps around the cloud instead of hard binary cutoff
	float wrap = 0.5;
	float ndotl_wrap = max(0.0, (dot(cloud_normal, sun_direction) + wrap) / (1.0 + wrap));

	// Height gradient for base color (dark bottom, bright top)
	float height_frac = apply_height_gradient(base_density, height_gradient);
	vec3 cloud_dark = vec3(0.2, 0.22, 0.27);
	vec3 cloud_bright = vec3(1.0, 0.98, 0.95);
	vec3 base_color = mix(cloud_dark, cloud_bright, height_frac);

	// Ambient occlusion from density
	float ao = 1.0 - smoothstep(0.3, 0.9, density) * 0.35;

	// Silver lining rim light
	float view_sun_dot = dot(normalize(-VIEW), sun_direction);
	float rim = smoothstep(0.0, 0.2, 1.0 - density) * max(view_sun_dot, 0.0);

	// Henyey-Greenstein forward scattering (clamped to prevent blow-out looking at sun)
	float g = 0.76;
	float hg_denom = max(1.0 + g * g - 2.0 * g * view_sun_dot, 0.01);
	float hg_sun = min((1.0 - g * g) / (4.0 * PI * pow(hg_denom, 1.5)), 2.0);

	// Powder effect BEFORE ndotl — applies to whole cloud, not just lit side
	float powder = mix(1.0, 1.0 - exp(-density * 6.0), 0.7);

	// Multi-scattering approximation: light bounces inside cloud
	// Denser regions scatter more internally, preventing pure black shadows
	float multi_scatter = 0.15 * (1.0 - density * 0.6);

	// --- Sun contribution (scaled by sun_energy for proper day/night) ---
	vec3 sun_lit = base_color * ndotl_wrap * sun_transmittance * sun_color * ao * powder * sun_energy;
	sun_lit += base_color * multi_scatter * sun_color * sun_energy;

	// Softer rim: scale by density falloff, not flat 2.5
	float rim_strength = 1.2;
	sun_lit += sun_color * rim * rim_strength * powder * sun_energy;

	// Forward scattering contribution (reduced)
	sun_lit *= (1.0 + hg_sun * 0.25);

	// --- Moon lighting (volumetric march toward moon) ---
	// moon_energy comes from the DirectionalLight which is already faded by
	// altitude/phase/curve, so boost it for cloud illumination visibility
	float moon_boost = moon_energy * 5.0;  // Boost so moonlit clouds are visible
	vec2 moon_step = moon_direction.xz * light_step_size;
	float moon_optical_depth = 0.0;
	if (moon_boost > 0.01) {
		for (int s = 1; s <= light_steps; s++) {
			vec2 march_uv = uv + moon_step * float(s);
			float sample_density = march_noise(march_uv);
			sample_density = smoothstep(1.0 - coverage - 0.1, 1.0 - coverage + 0.1, sample_density);
			moon_optical_depth += sample_density;
		}
	}
	float moon_transmittance = exp(-moon_optical_depth * shadow_density);
	float moon_ndotl = max(0.0, (dot(cloud_normal, moon_direction) + wrap) / (1.0 + wrap));
	float view_moon_dot = dot(normalize(-VIEW), moon_direction);
	float hg_moon_denom = max(1.0 + g * g - 2.0 * g * view_moon_dot, 0.01);
	float hg_moon = min((1.0 - g * g) / (4.0 * PI * pow(hg_moon_denom, 1.5)), 2.0);
	vec3 moon_color = vec3(0.7, 0.8, 0.95) * moon_boost;
	vec3 moon_lit = base_color * moon_ndotl * moon_transmittance * moon_color * ao * powder;
	moon_lit += base_color * multi_scatter * moon_color * 0.5;
	moon_lit *= (1.0 + hg_moon * 0.3);

	// --- Ambient ---
	// Day: blueish sky ambient. Night: dark blue-grey ambient.
	float night_blend = clamp(1.0 - sun_direction.y * 3.0, 0.0, 1.0);
	vec3 day_ambient = vec3(0.35, 0.4, 0.5) * 0.3;
	vec3 night_ambient = vec3(0.04, 0.045, 0.07);
	vec3 ambient = base_color * mix(day_ambient, night_ambient, night_blend) * ao;

	// --- Combine all lighting ---
	// Sun and moon are additive. Sun naturally fades via sun_energy at night,
	// moon naturally fades via moon_energy during day. No manual night tint needed.
	vec3 final_color = sun_lit + moon_lit + ambient;

	// --- Edge fade ---
	// Start fading earlier (0.7) for smooth horizon blending — prevents visible plane boundary
	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;

	// --- Output ---
	float final_alpha = density * 0.85;
	if (final_alpha < 0.01) discard;
	ALBEDO = final_color;
	ALPHA = final_alpha;
}