OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / addons / nubis_clouds / shaders / cloud_compositor.glsl

375 lines · glsl ref:
#[compute]
#version 450

#define MAX_VIEWS 2

#include "godot/scene_data_inc.glsl"

layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;

layout(set = 0, binding = 0, std140) uniform SceneDataBlock {
	SceneData data;
	SceneData prev_data;
} scene_data_block;

layout(rgba16f, set = 0, binding = 1) uniform image2D color_image;
layout(set = 0, binding = 2) uniform sampler2D depth_texture;

// Cloud volume data
layout(set = 1, binding = 0) uniform sampler3D cloud_sdf;
layout(set = 1, binding = 1) uniform sampler3D perlworlnoise;

layout(push_constant, std430) uniform Params {
	vec2 raster_size;
	float view;
	float density_scale;
	vec4 volume_pos;    // xyz = world center, w = wind_time
	vec4 volume_size;   // xyz = half extents, w = erosion_strength
	vec4 sun_dir;       // xyz = direction toward sun, w = noise_freq
	vec4 sun_color;     // rgb = sun light color, a = sun energy
	vec4 moon_dir;      // xyz = direction toward moon, w = debug_mode (0=normal)
	vec4 moon_color;    // rgb = moon light color, a = moon energy
} params;

// Ray-box intersection in world space
vec2 intersect_aabb(vec3 ro, vec3 rd, vec3 box_min, vec3 box_max) {
	vec3 inv_rd = 1.0 / rd;
	vec3 t0 = (box_min - ro) * inv_rd;
	vec3 t1 = (box_max - ro) * inv_rd;
	vec3 tmin = min(t0, t1);
	vec3 tmax = max(t0, t1);
	float near = max(max(tmin.x, tmin.y), tmin.z);
	float far = min(min(tmax.x, tmax.y), tmax.z);
	return vec2(max(near, 0.0), far);
}

float beer_powder(float d) {
	return 2.0 * exp(-d) * (1.0 - exp(-2.0 * d));
}

float hg_phase(float cos_theta, float g) {
	float g2 = g * g;
	return (1.0 - g2) / (4.0 * 3.14159265 * pow(1.0 + g2 - 2.0 * g * cos_theta, 1.5));
}

// Atmospheric scatter — same coefficients as Sunshine for exact color matching
vec4 compute_atmospheric_scatter(vec3 ray_dir, vec3 sun_dir, vec3 sun_color, float cloud_dist) {
	const float ATM_HEIGHT = 40000.0;
	const vec3 RAYLEIGH_COEF = vec3(5.5e-6, 13.0e-6, 22.4e-6);
	const float RAYLEIGH_SCALE = 8e3;
	const float MIE_COEF = 21e-6;
	const float MIE_SCALE = 1.2e3;
	const float MIE_DIR = 0.758;

	float mu = dot(ray_dir, sun_dir);
	float mumu = mu * mu;
	float gg = MIE_DIR * MIE_DIR;
	float p_rlh = 3.0 / (16.0 * 3.14159265) * (1.0 + mumu);
	float p_mie = 3.0 / (8.0 * 3.14159265) * ((1.0 - gg) * (mumu + 1.0))
		/ (pow(1.0 + gg - 2.0 * mu * MIE_DIR, 1.5) * (2.0 + gg));

	// Simplified: 10 steps along ray to accumulate scatter
	vec3 total_rlh = vec3(0.0);
	vec3 total_mie = vec3(0.0);
	float od_rlh = 0.0;
	float od_mie = 0.0;
	float step_dist = cloud_dist / 10.0;

	for (int i = 0; i < 10; i++) {
		float t = step_dist * float(i + 1);
		float h = max(t * 0.01, 0.0); // approximate height fraction
		float od_step_rlh = exp(-h / RAYLEIGH_SCALE) * step_dist;
		float od_step_mie = exp(-h / MIE_SCALE) * step_dist;
		od_rlh += od_step_rlh;
		od_mie += od_step_mie;
		vec3 attn = exp(-(MIE_COEF * (od_mie + MIE_SCALE) + RAYLEIGH_COEF * (od_rlh + RAYLEIGH_SCALE)));
		total_rlh += od_step_rlh * attn;
		total_mie += od_step_mie * attn;
	}

	float atm_power = length(RAYLEIGH_COEF * total_rlh + MIE_COEF * total_mie);
	vec3 atm_color = 22.0 * (vec3(0.1, 0.12, 0.18) * RAYLEIGH_COEF * total_rlh
		+ p_mie * MIE_COEF * sun_color * total_mie) / 10.0;
	return vec4(atm_color, atm_power);
}

void main() {
	ivec2 uv = ivec2(gl_GlobalInvocationID.xy);
	ivec2 size = ivec2(params.raster_size);
	int view = int(params.view);

	if (uv.x >= size.x || uv.y >= size.y) return;

	// --- Reconstruct world-space ray from SceneData ---
	vec2 uv_norm = (vec2(uv) + 0.5) / params.raster_size;
	vec2 ndc = uv_norm * 2.0 - 1.0;

	vec4 view_pos_near = scene_data_block.data.inv_projection_matrix * vec4(ndc, 1.0, 1.0);
	vec4 view_pos_far = scene_data_block.data.inv_projection_matrix * vec4(ndc, 0.0, 1.0);
	vec3 view_near = view_pos_near.xyz / view_pos_near.w;
	vec3 view_far = view_pos_far.xyz / view_pos_far.w;

	// Canonical mat3x4 → mat4 reconstruction (scene_forward_clustered.glsl:235-238)
	mat3x4 ivm = scene_data_block.data.inv_view_matrix;
	mat4 inv_view = transpose(mat4(ivm[0], ivm[1], ivm[2], vec4(0.0, 0.0, 0.0, 1.0)));
	vec3 cam_pos = inv_view[3].xyz;
	mat3 inv_view_rot = mat3(inv_view);

	vec3 view_dir = normalize(view_far - view_near);
	vec3 rd = normalize(inv_view_rot * view_dir);

	// --- Scene depth ---
	float raw_depth = texture(depth_texture, uv_norm).r;
	float scene_dist = 1e20;
	if (raw_depth > 1e-7) {
		vec4 scene_clip = vec4(ndc, raw_depth, 1.0);
		vec4 scene_view_pos = scene_data_block.data.inv_projection_matrix * scene_clip;
		vec3 sv = scene_view_pos.xyz / scene_view_pos.w;
		vec3 scene_world = (inv_view * vec4(sv, 1.0)).xyz;
		scene_dist = length(scene_world - cam_pos);
	}

	// --- Intersect ray with cloud volume AABB ---
	vec3 box_min = params.volume_pos.xyz - params.volume_size.xyz;
	vec3 box_max = params.volume_pos.xyz + params.volume_size.xyz;
	vec2 t = intersect_aabb(cam_pos, rd, box_min, box_max);

	if (t.x >= t.y || t.y <= 0.0) return;

	float max_dist = min(t.y, scene_dist);
	if (t.x >= max_dist) return;

	// --- Ray march through cloud volume ---
	vec3 sun_dir = params.sun_dir.xyz;
	vec3 sun_col = params.sun_color.rgb * params.sun_color.a;
	vec3 moon_dir = params.moon_dir.xyz;
	vec3 moon_col = params.moon_color.rgb * params.moon_color.a;
	float moon_energy = max(max(moon_col.r, moon_col.g), moon_col.b);
	bool has_moon = moon_energy > 0.001;

	float cos_sun = dot(rd, sun_dir);
	float cos_moon = dot(rd, moon_dir);
	float sun_phase = max(hg_phase(cos_sun, 0.7), hg_phase(cos_sun, -0.2));
	float moon_phase = max(hg_phase(cos_moon, 0.7), hg_phase(cos_moon, -0.2));
	float wind_time = params.volume_pos.w;

	float transmittance = 1.0;
	vec3 light_energy = vec3(0.0);
	int debug_mode = int(params.moon_dir.w);

	// Pre-compute atmospheric tint for per-step coloring (Fix 2)
	float cloud_mid_dist = (t.x + t.y) * 0.5;
	float sun_up_atm = smoothstep(0.0, 0.4, sun_dir.y);
	float sun_power_atm = params.sun_color.a * sun_up_atm;
	vec4 atm_tint = compute_atmospheric_scatter(rd, sun_dir, sun_col * sun_power_atm, cloud_mid_dist);
	vec3 atm_color = atm_tint.rgb;
	// Debug accumulators — weighted average over density samples
	float dbg_sun_optical = 0.0;
	float dbg_sun_beer = 0.0;
	float dbg_sdf = 0.0;
	float dbg_density = 0.0;
	float dbg_sun_bp = 0.0;
	float dbg_weight = 0.0;
	float ray_length = max_dist - t.x;
	// Scale steps by ray length: ~10m per step, clamped 32-192
	int steps = clamp(int(ray_length / 10.0), 32, 192);
	float step_size = ray_length / float(steps);

	// Jitter ray start to reduce banding
	float jitter = fract(sin(dot(vec2(uv), vec2(12.9898, 78.233))) * 43758.5453);

	for (int i = 0; i < steps; i++) {
		if (transmittance < 0.01) break;

		float tc = t.x + (float(i) + jitter) * step_size;
		vec3 pos = cam_pos + rd * tc;

		// Map world position to volume UVW [0,1]
		vec3 uvw = (pos - box_min) / (box_max - box_min);

		// Drift the SDF sample slowly — cloud structure morphs over time
		vec3 sdf_drift = vec3(wind_time * 2.0, 0.0, wind_time * 0.7);
		vec3 sdf_uvw = uvw + sdf_drift / (box_max - box_min);

		// Sample dimensional profile
		float sdf = texture(cloud_sdf, sdf_uvw).r;
		if (sdf <= 0.001) continue;

		// Negative-space noise: invisible field that carves dissolving channels
		// Samples at very low frequency so it creates large-scale gaps that drift
		vec3 neg_pos = pos * 0.001 + vec3(wind_time * 1.5, wind_time * 0.3, wind_time * 0.8);
		float neg_noise = textureLod(perlworlnoise, neg_pos, 0.0).b;
		// Where neg_noise is low (<0.3), carve a hole in the cloud
		float neg_mask = smoothstep(0.15, 0.35, neg_noise);
		sdf *= neg_mask;
		if (sdf <= 0.001) continue;

		// Flat bottom (uses original uvw)
		float height_frac = uvw.y;
		float bottom_fade = smoothstep(0.15, 0.35, height_frac);
		sdf *= bottom_fade;
		if (sdf <= 0.001) continue;

		// --- DENSITY FROM NOISE ---
		vec3 wind_offset = vec3(wind_time * 10.0, 0.0, wind_time * 3.0);
		vec3 sample_pos = pos - wind_offset;

		// Curl turbulence: distort sample position for organic swirls
		float freq = params.sun_dir.w;  // noise_freq from inspector
		float strength = params.volume_size.w;  // erosion_strength from inspector
		vec4 turb = textureLod(perlworlnoise, sample_pos * freq * 0.5, 0.0);
		vec3 curl = (turb.rgb - 0.5) * 2.0;
		sample_pos += curl * (20.0 / max(freq, 0.001));

		// Three octaves at freq, freq*3, freq*8
		vec4 n1 = textureLod(perlworlnoise, sample_pos * freq, 0.0);
		vec4 n2 = textureLod(perlworlnoise, sample_pos * freq * 3.0, 0.0);
		vec4 n3 = textureLod(perlworlnoise, sample_pos * freq * 8.0, 0.0);

		// FBM blend: large shape + medium bumps + fine detail
		float n_raw = n1.a * 0.55 + n2.a * 0.30 + n3.a * 0.15;
		float n = clamp((n_raw - 0.07) / 0.93, 0.0, 1.0);
		n = pow(n, 1.3);

		// Erode sphere: noise pushes boundary inward
		float erosion_factor = strength * (1.0 - sdf * 0.4);
		float erosion = (1.0 - n) * erosion_factor;
		float density = max(sdf - erosion, 0.0) * params.density_scale;

		// Edge fade at box boundaries
		vec3 edge = smoothstep(vec3(0.0), vec3(0.06), uvw) * smoothstep(vec3(0.0), vec3(0.06), vec3(1.0) - uvw);
		density *= edge.x * edge.y * edge.z;

		if (density < 0.001) continue;

		// Light march toward sun (6 steps)
		// Fix 3: sample noise-eroded density, not raw SDF
		float sun_od = 0.0;
		float l_step = 0.04 * length(box_max - box_min);
		float l_freq = params.sun_dir.w;
		float l_strength = params.volume_size.w;
		vec3 lp = pos;
		for (int j = 0; j < 6; j++) {
			lp += sun_dir * l_step;
			vec3 l_uvw = (lp - box_min) / (box_max - box_min);
			if (any(lessThan(l_uvw, vec3(0))) || any(greaterThan(l_uvw, vec3(1)))) break;
			// Sample full density (SDF + noise erosion) instead of raw SDF
			float l_sdf = max(texture(cloud_sdf, l_uvw).r, 0.0);
			vec3 l_noise_pos = lp - vec3(wind_time * 10.0, 0.0, wind_time * 3.0);
			float l_n = textureLod(perlworlnoise, l_noise_pos * l_freq, 2.0).a;
			float l_erosion = (1.0 - l_n) * l_strength * (1.0 - l_sdf * 0.4);
			float l_density = max(l_sdf - l_erosion, 0.0);
			sun_od += l_density;
		}

		float sun_optical = sun_od * params.density_scale * l_step * 0.005;
		float sun_relax = mix(0.25, 0.65, smoothstep(0.0, 0.9, cos_sun));
		float sun_beer = exp(-sun_optical * sun_relax);
		float sun_beer2 = exp(-sun_optical * sun_relax * 0.25) * 0.7;
		float sun_beer_total = max(sun_beer, sun_beer2);

		// Sun lighting — balanced contrast, multi-scatter fills shadows
		float sun_ms = sdf * sun_beer_total;
		float sun_bp = beer_powder(sun_optical);
		// ms_volume provides the base brightness, bp adds crevice detail
		// Multi-scatter minimum (0.15) prevents pure black shadows
		vec3 sun_direct = sun_col * (sun_ms * 0.5 + sun_bp * 0.2 + sdf * 0.15);
		vec3 sun_silver = sun_col * sun_beer_total * sun_beer_total * sun_phase * 0.3;

		// Moon lighting (skip if moon contributes nothing)
		vec3 moon_direct = vec3(0.0);
		vec3 moon_silver = vec3(0.0);
		if (has_moon) {
			float moon_od = 0.0;
			vec3 mlp = pos;
			for (int j = 0; j < 4; j++) {
				mlp += moon_dir * l_step;
				vec3 ml_uvw = (mlp - box_min) / (box_max - box_min);
				if (any(lessThan(ml_uvw, vec3(0))) || any(greaterThan(ml_uvw, vec3(1)))) break;
				float ml_sdf = max(texture(cloud_sdf, ml_uvw).r, 0.0);
				vec3 ml_noise_pos = mlp - vec3(wind_time * 10.0, 0.0, wind_time * 3.0);
				float ml_n = textureLod(perlworlnoise, ml_noise_pos * l_freq, 2.0).a;
				float ml_erosion = (1.0 - ml_n) * l_strength * (1.0 - ml_sdf * 0.4);
				moon_od += max(ml_sdf - ml_erosion, 0.0);
			}
			float moon_optical = moon_od * params.density_scale * l_step * 0.005;
			float moon_relax = mix(0.25, 0.65, smoothstep(0.0, 0.9, cos_moon));
			float moon_beer = exp(-moon_optical * moon_relax);
			float moon_beer2 = exp(-moon_optical * moon_relax * 0.25) * 0.7;
			float moon_beer_total = max(moon_beer, moon_beer2);
			float moon_ms = sdf * moon_beer_total;
			float moon_bp = beer_powder(moon_optical);
			moon_direct = moon_col * (moon_ms * 0.5 + moon_bp * 0.2 + sdf * 0.15);
			moon_silver = moon_col * moon_beer_total * moon_beer_total * moon_phase * 0.3;
		}

		// Ambient scattering — tinted by atmospheric scatter (Fix 2)
		float ambient_scatter = pow(max(1.0 - sdf, 0.0), 0.5);
		float sun_up_w = smoothstep(-0.05, 0.15, sun_dir.y);
		float moon_up_w = smoothstep(-0.05, 0.15, moon_dir.y);
		// Atmospheric color provides the environment tint
		vec3 env_ambient = atm_color * 0.5 + sun_col * sun_up_w * 0.2 + moon_col * moon_up_w * 0.3;
		vec3 ambient = (env_ambient + vec3(0.04, 0.045, 0.07)) * ambient_scatter;

		// Also tint direct lighting by atmospheric color (subtle)
		sun_direct = mix(sun_direct, sun_direct * (vec3(1.0) + atm_color), 0.15);
		moon_direct = mix(moon_direct, moon_direct * (vec3(1.0) + atm_color * 0.5), 0.1);

		// Debug accumulation
		if (debug_mode > 0) {
			float w = density;
			dbg_sun_optical += sun_optical * w;
			dbg_sun_beer += sun_beer_total * w;
			dbg_sdf += sdf * w;
			dbg_density += density * w;
			dbg_sun_bp += sun_bp * w;
			dbg_weight += w;
		}

		vec3 col = sun_direct + sun_silver + moon_direct + moon_silver + ambient;

		float st = exp(-density * step_size * 0.01);
		light_energy += col * transmittance * (1.0 - st);
		transmittance *= st;
	}

	float alpha = 1.0 - transmittance;
	if (alpha < 0.005) return;

	// Debug visualization
	if (debug_mode > 0 && dbg_weight > 0.0) {
		vec3 dbg_color = vec3(0.0);
		float inv_w = 1.0 / dbg_weight;
		if (debug_mode == 1) dbg_color = vec3(dbg_sun_optical * inv_w * 0.01);  // sun_optical scaled
		if (debug_mode == 2) dbg_color = vec3(dbg_sun_beer * inv_w);             // beer transmittance
		if (debug_mode == 3) dbg_color = vec3(dbg_sdf * inv_w);                  // dimensional profile
		if (debug_mode == 4) dbg_color = vec3(dbg_density * inv_w * 0.01);       // density scaled
		if (debug_mode == 5) dbg_color = vec3(dbg_sun_bp * inv_w);               // beer-powder
		vec4 scene_color = imageLoad(color_image, uv);
		imageStore(color_image, uv, vec4(dbg_color * alpha + scene_color.rgb * transmittance, 1.0));
		return;
	}

	// Atmospheric scatter — same Rayleigh+Mie as Sunshine for exact color matching
	float cloud_dist = (t.x + t.y) * 0.5;
	float sun_up_w = smoothstep(0.0, 0.4, sun_dir.y);
	float sun_power = params.sun_color.a * sun_up_w;
	vec4 atm_sun = compute_atmospheric_scatter(rd, sun_dir, sun_col * sun_power, cloud_dist);

	// Blend cloud color toward atmospheric scatter (like Sunshine line 859)
	vec3 atm_cloud = mix(light_energy, atm_sun.rgb, atm_sun.a);

	// Also blend toward atmosphere for moon if present
	if (has_moon) {
		float moon_up_w = smoothstep(0.0, 0.4, moon_dir.y);
		float moon_power = params.moon_color.a * moon_up_w;
		vec4 atm_moon = compute_atmospheric_scatter(rd, moon_dir, moon_col * moon_power, cloud_dist);
		atm_cloud = mix(atm_cloud, atm_cloud + atm_moon.rgb, atm_moon.a);
	}

	// Composite over scene
	vec4 scene_color = imageLoad(color_image, uv);
	vec3 final_color = atm_cloud + scene_color.rgb * transmittance;
	imageStore(color_image, uv, vec4(final_color, 1.0));
}