[Godot] Full-Screen Post-Processing Shaders: Screen Effects with hint_screen_texture

Created: 2026-07-09

A guide to post-processing shaders that affect the whole screen in Godot. Covers the CanvasLayer plus full-screen ColorRect setup, reading the rendered screen with hint_screen_texture, standard recipes like vignette, chromatic aberration, and CRT scanlines, and a hands-on damage flash.

Softly darkening the corners of the screen. Laying retro CRT scanlines over everything. Flashing the whole screen red the instant you take a hit. Effects that act on the entire game screen can't be built with shaders on individual objects. What you want is a post-processing shader.

Post-processing is a technique that reads the entire already-rendered screen and applies processing on top of it. This article covers the setup (CanvasLayer plus a full-screen ColorRect), the key to reading the screen (hint_screen_texture), standard recipes like vignette and chromatic aberration, and a hands-on damage flash.

A post-processing concept: an effect layer covering the whole screen sits on top of the finished game screen, applying vignette, scanlines, and similar processing across everything

What You'll Learn

  • The post-processing setup (CanvasLayer plus a full-screen ColorRect)
  • Reading the rendered screen with hint_screen_texture and SCREEN_UV
  • Standard recipes (vignette, chromatic aberration, CRT scanlines, and more)
  • A hands-on damage flash, and when to use the SubViewport approach instead

Sponsored

How Post-Processing Works

The idea behind post-processing is "draw the game screen as a single image, then process that image." It's like taking a photo and then applying a filter.

A post-processing pipeline diagram. The game world is rendered into a single screen image. A full-screen ColorRect on a CanvasLayer covers it, applies processing through a ShaderMaterial, and the processed screen is what finally gets displayed

The setup for this in Godot is simple.

  • CanvasLayer: a layer always drawn in front of the game screen. Anything you put here ends up on top
  • A full-screen ColorRect: a rectangle stretched to fill the screen. This is the canvas you apply processing to
  • ShaderMaterial: the processing itself (the shader), assigned to that ColorRect

In other words, you apply a shader to a ColorRect covering the whole screen and process the game screen beneath it. Set the ColorRect's anchors to "Full Rect" so it fills the screen and you're ready.

Reading the Whole Screen: hint_screen_texture

The heart of post-processing is hint_screen_texture. It's the mechanism that hands your shader the screen exactly as it was just drawn.

A hint_screen_texture diagram. The rendered game screen is passed in as screen_tex, and the shader uses SCREEN_UV (the coordinate on screen) to read that screen's color pixel by pixel, process it, and output the result

On the shader side, you declare and read it like this.

shader_type canvas_item;

// hint_screen_texture receives the already-rendered screen
uniform sampler2D screen_tex : hint_screen_texture, filter_linear_mipmap;

void fragment() {
    // SCREEN_UV is the coordinate on screen. Read the color there
    vec3 screen_color = texture(screen_tex, SCREEN_UV).rgb;

    // Process screen_color here (this example just passes it through)
    COLOR = vec4(screen_color, 1.0);
}

There are two things to note. hint_screen_texture feeds the unprocessed screen into screen_tex, and SCREEN_UV represents the position on that screen (0.0 to 1.0). Read each pixel's color with those two, write your processed result to COLOR, and the whole screen changes. From there, what you do with the color you read is what makes each recipe distinct.

Sponsored

Standard Recipes

How you process the screen you read produces a wide range of screen effects. Let's look at the main ones.

Standard post-processing recipes shown as four before/after examples: vignette (softly darkened corners), CRT scanlines (a retro screen with horizontal lines), grayscale (color removed for monochrome), and chromatic aberration (the image slightly offset at the screen edges)
RecipeWhat It DoesCommon Uses
VignetteDarkens the corners of the screenFocus, cinematic mood
Chromatic aberrationOffsets colors slightly at the screen edgesImpact, disorientation, glitch effects
CRT scanlinesAdds horizontal lines and bleed for a tube-TV lookRetro game atmosphere
GrayscaleRemoves color for monochromeNear death, flashbacks, pause screens

All of them share the same skeleton: read the screen with SCREEN_UV, tweak the color or coordinates, and write it back to COLOR. A vignette darkens based on distance from the screen center; grayscale averages the RGB to strip the color. Only the math differs, the structure is the same.

Hands-On: Flashing the Screen Red on Damage

Taking damage in an action game, a scare in a horror game, showing danger in a shmup. Flashing the whole screen red the instant you take damage is a standard screen effect for conveying state intuitively. Let's build it with post-processing.

A red damage flash diagram. Normally the screen displays as-is; on damage the uniform flash_amount rises, red washes over the whole screen and pulses, then it returns to 0 and the original screen comes back

All it does is push the screen color toward red according to flash_amount (a uniform).

shader_type canvas_item;

uniform sampler2D screen_tex : hint_screen_texture, filter_linear_mipmap;
uniform float flash_amount : hint_range(0.0, 1.0) = 0.0;  // The redness, controlled from outside

void fragment() {
    vec3 screen_color = texture(screen_tex, SCREEN_UV).rgb;
    vec3 red = vec3(0.8, 0.0, 0.0);
    // Push toward red by the amount of flash_amount
    COLOR = vec4(mix(screen_color, red, flash_amount), 1.0);
}

On the script side, raise flash_amount to 1 on damage and drop it back to 0 right away.

@onready var mat: ShaderMaterial = $CanvasLayer/ColorRect.material

func on_damaged() -> void:
    mat.set_shader_parameter("flash_amount", 0.6)   # Snap to red
    # Returning it to 0 with a Tween makes the redness drain away
    create_tween().tween_property(mat, "shader_parameter/flash_amount", 0.0, 0.3)

There are two points to take away.

  • It applies uniformly across the whole screen: unlike modulate, which lights up an individual character, this turns the entire screen red. It strongly conveys "I took that hit."
  • Control it with a uniform plus a Tween: prepare a single flash_amount and drive it with set_shader_parameter(). Returning it to 0 with a Tween drains the redness smoothly.

Choosing Between This and SubViewport

There's also a way to process the screen using SubViewport. Choose based on your goal.

A diagram on choosing between post-processing and SubViewport. Post-processing applies an effect uniformly across the whole screen after the fact (vignette, CRT). SubViewport renders specific footage separately and processes it (minimaps, security cameras, effects on part of the screen). Full-screen means post-processing, partial means SubViewport
  • Post-processing: applied uniformly and after the fact to the finished screen. Vignette, CRT, full-screen flashes
  • SubViewport: renders specific footage separately and processes it. Minimaps, security cameras, effects on just part of the screen

Remember it as post-processing for the entire game screen and SubViewport for crafting a specific piece of footage, and you won't get stuck.

Bonus: Good to Know for Later

  • It works on 3D too: a full-screen ColorRect on a CanvasLayer sits on top of both 2D and 3D games, so you can apply the same post-processing to a 3D game's screen.
  • Watch the order when stacking: to apply multiple effects (vignette plus CRT, say), either combine them into one shader or stack ColorRects. When stacking, keep the bottom-to-top processing order in mind.
  • Great for screen-transition wipes: using SCREEN_UV and time to progressively fill the screen from the left lets you build scene-transition wipes with post-processing too.

Summary

  • Post-processing reads the finished screen and processes it. The setup is CanvasLayer plus a full-screen ColorRect plus a ShaderMaterial
  • Receive the screen through hint_screen_texture and read each pixel with SCREEN_UV
  • Vignette, chromatic aberration, CRT scanlines, grayscale: only the processing math changes to produce a wide range of screen effects
  • A damage flash just pushes the color you read toward red with flash_amount (a uniform), then eases it back with a Tween
  • Use post-processing for full-screen and SubViewport for partial

Start with the minimal shader that passes the screen through unchanged, then add a vignette to it. The moment the whole screen visibly tightens up is when post-processing clicks.

Further Reading