Depth fog

A quad in front of the camera reading DEPTH_TEXTURE, so the fog knows how far every pixel is.

The editor above is running this project. Change a line and it reloads.

Godot classes used

QuadMeshCamera3DShaderMaterial

The code

scripts/main.gd
extends Node


func _ready() -> void:
	_add_environment()
	_add_light()
	_add_ground()

	for i in 12:
		_add_pillar(Vector3(-3.5 if i % 2 == 0 else 3.5, 0, -float(i) * 3.4))

	var camera: Camera3D = Camera3D.new()
	camera.position = Vector3(0, 2.4, 6)
	add_child(camera)
	camera.look_at(Vector3(0, 1.6, -20), Vector3.UP)
	camera.current = true

	# the quad rides in front of the camera and covers the screen, which is how it reads the scene depth
	var material: ShaderMaterial = ShaderMaterial.new()
	material.shader = Get.shader('depth_fog')

	var mesh: QuadMesh = QuadMesh.new()
	mesh.size = Vector2(2, 2)

	var screen: MeshInstance3D = MeshInstance3D.new()
	screen.mesh = mesh
	screen.material_override = material
	screen.extra_cull_margin = 1e5
	camera.add_child(screen)
	screen.position = Vector3(0, 0, -0.5)

	print('the fog reads DEPTH_TEXTURE, so it knows how far each pixel is')


func _add_pillar(at: Vector3) -> void:
	var mesh: BoxMesh = BoxMesh.new()
	mesh.size = Vector3(1.2, 3.6, 1.2)

	var material: StandardMaterial3D = StandardMaterial3D.new()
	material.albedo_color = Color('#8da5f3')
	material.roughness = 0.5

	var pillar: MeshInstance3D = MeshInstance3D.new()
	pillar.mesh = mesh
	pillar.material_override = material
	pillar.position = at + Vector3(0, 1.8, 0)
	add_child(pillar)


func _add_ground() -> void:
	var mesh: PlaneMesh = PlaneMesh.new()
	mesh.size = Vector2(60, 120)

	var material: StandardMaterial3D = StandardMaterial3D.new()
	material.albedo_color = Color('#2a2f3d')
	material.roughness = 0.9

	var ground: MeshInstance3D = MeshInstance3D.new()
	ground.mesh = mesh
	ground.material_override = material
	ground.position = Vector3(0, 0, -30)
	add_child(ground)


func _add_environment() -> void:
	var env: Environment = Environment.new()
	env.background_mode = Environment.BG_COLOR
	env.background_color = Color(0.35, 0.42, 0.6)
	env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
	env.ambient_light_color = Color(0.2, 0.24, 0.35)
	env.ambient_light_energy = 1.0

	var world: WorldEnvironment = WorldEnvironment.new()
	world.environment = env
	add_child(world)


func _add_light() -> void:
	var light: DirectionalLight3D = DirectionalLight3D.new()
	light.rotation_degrees = Vector3(-45, -30, 0)
	light.light_energy = 1.1
	add_child(light)
shaders/depth_fog.gdshader
shader_type spatial;
render_mode unshaded, depth_test_disabled, cull_disabled;

uniform sampler2D screen_texture: hint_screen_texture, filter_linear;
uniform sampler2D depth_texture: hint_depth_texture;
uniform vec3 fog_color: source_color = vec3(0.35, 0.42, 0.6);
uniform float fog_start = 6.0;
uniform float fog_end = 34.0;

void vertex() {
	POSITION = vec4(VERTEX.xy, 1.0, 1.0);
}

void fragment() {
	float raw = texture(depth_texture, SCREEN_UV).r;
	vec3 ndc = vec3(SCREEN_UV * 2.0 - 1.0, raw);
	vec4 view = INV_PROJECTION_MATRIX * vec4(ndc, 1.0);
	view.xyz /= view.w;

	float distance_to_camera = -view.z;
	float fog = clamp((distance_to_camera - fog_start) / (fog_end - fog_start), 0.0, 1.0);

	ALBEDO = mix(texture(screen_texture, SCREEN_UV).rgb, fog_color, fog);
}

More Shaders examples