Screen-reading shaders: frame copies and chained effects
Desaturate the displayed image and pass the result correctly to a second effect.
Run the example
Download the project. Requires Reckfell 1.0.0 with the C# SDK and Forward+/Vulkan. Open project.reckfell, build C# and run Main.rscene. Choose root Preview=Original for the room, Grayscale for desaturation, or Chained for the diagnostic chain. At runtime call SetPreview(mode) after _Ready.
A separate SubViewport renders the room and a TextureRect displays it. A canvas_item shader then reads the displayed image. Source creation and the complete C# controller are in the live-texture guide.
Original image:
The same texture desaturated by the screen shader:
Read screen coordinates
ScreenTone.reckfellshader:
shader_type canvas_item;
render_mode unshaded;
uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_linear;
uniform float desaturate : hint_range(0.0, 1.0) = 0.0;
void fragment() {
vec4 source = textureLod(screen_texture, SCREEN_UV, 0.0);
float gray = dot(source.rgb, vec3(0.2126, 0.7152, 0.0722));
COLOR = vec4(mix(source.rgb, vec3(gray), desaturate), source.a);
}hint_screen_texture binds the sampler to a copy of the current viewport’s screen. SCREEN_UV provides coordinates for that screen; UV belongs to the ColorRect’s own geometry. Moving or shrinking the rectangle keeps SCREEN_UV aligned with the image behind it.
repeat_disable prevents wrapping beyond texture edges; filter_linear selects linear sampling. textureLod(..., 0.0) reads the base level here. This example does not implement mipmap blur; increasing LOD without a suitable filter is not a complete blur solution.
At desaturate=0 the shader returns the original color. At 1 all RGB channels equal a weighted gray value. Source alpha is preserved. The ColorRect must draw after the TextureRect and cover the effect area; a white ColorRect without its ShaderMaterial simply covers the image.
Chain two passes
The second shader, ScreenInvert.reckfellshader:
shader_type canvas_item;
render_mode unshaded;
uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_linear;
void fragment() {
vec4 source = textureLod(screen_texture, SCREEN_UV, 0.0);
COLOR = vec4(vec3(1.0) - source.rgb, source.a);
}The first 2D item using hint_screen_texture obtains a screen copy unless BackBufferCopy has already prepared one. Subsequent screen-reading shaders normally reuse that copy. They do not automatically receive the preceding effect’s output.
The example’s draw order with equal z_index:
- Display shows the live room texture.
- TonePass desaturates it.
- Copy with CopyMode=Viewport copies the desaturated frame.
- InvertPass reads the refreshed copy and inverts it.
Disabling Copy makes inversion read the original colored room instead of TonePass’s gray output. Pixel checks confirm that the second pass contains color again. Chained enables the copy, so its result stays gray.
Diagnostic inversion after desaturation; not a finished game color grade:
Copy only the required region
CopyMode=Rect uses Copy.Rect in the BackBufferCopy’s local coordinates. In the accepted case Copy is at the origin, Rect=(0, 0, 1280, 1440), and the second ColorRect draws only the left half. It sees TonePass’s result within that half; the first pass remains visible on the right.
Do not sample beyond the region actually copied: up-to-date contents are not guaranteed there. If a shader offsets its sample coordinates, the copy region must cover those offsets. Start with Viewport and reduce the region after obtaining the correct result.
Diagnose the result
| Symptom | Check |
|---|---|
| no visible effect | canvas_item, assigned ShaderMaterial, ColorRect size and draw order |
| repeated or misaligned image | SCREEN_UV instead of local UV, repeat_disable |
| second effect loses the first | BackBufferCopy between passes and CopyMode other than Disabled |
| adding UI changes the chain | actual draw order, z_index and CanvasLayer |
| stale pixels near effect edges | Rect does not cover the sampling area |
| a 3D shader behaves differently | this example processes an already displayed texture in 2D, not spatial screen-reading |
Accepted in current Forward+/Vulkan: live scene, neutral pass, desaturation, SCREEN_UV on a half-screen rectangle, cached screen-copy reuse, full and rectangular BackBufferCopy. 3D depth/normals, mipmap blur, Compatibility and chain performance are not accepted here. Spatial effects need a separate 3D postprocessing explanation.