Screen Space Lens Flare with rainbow colored effect

Hey there,
this is Marcel aka WuotanStudios.

I wanted to share my lens-flare shader i am using in my project RIAD (Reborn in a dungeon).

Godot Version:
4.3-stable, Vulkan-Forward+

It is simple to use:
Step 1: create a new spatial shader and copy-paste the shader code.
Step 2: Create a Color-Rect node which is visible in the game (best make it a child of the player camera)
Step 3: Make sure that the Color-Rect node is in Full-Rect mode.
Step 4: assign a new material for the Color-Rect and use the shader as material.
Step 5: it already should work
Step 6: play with the value until you have the results you are seeking for.

 

Shader-Konzept:
It takes the rendered screen, and projects the bright parts of the render into the color-rect.
You can adjust the FlareThreshold value. The higher the value, the less of not so bright screen parts will be used. If the value is to high, nothing will be used as flare -> no flare effect. If the value is too small, you might see too much of the original screen render.

Another important parameter is blur and Thresholdsmoothness, to make the flare “round” and less sharp.
You might need to play around with all values a bit.

Bright levels might create too much weird effects.
I think this shader works better in dark or “less bright” games with a few lightsources / bright elements…

Ideas for the future of this shader:
I consider to use emissive-render,
where only textures and materials with emissive material create the lens-flare effect.
Currently it uses the normal render as basis.

Note:
This is a prototype shader.
There are still things i am not happy with.
Feel free to modify or improve the shader code further, and upload a better version here 😉

For more infos follow me on:

Twitter: https://x.com/WuotanStudios

Bluesky: https://bsky.app/profile/wuotanstudios.bsky.social

Striked: https://striked.gg/app/space/29-riad/news-about-riad

www.wuotanstudios.com

 

Shader code
//////////////////////////////////////////////////////////////////
//by Marcel Klee - WuotanStudios - 2024
//used in project "RIAD - Reborn in a Dungeon"
//A first person - story driven - action game [WIP]
//
//For more infos follow me on:
//Twitter: https://x.com/WuotanStudios
//Bluesky: https://bsky.app/profile/wuotanstudios.bsky.social
//Striked: https://striked.gg/app/space/29-riad/news-about-riad
//www.wuotanstudios.com
//////////////////////////////////////////////////////////////////

shader_type canvas_item;

render_mode blend_add;

uniform lowp sampler2D Screen_Sample : hint_screen_texture, filter_linear_mipmap_anisotropic;
uniform lowp sampler2D FlareMult;
uniform lowp sampler2D FlareMult2;

uniform float Blur = 2.5; //Blur the Flare after Threshold
uniform float FlareThreshold;
uniform float Thresholdsmoothness = 0.2; //Smoot the Threshold+Blur result
uniform int Flares = 3; //Flare Amount
uniform float FlareSpacing; //Testing-Status
uniform float FlareDistance = 0.5;  //Testing-Status
uniform float LensThickness = 1.0;  //Testing-Status

uniform float Intensity; //Intensity of the Flare Effect
uniform float Saturation_ = 5.0; // Saturate the lensflare effect
uniform float visibility = 1.0; //Effect visibility by using alpha


uniform float MinFlareIntensity = 0.0;  // Minimal brightness
uniform float MaxFlareIntensity = 1.0;  // Maximum brightness

uniform float RainbowIntensity = 1.0; // Intensity of rainbow effect


vec3 ApplyThreshold(vec3 color, float threshold){
	return color * smoothstep(threshold - Thresholdsmoothness, threshold + Thresholdsmoothness, length(color));
}


vec3 AdjustSaturation(vec3 color, float saturation) {
    float gray = dot(color, vec3(0.299, 0.587, 0.114)); // Luminanz-Calculation - NTSC-Standard
    return mix(vec3(gray), color, saturation);
}


// Convert from RGB to HSV and back
vec3 RGBToHSV(vec3 color) {
    float cmax = max(color.r, max(color.g, color.b));
    float cmin = min(color.r, min(color.g, color.b));
    float delta = cmax - cmin;
    
    float h = 0.0;
    if (delta != 0.0) {
        if (cmax == color.r) {
            h = mod(((color.g - color.b) / delta), 6.0);
        } else if (cmax == color.g) {
            h = ((color.b - color.r) / delta) + 2.0;
        } else {
            h = ((color.r - color.g) / delta) + 4.0;
        }
    }
    h /= 6.0;

    float s = (cmax == 0.0) ? 0.0 : delta / cmax;
    float v = cmax;

    return vec3(h, s, v);
}

vec3 HSVToRGB(vec3 hsv) {
    float h = hsv.x * 6.0;
    float s = hsv.y;
    float v = hsv.z;

    int i = int(h);
    float f = h - float(i);
    float p = v * (1.0 - s);
    float q = v * (1.0 - f * s);
    float t = v * (1.0 - (1.0 - f) * s);

    if (i == 0) return vec3(v, t, p);
    else if (i == 1) return vec3(q, v, p);
    else if (i == 2) return vec3(p, v, t);
    else if (i == 3) return vec3(p, q, v);
    else if (i == 4) return vec3(t, p, v);
    else return vec3(v, p, q);
}

// Ranbow Effect Function (Hue Shifting)
vec3 ApplyRainbowEffect(vec3 color, float factor) {
    vec3 hsv = RGBToHSV(color); // Convert to HSV
    hsv.x = mod(hsv.x + factor * RainbowIntensity, 1.0); // Adjust hue based on factor
    return HSVToRGB(hsv); // Convert back to RGB
}




void fragment(){
	vec2 FlippedUV = vec2(LensThickness) - SCREEN_UV;
	vec2 FlareVector = (vec2(FlareDistance) - SCREEN_UV) * FlareSpacing;
	vec3 FinalFlare = vec3(0.0);
	
	for (int i = 0; i < Flares; ++i){
		vec2 SUV = fract(SCREEN_UV + FlareVector * vec2(float(i)));
		float Dist = distance(SUV, vec2(0.5));
		float Weight = 1.0 - smoothstep(0.0, 0.75, Dist);
		vec3 BlurredScreen = texture(Screen_Sample, SUV, Blur).rgb;
		
		BlurredScreen = ApplyThreshold(BlurredScreen, FlareThreshold); //smoothing the screen (the blur effect can make hard edges)
		
		FinalFlare += BlurredScreen * Weight;
	}
	FinalFlare *= texture(FlareMult, SCREEN_UV).rgb;
	FinalFlare *= texture(FlareMult2, SCREEN_UV).rgb;
	FinalFlare *= Intensity;
	FinalFlare = clamp(FinalFlare, vec3(MinFlareIntensity), vec3(MaxFlareIntensity));

	COLOR.rgb = FinalFlare;
	COLOR.rgb = AdjustSaturation(FinalFlare, Saturation_);
	
	
	// Rainbow Effect based on the Flare-Position
	float factor = distance(SCREEN_UV, vec2(0.5)); // Factor can be based on the distance from the center or any other property
	FinalFlare = ApplyRainbowEffect(FinalFlare, factor);
	
	
	
	COLOR.rgb = FinalFlare;
	COLOR.a = visibility;
}
Tags
4, camera, flare, godot 4, godot4, lens, lens-flare, lensflare, shader
The shader code and all code snippets in this post are under CC0 license and can be used freely without the author's permission. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

Related shaders

Lens Flare Shader

Animated Screen Outline (flaming rainbow)

Colored Dithering

Subscribe
Notify of
guest

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Logical
Logical
15 days ago

Just wanted to say, you’re trying to apply a Spatial (3D) shader to a Canvas Rect (2D) node, that doesn’t work