shader_type spatial; render_mode unshaded, fog_disabled, depth_draw_never, cull_disabled, blend_mix; const int MAX_LIGHTS = 256; uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_linear; uniform sampler2D depth_texture : hint_depth_texture, repeat_disable, filter_nearest; // Camera matrices — set from GDScript. uniform mat4 inv_projection; uniform mat4 inv_view; // Fog uniform vec3 fog_color : source_color = vec3(0.55, 0.65, 0.75); uniform float fog_density : hint_range(0.0, 10.0) = 0.5; uniform float fog_height_falloff : hint_range(0.0, 10.0) = 0.15; // Light scattering uniform int light_count = 0; uniform vec3 light_positions[MAX_LIGHTS]; uniform vec4 light_colors[MAX_LIGHTS]; uniform float light_energies[MAX_LIGHTS]; uniform float light_ranges[MAX_LIGHTS]; uniform float scattering : hint_range(0.0, 2.0) = 0.5; uniform float scattering_power : hint_range(0.1, 32.0) = 8.0; uniform int steps : hint_range(8, 64) = 32; vec3 reconstruct_view_position(vec2 uv, float depth) { vec3 ndc = vec3(uv * 2.0 - 1.0, depth); vec4 view_pos = inv_projection * vec4(ndc, 1.0); view_pos.xyz /= view_pos.w; return view_pos.xyz; } void fragment() { vec2 screen_uv = SCREEN_UV; vec4 scene = texture(screen_texture, screen_uv); float depth = texture(depth_texture, screen_uv).r; vec3 ray_end_view = reconstruct_view_position(screen_uv, depth); vec3 camera_position = (inv_view * vec4(0.0, 0.0, 0.0, 1.0)).xyz; vec3 ray_end = (inv_view * vec4(ray_end_view, 1.0)).xyz; vec3 ray_vector = ray_end - camera_position; float ray_length = length(ray_vector); vec3 ray_dir = normalize(ray_vector); float step_length = ray_length / float(steps); vec3 accumulated = vec3(0.0); float transmittance = 1.0; for (int i = 0; i < steps; i++) { float t = (float(i) + 0.5) / float(steps); vec3 world_position = camera_position + ray_dir * (ray_length * t); float height_density = exp(-world_position.y * fog_height_falloff); float density = fog_density * height_density; vec3 light_scattering = vec3(0.0); for (int l = 0; l < MAX_LIGHTS; l++) { if (l >= light_count) break; vec3 to_light = light_positions[l] - world_position; float light_distance = length(to_light); if (light_distance <= 0.001) continue; vec3 light_dir = to_light / light_distance; float attenuation = 1.0 - smoothstep( 0.0, light_ranges[l], light_distance ); attenuation /= 1.0 + light_distance * light_distance * 0.01; float phase = pow( max(dot(-ray_dir, light_dir), 0.0), scattering_power ); light_scattering += light_colors[l].rgb * light_energies[l] * attenuation * phase * scattering; } float absorption = exp(-density * step_length * 10.0); accumulated += (fog_color + light_scattering) * (1.0 - absorption) * transmittance; transmittance *= absorption; } ALBEDO = scene.rgb * transmittance + accumulated; ALPHA = 1.0; }