Vignette and grain

Desaturation, vignette and film grain over a 3D scene, in eleven lines that read the screen texture.

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

Godot classes used

ColorRectCanvasLayerMeshInstance3D

The code

scripts/main.gd
extends Node

var pivot: Node3D


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

	pivot = Node3D.new()
	add_child(pivot)

	var meshes: Array[Mesh] = [BoxMesh.new(), SphereMesh.new(), CylinderMesh.new(), TorusMesh.new(), PrismMesh.new()]

	for i in meshes.size():
		var angle: float = TAU * i / meshes.size()

		var material: StandardMaterial3D = StandardMaterial3D.new()
		material.albedo_color = Color.from_hsv(float(i) / meshes.size(), 0.45, 0.95)
		material.roughness = 0.35

		var item: MeshInstance3D = MeshInstance3D.new()
		item.mesh = meshes[i]
		item.material_override = material
		item.position = Vector3(cos(angle), 0, sin(angle)) * 2.6
		pivot.add_child(item)

	var camera: Camera3D = Camera3D.new()
	camera.position = Vector3(0, 3, 6.5)
	add_child(camera)
	camera.look_at(Vector3.ZERO, Vector3.UP)
	camera.current = true

	var layer: CanvasLayer = CanvasLayer.new()
	layer.layer = 10
	add_child(layer)

	var material: ShaderMaterial = ShaderMaterial.new()
	material.shader = Get.shader('vignette')

	var screen: ColorRect = ColorRect.new()
	screen.material = material
	layer.add_child(screen)
	screen.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)

	print('vignette, desaturation and film grain over a 3D scene, in eleven lines of shader')


func _process(delta: float) -> void:
	pivot.rotate_y(delta * 0.3)


func _add_environment() -> void:
	var sky_material: ProceduralSkyMaterial = ProceduralSkyMaterial.new()
	sky_material.sky_top_color = Color(0.06, 0.07, 0.14)
	sky_material.sky_horizon_color = Color(0.16, 0.18, 0.26)
	sky_material.ground_bottom_color = Color(0.02, 0.02, 0.04)
	sky_material.ground_horizon_color = Color(0.1, 0.11, 0.16)

	var sky: Sky = Sky.new()
	sky.sky_material = sky_material

	var env: Environment = Environment.new()
	env.background_mode = Environment.BG_SKY
	env.sky = sky
	env.ambient_light_source = Environment.AMBIENT_SOURCE_SKY
	env.ambient_light_energy = 0.9

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


func _add_light() -> void:
	var light: DirectionalLight3D = DirectionalLight3D.new()
	light.rotation_degrees = Vector3(-50, -35, 0)
	light.light_energy = 1.2
	add_child(light)
shaders/vignette.gdshader
shader_type canvas_item;

uniform sampler2D screen_texture: hint_screen_texture, filter_linear;
uniform float vignette_size = 0.75;
uniform float vignette_softness = 0.55;
uniform float grain_amount = 0.06;
uniform float saturation = 0.85;

float hash(vec2 p) {
	return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453);
}

void fragment() {
	vec3 color = texture(screen_texture, SCREEN_UV).rgb;

	float gray = dot(color, vec3(0.299, 0.587, 0.114));
	color = mix(vec3(gray), color, saturation);

	float distance_to_center = length(SCREEN_UV - vec2(0.5)) * 1.4142;
	float vignette = 1.0 - smoothstep(vignette_size - vignette_softness, vignette_size, distance_to_center);
	color *= vignette;

	float grain = hash(SCREEN_UV + fract(TIME)) - 0.5;
	color += grain * grain_amount;

	COLOR = vec4(color, 1.0);
}

More Shaders examples