OverviewFilesCommitsIssuesDocsReleasesPeers

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

203 lines · gdshader ref:
// Copyright (c) 2023-2025 Cory Petkovsek and Contributors
// Copyright (c) 2021 J. Cuellar

shader_type spatial;
render_mode blend_mix, cull_front, unshaded, fog_disabled;

uniform vec2 color_correction;

uniform float fog_density = .0001;
uniform float fog_rayleigh_depth = .115;
uniform float fog_mie_depth = 0.;
uniform float fog_falloff = 3.;
uniform float fog_start = 0.;
uniform float fog_end = 1000.;
// LAYERED FOG (hyper-tps 2026-10-06, fog_model 1). The original factor MULTIPLIED an
// exponential, a linear ramp to fog_end and a view-direction falloff: near the camera
// the product is ~quadratic, so any density that showed haze at 1-2 km made a wall at
// the far side (mountain: 1 km 2.8 %, 12 km 94 %), and "falloff" was by VIEW DIRECTION,
// not altitude — valleys could not be hazier than peaks. Three independent terms instead,
// combined as 1 - (1-near)(1-far)(1-height):
//   near   = 1 - exp(-d * near_density)                     haze from the first metre
//   far    = far_max * smoothstep(far_begin, far_end, d)^far_curve      distance falloff
//   height = 1 - exp(-∫ a·exp(-(y - height_base)/height_falloff) along the view ray)
//            with a = height_density — from the camera's REAL altitude, so valleys fill.
// haze_color_depth: the scatter colour is evaluated at least this far out, so near haze
// LIGHTENS toward the sky colour instead of the original's dim, murky short-range tint.
// fog_model 0 keeps the original behaviour exactly.
uniform int fog_model = 1;
uniform float near_density = 0.00002;
uniform float far_begin = 2000.0;
uniform float far_end = 14000.0;
uniform float far_curve = 1.5;
uniform float far_max = 0.85;
uniform float height_base = 0.0;
uniform float height_falloff = 150.0;
uniform float height_density = 0.0002;
uniform float haze_color_depth = 5000.0;
uniform vec3 sun_direction = vec3(.25, -.25, .25);
uniform vec3 moon_direction = vec3(.45, -.25, .45);

uniform float atm_darkness = .5;
uniform float atm_sun_intensity = 30.;
uniform vec4 atm_day_tint: source_color = vec4(.78, .85, .98, 1.);
uniform vec4 atm_horizon_light_tint: source_color = vec4(.98, .73, .49, 1.);
uniform vec4 atm_night_tint: source_color = vec4(.16, .2, .25, 1.);
uniform vec3 atm_level_params = vec3(1., 0., 0.);
uniform float atm_thickness = .7;

uniform vec3 atm_beta_ray;
uniform vec3 atm_beta_mie;

uniform vec3 atm_sun_partial_mie_phase;
uniform vec4 atm_sun_mie_tint: source_color = vec4(1.);
uniform float atm_sun_mie_intensity = 1.;

uniform vec3 atm_moon_partial_mie_phase;
uniform vec4 atm_moon_mie_tint: source_color = vec4(.13, .18, .29, 1.);
uniform float atm_moon_mie_intensity = .7;

uniform sampler2D DEPTH_TEXTURE : hint_depth_texture, filter_linear_mipmap;

#include "Common.gdshaderinc"


// Atmosphere
//------------------------------------------------------------------------------
vec3 calc_atmospheric_scatter(float sr, float sm, vec2 mu, vec3 mult, float depth) {
	vec3 beta_mie = atm_beta_mie;
	vec3 beta_ray = atm_beta_ray * atm_thickness;

	vec3 extc_factor = clamp_rgb_to_unit(exp(-(beta_ray * sr + beta_mie * sm)));

	float extc_ff = mix(clamp_to_unit(atm_thickness * 0.5), 1.0, mult.x);
	vec3 final_extc_factor = mix(1.0 - extc_factor, (1.0 - extc_factor) * extc_factor, extc_ff);

	float rayleigh_phase = calc_rayleigh_phase(mu.x);
	vec3 BRT = beta_ray * rayleigh_phase * clamp_to_unit(depth * fog_rayleigh_depth);
	vec3 BMT = beta_mie * calc_mie_phase(mu.x, atm_sun_partial_mie_phase);
	BMT *= atm_sun_mie_intensity * atm_sun_mie_tint.rgb * clamp_to_unit(depth * fog_mie_depth);

	vec3 BRMT = (BRT + BMT) / (beta_ray + beta_mie);
	vec3 scatter = atm_sun_intensity * (BRMT * final_extc_factor) * atm_day_tint.rgb * mult.y;
	scatter = mix(scatter, scatter * (1.0 - extc_factor), atm_darkness);

	vec3 lcol = mix(atm_day_tint.rgb, atm_horizon_light_tint.rgb, mult.x);
	vec3 nscatter = (1.0 - extc_factor) * atm_night_tint.rgb * clamp_to_unit(depth * fog_rayleigh_depth);
	nscatter += calc_mie_phase(mu.y, atm_moon_partial_mie_phase) *
		atm_moon_mie_tint.rgb * atm_moon_mie_intensity * 0.005 * clamp_to_unit(depth * fog_mie_depth);

	return (scatter * lcol) + nscatter;
}

// Fog
//------------------------------------------------------------------------------
float calc_fog_exp(float depth, float density) {
	return 1.0 - clamp_to_unit(exp2(-depth * density));
}

float calc_fog_falloff(float y, float zeroLevel, float falloff) {
	return clamp_to_unit(exp(-(y + zeroLevel) * falloff));
}

float calc_fog_distance(float depth) {
	float d = depth;
	d = (fog_end - d) / (fog_end - fog_start);
	return clamp_to_unit(1.0 - d);
}

void calc_coords(vec2 uv, float depth, mat4 camMat, mat4 invProjMat,
	out vec3 viewDir, out vec3 worldPos) {

	vec3 ndc = vec3(uv * 2.0 - 1.0, depth);

	// ViewDir
	vec4 view = invProjMat * vec4(ndc, 1.0);
	viewDir = view.xyz / view.w;

	// worldPos
	view = camMat * view;
	view.xyz /= view.w;
	view.xyz -= (camMat * vec4(0.0, 0.0, 0.0, 1.0)).xyz;
	worldPos = view.xyz;
}

// Main
//------------------------------------------------------------------------------

varying mat4 camera_matrix;
varying vec4 angle_mult;

void vertex() {
	POSITION = vec4(VERTEX.xy, 1.0, 1.0);
	angle_mult = vec4(
		clamp_to_unit(1.0 - sun_direction.y),
		clamp_to_unit(sun_direction.y + 0.45),
		clamp_to_unit(-sun_direction.y + 0.30),
		clamp_to_unit(-sun_direction.y + 0.60));
	camera_matrix = INV_VIEW_MATRIX;
}

void fragment() {
	float depthRaw = texture(DEPTH_TEXTURE, SCREEN_UV).r;

	#ifndef CURRENT_RENDERER
	    // For 4.3 or earlier, will break if far plane is less than 1m.
	    depthRaw = PROJECTION_MATRIX[3][2] > PROJECTION_MATRIX[2][2] ? depthRaw : depthRaw * 2.0 - 1.0;
    #else
	    #if CURRENT_RENDERER == RENDERER_COMPATIBILITY
	    	depthRaw = depthRaw * 2.0 - 1.0;
	    #endif
    #endif

	vec3 view; vec3 worldPos;
	calc_coords(SCREEN_UV, depthRaw, camera_matrix, INV_PROJECTION_MATRIX, view, worldPos);
	float ray_len = length(worldPos);          // camera -> surface, metres
	worldPos = normalize(worldPos);

	float linear_depth = -view.z;
	float fog_factor;
	if (fog_model == 1) {
		float d = ray_len;
		float near_f = 1.0 - exp(-d * near_density);
		float far_f = far_max * pow(smoothstep(far_begin, far_end, d), far_curve);
		// Exponential height fog integrated along the ray from the camera's altitude.
		float b = 1.0 / max(height_falloff, 1.0);
		float cam_y = camera_matrix[3].y;
		float ry = worldPos.y;
		float base = height_density * exp(-(cam_y - height_base) * b);
		float k = b * d * ry;
		float optical = abs(k) > 1e-4 ? base * (1.0 - exp(-k)) / (b * ry) : base * d;
		float height_f = 1.0 - exp(-max(optical, 0.0));
		fog_factor = 1.0 - (1.0 - near_f) * (1.0 - far_f) * (1.0 - height_f);
		// THE SKY IS NOT FOGGED: it carries its own atmosphere, and far_max on sky
		// pixels washed the cloud bank out (A/B render, mountain). Reverse-Z: the
		// cleared depth of a sky pixel is 0.
		// Below the horizon the "sky" is the dome's dark ground colour, seen where no
		// terrain is: it keeps the full far fog (unfogged it read as a dark band along
		// the horizon), fading to none just above it.
		if (texture(DEPTH_TEXTURE, SCREEN_UV).r <= 1e-7) {
			fog_factor = far_max * (1.0 - smoothstep(-0.01, 0.06, ry));
		}
	} else {
		fog_factor = calc_fog_exp(linear_depth, fog_density);
		fog_factor *= calc_fog_falloff(worldPos.y, 0.0, fog_falloff);
		fog_factor *= calc_fog_distance(linear_depth);
	}

	vec2 mu = vec2(dot(sun_direction, worldPos), dot(moon_direction, worldPos));
	float sr, sm;
	calc_simple_optical_depth(worldPos.y + atm_level_params.z, sr, sm, atm_level_params.xy);
	float scatter_depth = fog_model == 1 ? max(linear_depth, haze_color_depth) : linear_depth;
	vec3 scatter = calc_atmospheric_scatter(sr, sm, mu.xy, angle_mult.xyz, scatter_depth);

	vec3 tint =  scatter;
	vec4 fog_color = vec4(tint.rgb, fog_factor);
	fog_color = vec4((fog_color.rgb), clamp_to_unit(fog_color.a));
	fog_color.rgb = apply_photo_tonemap(fog_color.rgb, color_correction.y, color_correction.x);

	ALBEDO = fog_color.rgb;
	ALPHA = fog_color.a;
	//ALPHA = (depthRaw) < 0.999999999999 ? fog_color.a: 0.0; // Exclude sky.
}