Hi there! :satellite:
People seem to enjoy Kuiper Space Rescue aesthetics and atmosphere a lot, so I thought I'd share how the rendering was made! Nothing very complex, but hopefully this will give some people ideas or inspiration!
The breakdown is about how to go from the left screenshot to the right one:

The effect looks like this in game:

Reference image:

The idea was not to copy it perfectly, simply to have a starting point to write the shader.
The game has been made with Godot, so the post process setup is a simple CanvasLayer node containing a ColorRect rendered above the game. On this ColorRect is attached a material with the post process shader this breakdown is about.
The very first thing I've done is adding a bit of chromatic aberration, making the image a bit less clean, and most importantly splitting the image into a yellow and a red layer, the yellow being rendered on top for the global tint.


Next effect is adding more red. On the reference, the red color is more diffuse than the yellow, but always near it. A blur function is a perfect fit for this. Cool thing is, it does not really matter what blur function is used, since the effect tries to mimic an old machine screen, even a low quality blur can work perfectly fine.

Then, the blueish color comes in. On the reference it looks like it's leaking horizontally, which again is a nice fit for a blur, this time a directional blur. I went for a blur going in 5 directions instead of a horizontal one as we thought it looked better.
Here's a gif showing the blur parameters in action:


Then, a mandatory grain effect is added on top of everything.

Adding the grain to the rest looks like this:

The rest of the shader is a very light color correction and another grain pass added on top to make the yellow part of the game a bit more dirty.

And that's it!
What's great with the effect was that, any asset drawn in pure white looked good once added to the scene. The logo of the game was even exported directly from there:

Thanks for reading! :v:
Last thing to do is to share the shader code! Do not consider it a perfectly written shader, it's still a game jam implementation, but if there's anything you want to take from it, there you go!
```
shadertype canvasitem;
uniform sampler2D screentexture: hintscreen_texture;
groupuniforms Aberration;
uniform float aberrationoffset: hintrange(0.0, 1.0) = 0.01;
uniform float aberrationyellowreduction: hintrange(0.0, 1.0) = 0.1;
groupuniforms RedBlur;
uniform float redblurriness: hintrange(0, 128, 1) = 64;
uniform int rediterations: hintrange(0, 128, 1) = 64;
uniform int redquality: hintrange(0, 128, 1) = 32;
uniform float redblurintensity: hintrange(0.0, 10.0) = 3.0;
uniform vec3 redblurcolor: source_color = vec3(1.0, 0.0, 0.0);
groupuniforms BlueBlur;
uniform float blueblurblurriness: hintrange(0, 128, 1) = 8;
uniform int bluebluriterations: hintrange(0, 128, 1) = 8;
uniform int blueblurquality: hintrange(0, 128, 1) = 8;
uniform float blueblurintensity: hintrange(0.0, 10.0) = 1.0;
uniform vec3 blueblurcolor: sourcecolor = vec3(0.0, 0.0, 1.0);
groupuniforms Grain;
uniform sampler2D graintexture: repeatenable;
uniform float grainspeed: hintrange(0.0, 10.0) = 0.1;
uniform float grainintensity: hint_range(0.0, 1.0) = 0.1;
vec3 hash23(vec2 input)
{
float a = dot(input.xyx, vec3(127.1, 311.7, 74.7));
float b = dot(input.yxx, vec3(269.5, 183.3, 246.1));
float c = dot(input.xyy, vec3(113.5, 271.9, 124.6));
return fract(sin(vec3(a, b, c)) * 43758.5453123);
}
vec4 texturexorgaussian(sampler2D tex, vec2 uv, vec2 pixelsize, float blurriness, int iterations, int quality)
{
vec2 radius = blurriness / (1.0 / pixelsize).xy;
vec4 blurredtex = texture(tex, uv);
for (float d = 0.0; d < TAU; d += TAU / float(iterations))
{
for (float i = 1.0 / float(quality); i <= 1.0; i += 1.0 / float(quality))
{
vec2 directions = uv + vec2(cos(d), sin(d)) * radius * i;
blurred_tex += texture(tex, directions);
}
}
blurred_tex /= float(quality) * float(iterations) + 1.0;
return blurred_tex;
}
float luminance(vec3 color)
{
const vec3 w = vec3(0.2125, 0.7154, 0.0721);
return dot(color, w);
}
void fragment()
{
vec3 screencolor = texture(screentexture, SCREEN_UV).rgb;
// Red blur.
vec3 red_blur = texture_xorgaussian(screen_texture, SCREEN_UV, SCREEN_PIXEL_SIZE, red_blurriness, red_iterations, red_quality).rgb;
red_blur = clamp(red_blur, 0.0, 1.0);
// Blue blur.
vec3 blue_blur = texture_xorgaussian(screen_texture, SCREEN_UV, SCREEN_PIXEL_SIZE, blue_blur_blurriness, blue_blur_iterations, blue_blur_quality).rgb;
blue_blur = clamp(blue_blur, 0.0, 1.0);
// Aberration.
vec2 uv_offset = vec2(aberration_offset, 0.0);
vec4 aberration_right = texture(screen_texture, SCREEN_UV + uv_offset);
vec4 aberration_left = texture(screen_texture, SCREEN_UV - uv_offset);
vec3 aberration = vec3((aberration_left.x + aberration_right.x) * 0.8, aberration_right.y * 1.0, 0.0);
aberration = clamp(aberration, 0.0, 1.0);
// Grain.
float grain_time = fract(TIME * grain_speed);
float grain_sample = 0.0;
if (grain_time < 0.333f)
grain_sample = texture(grain_texture, (UV * 200.0) + vec2(0.2, 0.2)).r;
else if (grain_time < 0.666f)
grain_sample = texture(grain_texture, (UV * 200.0) + vec2(0.3, 0.1)).r;
else
grain_sample = texture(grain_texture, (UV * 200.0)).r;
vec3 grain = (hash23((UV * 10.0) + vec2(TIME * 0.01)) * grain_intensity) - (grain_intensity * 0.5);
grain = vec3(grain_sample) * grain_intensity;
grain *= grain;
// Final computation.
vec3 result = screen_color;
result = aberration;
result += red_blur * red_blur_intensity * red_blur_color;
result += blue_blur * blue_blur_intensity * blue_blur_color * (1.0 - red_blur);
result += grain * grain;
result = mix(result, vec3(1.0), luminance(result) * aberration_yellow_reduction);
result = mix(result, vec3(0.0), (1.0 - grain) * 0.3);
COLOR = vec4(clamp(result, 0.0, 1.0), 1.0);
}
```