Skip to content
Lens · BloomIntermediate

Neon bloom

Glowing signs that bleed light into the dark around them, from a half-size blur of only the brightest pixels.

UVScreenScreen / post
  1. 01GoalLens
  2. 02TechniqueBloom
  3. 03ExecutionNeon bloom
Test scene

Parameters

1.00
4.0
1.00
0.5

Copy for your platform

  • GLSLAdapt
  • GodotAdapt

Node chain

Built step by step, in GLSL style, with its node names.

Built step by step, in Godot style, with its node names.

No Unreal Engine 5 version of this card yet, so the nodes show the GLSL names.

No Unity version of this card yet, so the nodes show the GLSL names.

No Blender version of this card yet, so the nodes show the GLSL names.

  1. dotDotProductdotdotdot
    UV
    Screen

    01 Brightness

    Pass bright

  2. smoothstepSmoothStepsmoothstepsmoothstepsmoothstep

    02 Keep the bright parts

    Pass bright

  3. texture * expExpressiontexture * exptexture * exptexture * exp

    03 Blur them wide

    Pass blurV

  4. +Add+++

    04 Add over the scene

    Pass main

  5. /Divide///

    05 Tone map

    Pass main

How it works

The test scene holds emissive shapes far brighter than 1. Without help they would clip to flat white strokes. This card keeps only those bright pixels, blurs them wide at half resolution, adds the blur back over the frame and then tone-maps the sum, so the shapes glow and the halo fades smoothly into the dark.

Step by step

  1. Brightness. The bright pass reads the scene and takes its luminance, luma: the color dotted with how bright each channel looks to the eye (green counts most, blue least). One number per pixel decides whether it glows, so a saturated blue light and a white one of the same brightness behave alike.
  2. Keep the bright parts. smoothstep(p_threshold, p_threshold + 0.5, luma) is 0 below the threshold and 1 half a unit above it, and kept is the scene color scaled by it. The soft ramp is a knee: a surface that brightens slowly fades into the glow instead of popping on at one exact value.
  3. Blur them wide. Two passes run a separable Gaussian over kept, rows into t_blurH, then columns into t_blurV, whose result is glow. All three passes are half size: a quarter of the pixels, and every tap spans two screen pixels, so the halo reaches twice as far for the same number of reads.
  4. Add over the scene. The last pass adds glow × p_strength to the full-resolution scene and applies the exposure, exp2(p_exposure), in stops. hdr is now well above 1 around every light.
  5. Tone map. hdr / (1.0 + hdr) (Reinhard’s curve) brings any value into 0–1 without clipping: 1 becomes 0.5, 4 becomes 0.8. The core of each sign stays brighter than its halo, with a gradient between them instead of a flat white plateau.

Why the threshold needs HDR

A bright pass works only if bright still means something. In an 8-bit frame a white shirt and a neon tube are both 1.0, and both would glow. In the half-float target the tube is 6.0 and the shirt 0.8, so a threshold of 1 picks the tube alone. The same is true for tone mapping: it has to run after the add, on the HDR sum, or the glow has no headroom.

Production notes

Counted per pixel of each pass, the chain reads 1 + 25 + 25 + 2 = 53 texels and does about 565 operations at the default spread (σ = 4, 25 taps per blur pass). Three of the four passes run at half size, though, so per pixel of the screen it is closer to 15 reads: the half-size trick is most of the reason bloom is affordable. Memory is three half-size half-float targets, plus the HDR scene the frame already has.

The bright pass reads the screen once at a half-size pixel’s center, which falls exactly between four screen texels, so the bilinear filter averages them for free. That average can still flicker when a single very bright pixel moves across the grid (fireflies); production bloom weights that downsample by 1 / (1 + luma) to tame them.

One blur size gives a halo with one falloff. Real lenses scatter with a sharp core and a very long tail, which engines build by blurring a chain of smaller and smaller copies and adding them all up. Reach for that when one Gaussian looks like a uniform fog around the light.

Reinhard on each channel desaturates bright colors toward white, which suits neon. For a whole game, use the same tone mapper as the rest of the frame (ACES, AgX, Khronos PBR Neutral) and put bloom just before it.

Make it in GLSL

Four fragment shaders, one per pass. Render the scene into a half-float texture, draw passes "bright", "blurH" and "blurV" in turn into three half-size half-float textures, each reading the one before, then draw pass "main" to the canvas with the scene and the last blur bound.

Each pass is a full-screen draw: a triangle covering the viewport, with a vertex shader that writes v_uv from 0 to 1 across it. The copied code holds the four fragment shaders, each with its own main().

  1. Draw your scene into a framebuffer whose color attachment is an RGBA16F texture, in linear light, with emissive surfaces well above 1. This is t_screen.
  2. Create three RGBA16F textures at half the canvas size (rounded up), each in its own framebuffer, with LINEAR filtering and CLAMP_TO_EDGE.
  3. Draw pass bright into the first with t_screen bound: t_bright.
  4. Draw pass blurH into the second with t_bright bound: t_blurH. Then pass blurV into the third with t_blurH bound: t_blurV. The blur passes find their texel size with textureSize(), so they work at any size.
  5. Bind the canvas, set the viewport back to full size, and draw pass main with t_screen and t_blurV bound. It adds the glow, applies the exposure and the Reinhard curve, and writes sRGB.
  6. Set p_threshold, p_spread, p_strength and p_exposure on every program that declares them.

For a longer tail, repeat steps 3 and 4 on a quarter-size copy and add both blurs in the last pass, each with its own strength.

Make it in Godot

Four spatial shaders, chained through three half-size SubViewports. Pass "bright" renders the world from a camera that follows yours; "blurH" and "blurV" each draw only their quad in an Own World 3D viewport, reading the one before through a ViewportTexture; pass "main" runs on a full-screen quad in front of your camera. In most projects Godot's own Glow (Environment) is the better choice; this chain is for a look Glow can't give, or to match the web version.

The copied code is four shaders, one per pass. Godot draws a post pass as a mesh in front of a camera whose vertex shader stretches it over the screen, and a pass reads an earlier one only through a ViewportTexture.

  1. Add a MeshInstance3D with a 2×2 QuadMesh as a child of your Camera3D. Give it a ShaderMaterial with pass main, put it on Visual Layer 3 only, and raise Extra Cull Margin to its maximum so it is never culled.
  2. Add a SubViewport for bright: Size half the window’s, Use HDR 2D on, Update Mode Always, Own World 3D off. Inside it add a Camera3D that your camera drives through a RemoteTransform3D child (match the FOV), and a 2×2 quad with pass bright on Visual Layer 2 only.
  3. Add two more SubViewports, for blurH and blurV: the same size and settings, but with Own World 3D on. Inside each add a Camera3D and a 2×2 quad with its pass. Their worlds hold nothing else, so they draw only the quad.
  4. Wire the textures: t_bright on the blurH material is a ViewportTexture of the bright viewport, t_blurH on blurV is the blurH viewport, and t_blurV on main is the blurV viewport.
  5. Set the Cull Masks: the bright camera sees layers 1 and 2, your camera layers 1 and 3.
  6. Set threshold, spread, strength and exposure on every material; each declares them all.

For the bright pass to catch anything, emissive materials need Emission Energy well above 1: Godot keeps the 3D frame in HDR, so a neon tube at energy 6 reads as 6 in the screen texture.

No Unreal Engine 5 tutorial for this card yet.

No Unity tutorial for this card yet.

No Blender tutorial for this card yet.

Code

neon-bloom.glslGLSL
#version 300 es// Bloom → Neon bloom, pass "bright" (1 of 4), web GLSL (GLSL ES 3.00). From Creative Center, MIT license.precision highp float;uniform float u_time;uniform vec2 u_resolution; // this pass's output size in pixelsuniform float p_threshold; // Threshold: 1.0 (0.0 to 4.0)uniform float p_spread; // Spread (sigma, half-size px): 4.0 (1.0 to 8.0)uniform float p_strength; // Strength: 1.0 (0.0 to 3.0)uniform float p_exposure; // Exposure (stops): 0.5 (-2.0 to 2.0)uniform sampler2D t_screen; // the scene's color: linear HDR (RGBA16F), linear filter, clamp to edgeuniform sampler2D t_bright; // pass "bright"'s output: linear HDR (RGBA16F), half the output's size, linear filter, clamp to edgeuniform sampler2D t_blurH; // pass "blurH"'s output: linear HDR (RGBA16F), half the output's size, linear filter, clamp to edgeuniform sampler2D t_blurV; // pass "blurV"'s output: linear HDR (RGBA16F), half the output's size, linear filter, clamp to edgein vec2 v_uv;in vec3 v_position;in vec3 v_normal;in vec3 v_view;out vec4 fragColor;struct Inputs {	vec2 uv;	vec3 position;	vec3 normal;	vec3 view;	float time;	vec2 fragCoord;	vec2 resolution;};// Bloom: keep only what is brighter than the threshold, blur it wide at half size, add it back over the scene, then// tone-map, since the sum goes well above 1.vec4 bright(Inputs i) {	// This pass is half size: each of its pixels sits between four screen texels, so one bilinear read averages them	vec3 scene = texture(t_screen, i.uv).rgb;	float luma = dot(scene, vec3(0.2126, 0.7152, 0.0722));	// A soft knee: light fades in over half a unit above the threshold, so a glow doesn't pop on as a surface brightens	vec3 kept = scene * smoothstep(p_threshold, p_threshold + 0.5, luma);	return vec4(kept, 1.0);}// One axis of a Gaussian, taps one texel apart out to three sigmas; both callers pass a pass's own texturevec3 gaussian(sampler2D source, vec2 uv, vec2 axis) {	vec2 texel = axis / vec2(textureSize(source, 0));	int reach = int(ceil(3.0 * p_spread));	vec3 sum = vec3(0.0);	float total = 0.0;	for (int k = -reach; k <= reach; k++) {		float w = exp(-0.5 * float(k * k) / (p_spread * p_spread));		sum += texture(source, uv + texel * float(k)).rgb * w;		total += w;	}	return sum / total;}vec4 blurH(Inputs i) {	vec3 wide = gaussian(t_bright, i.uv, vec2(1.0, 0.0));	return vec4(wide, 1.0);}vec4 blurV(Inputs i) {	vec3 glow = gaussian(t_blurH, i.uv, vec2(0.0, 1.0));	return vec4(glow, 1.0);}vec4 effect(Inputs i) {	vec3 scene = texture(t_screen, i.uv).rgb;	vec3 hdr = (scene + texture(t_blurV, i.uv).rgb * p_strength) * exp2(p_exposure);	// Reinhard: 1 maps to 0.5 and anything brighter approaches 1 without clipping, so the glow's core keeps a gradient	vec3 mapped = hdr / (1.0 + hdr);	return vec4(mapped, 1.0);}void main() {	Inputs i;	i.uv = v_uv;	i.position = v_position;	// Interpolation shortens the vectors between vertices	i.normal = normalize(v_normal);	i.view = normalize(v_view);	i.time = u_time;	i.fragCoord = gl_FragCoord.xy;	i.resolution = u_resolution;	// Linear HDR into this pass's target, read by the passes after it	fragColor = bright(i);}#version 300 es// Bloom → Neon bloom, pass "blurH" (2 of 4), web GLSL (GLSL ES 3.00). From Creative Center, MIT license.precision highp float;uniform float u_time;uniform vec2 u_resolution; // this pass's output size in pixelsuniform float p_threshold; // Threshold: 1.0 (0.0 to 4.0)uniform float p_spread; // Spread (sigma, half-size px): 4.0 (1.0 to 8.0)uniform float p_strength; // Strength: 1.0 (0.0 to 3.0)uniform float p_exposure; // Exposure (stops): 0.5 (-2.0 to 2.0)uniform sampler2D t_screen; // the scene's color: linear HDR (RGBA16F), linear filter, clamp to edgeuniform sampler2D t_bright; // pass "bright"'s output: linear HDR (RGBA16F), half the output's size, linear filter, clamp to edgeuniform sampler2D t_blurH; // pass "blurH"'s output: linear HDR (RGBA16F), half the output's size, linear filter, clamp to edgeuniform sampler2D t_blurV; // pass "blurV"'s output: linear HDR (RGBA16F), half the output's size, linear filter, clamp to edgein vec2 v_uv;in vec3 v_position;in vec3 v_normal;in vec3 v_view;out vec4 fragColor;struct Inputs {	vec2 uv;	vec3 position;	vec3 normal;	vec3 view;	float time;	vec2 fragCoord;	vec2 resolution;};// Bloom: keep only what is brighter than the threshold, blur it wide at half size, add it back over the scene, then// tone-map, since the sum goes well above 1.vec4 bright(Inputs i) {	// This pass is half size: each of its pixels sits between four screen texels, so one bilinear read averages them	vec3 scene = texture(t_screen, i.uv).rgb;	float luma = dot(scene, vec3(0.2126, 0.7152, 0.0722));	// A soft knee: light fades in over half a unit above the threshold, so a glow doesn't pop on as a surface brightens	vec3 kept = scene * smoothstep(p_threshold, p_threshold + 0.5, luma);	return vec4(kept, 1.0);}// One axis of a Gaussian, taps one texel apart out to three sigmas; both callers pass a pass's own texturevec3 gaussian(sampler2D source, vec2 uv, vec2 axis) {	vec2 texel = axis / vec2(textureSize(source, 0));	int reach = int(ceil(3.0 * p_spread));	vec3 sum = vec3(0.0);	float total = 0.0;	for (int k = -reach; k <= reach; k++) {		float w = exp(-0.5 * float(k * k) / (p_spread * p_spread));		sum += texture(source, uv + texel * float(k)).rgb * w;		total += w;	}	return sum / total;}vec4 blurH(Inputs i) {	vec3 wide = gaussian(t_bright, i.uv, vec2(1.0, 0.0));	return vec4(wide, 1.0);}vec4 blurV(Inputs i) {	vec3 glow = gaussian(t_blurH, i.uv, vec2(0.0, 1.0));	return vec4(glow, 1.0);}vec4 effect(Inputs i) {	vec3 scene = texture(t_screen, i.uv).rgb;	vec3 hdr = (scene + texture(t_blurV, i.uv).rgb * p_strength) * exp2(p_exposure);	// Reinhard: 1 maps to 0.5 and anything brighter approaches 1 without clipping, so the glow's core keeps a gradient	vec3 mapped = hdr / (1.0 + hdr);	return vec4(mapped, 1.0);}void main() {	Inputs i;	i.uv = v_uv;	i.position = v_position;	// Interpolation shortens the vectors between vertices	i.normal = normalize(v_normal);	i.view = normalize(v_view);	i.time = u_time;	i.fragCoord = gl_FragCoord.xy;	i.resolution = u_resolution;	// Linear HDR into this pass's target, read by the passes after it	fragColor = blurH(i);}#version 300 es// Bloom → Neon bloom, pass "blurV" (3 of 4), web GLSL (GLSL ES 3.00). From Creative Center, MIT license.precision highp float;uniform float u_time;uniform vec2 u_resolution; // this pass's output size in pixelsuniform float p_threshold; // Threshold: 1.0 (0.0 to 4.0)uniform float p_spread; // Spread (sigma, half-size px): 4.0 (1.0 to 8.0)uniform float p_strength; // Strength: 1.0 (0.0 to 3.0)uniform float p_exposure; // Exposure (stops): 0.5 (-2.0 to 2.0)uniform sampler2D t_screen; // the scene's color: linear HDR (RGBA16F), linear filter, clamp to edgeuniform sampler2D t_bright; // pass "bright"'s output: linear HDR (RGBA16F), half the output's size, linear filter, clamp to edgeuniform sampler2D t_blurH; // pass "blurH"'s output: linear HDR (RGBA16F), half the output's size, linear filter, clamp to edgeuniform sampler2D t_blurV; // pass "blurV"'s output: linear HDR (RGBA16F), half the output's size, linear filter, clamp to edgein vec2 v_uv;in vec3 v_position;in vec3 v_normal;in vec3 v_view;out vec4 fragColor;struct Inputs {	vec2 uv;	vec3 position;	vec3 normal;	vec3 view;	float time;	vec2 fragCoord;	vec2 resolution;};// Bloom: keep only what is brighter than the threshold, blur it wide at half size, add it back over the scene, then// tone-map, since the sum goes well above 1.vec4 bright(Inputs i) {	// This pass is half size: each of its pixels sits between four screen texels, so one bilinear read averages them	vec3 scene = texture(t_screen, i.uv).rgb;	float luma = dot(scene, vec3(0.2126, 0.7152, 0.0722));	// A soft knee: light fades in over half a unit above the threshold, so a glow doesn't pop on as a surface brightens	vec3 kept = scene * smoothstep(p_threshold, p_threshold + 0.5, luma);	return vec4(kept, 1.0);}// One axis of a Gaussian, taps one texel apart out to three sigmas; both callers pass a pass's own texturevec3 gaussian(sampler2D source, vec2 uv, vec2 axis) {	vec2 texel = axis / vec2(textureSize(source, 0));	int reach = int(ceil(3.0 * p_spread));	vec3 sum = vec3(0.0);	float total = 0.0;	for (int k = -reach; k <= reach; k++) {		float w = exp(-0.5 * float(k * k) / (p_spread * p_spread));		sum += texture(source, uv + texel * float(k)).rgb * w;		total += w;	}	return sum / total;}vec4 blurH(Inputs i) {	vec3 wide = gaussian(t_bright, i.uv, vec2(1.0, 0.0));	return vec4(wide, 1.0);}vec4 blurV(Inputs i) {	vec3 glow = gaussian(t_blurH, i.uv, vec2(0.0, 1.0));	return vec4(glow, 1.0);}vec4 effect(Inputs i) {	vec3 scene = texture(t_screen, i.uv).rgb;	vec3 hdr = (scene + texture(t_blurV, i.uv).rgb * p_strength) * exp2(p_exposure);	// Reinhard: 1 maps to 0.5 and anything brighter approaches 1 without clipping, so the glow's core keeps a gradient	vec3 mapped = hdr / (1.0 + hdr);	return vec4(mapped, 1.0);}void main() {	Inputs i;	i.uv = v_uv;	i.position = v_position;	// Interpolation shortens the vectors between vertices	i.normal = normalize(v_normal);	i.view = normalize(v_view);	i.time = u_time;	i.fragCoord = gl_FragCoord.xy;	i.resolution = u_resolution;	// Linear HDR into this pass's target, read by the passes after it	fragColor = blurV(i);}#version 300 es// Bloom → Neon bloom, pass "main" (4 of 4), web GLSL (GLSL ES 3.00). From Creative Center, MIT license.precision highp float;uniform float u_time;uniform vec2 u_resolution; // this pass's output size in pixelsuniform float p_threshold; // Threshold: 1.0 (0.0 to 4.0)uniform float p_spread; // Spread (sigma, half-size px): 4.0 (1.0 to 8.0)uniform float p_strength; // Strength: 1.0 (0.0 to 3.0)uniform float p_exposure; // Exposure (stops): 0.5 (-2.0 to 2.0)uniform sampler2D t_screen; // the scene's color: linear HDR (RGBA16F), linear filter, clamp to edgeuniform sampler2D t_bright; // pass "bright"'s output: linear HDR (RGBA16F), half the output's size, linear filter, clamp to edgeuniform sampler2D t_blurH; // pass "blurH"'s output: linear HDR (RGBA16F), half the output's size, linear filter, clamp to edgeuniform sampler2D t_blurV; // pass "blurV"'s output: linear HDR (RGBA16F), half the output's size, linear filter, clamp to edgein vec2 v_uv;in vec3 v_position;in vec3 v_normal;in vec3 v_view;out vec4 fragColor;struct Inputs {	vec2 uv;	vec3 position;	vec3 normal;	vec3 view;	float time;	vec2 fragCoord;	vec2 resolution;};vec3 u_toSrgb(vec3 c) {	return mix(c * 12.92, 1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, step(0.0031308, c));}// Bloom: keep only what is brighter than the threshold, blur it wide at half size, add it back over the scene, then// tone-map, since the sum goes well above 1.vec4 bright(Inputs i) {	// This pass is half size: each of its pixels sits between four screen texels, so one bilinear read averages them	vec3 scene = texture(t_screen, i.uv).rgb;	float luma = dot(scene, vec3(0.2126, 0.7152, 0.0722));	// A soft knee: light fades in over half a unit above the threshold, so a glow doesn't pop on as a surface brightens	vec3 kept = scene * smoothstep(p_threshold, p_threshold + 0.5, luma);	return vec4(kept, 1.0);}// One axis of a Gaussian, taps one texel apart out to three sigmas; both callers pass a pass's own texturevec3 gaussian(sampler2D source, vec2 uv, vec2 axis) {	vec2 texel = axis / vec2(textureSize(source, 0));	int reach = int(ceil(3.0 * p_spread));	vec3 sum = vec3(0.0);	float total = 0.0;	for (int k = -reach; k <= reach; k++) {		float w = exp(-0.5 * float(k * k) / (p_spread * p_spread));		sum += texture(source, uv + texel * float(k)).rgb * w;		total += w;	}	return sum / total;}vec4 blurH(Inputs i) {	vec3 wide = gaussian(t_bright, i.uv, vec2(1.0, 0.0));	return vec4(wide, 1.0);}vec4 blurV(Inputs i) {	vec3 glow = gaussian(t_blurH, i.uv, vec2(0.0, 1.0));	return vec4(glow, 1.0);}vec4 effect(Inputs i) {	vec3 scene = texture(t_screen, i.uv).rgb;	vec3 hdr = (scene + texture(t_blurV, i.uv).rgb * p_strength) * exp2(p_exposure);	// Reinhard: 1 maps to 0.5 and anything brighter approaches 1 without clipping, so the glow's core keeps a gradient	vec3 mapped = hdr / (1.0 + hdr);	return vec4(mapped, 1.0);}void main() {	Inputs i;	i.uv = v_uv;	i.position = v_position;	// Interpolation shortens the vectors between vertices	i.normal = normalize(v_normal);	i.view = normalize(v_view);	i.time = u_time;	i.fragCoord = gl_FragCoord.xy;	i.resolution = u_resolution;	vec4 frag_out = effect(i);	// The output is sRGB: clamped and encoded once, here, with no tone mapping; opaque, so alpha is 1	fragColor = vec4(u_toSrgb(clamp(frag_out.rgb, 0.0, 1.0)), 1.0);}
neon-bloom.gdshaderGodot shader
// Bloom → Neon bloom, pass "bright" (1 of 4), Godot 4. From Creative Center, MIT license.// Godot has no pass chain on one material: draw this pass in its own SubViewport (or a CompositorEffect) and// give its ViewportTexture to the passes that read t_bright.shader_type spatial;// A full-screen quad over the finished frame: no lighting, no culling, never hidden by depthrender_mode unshaded, cull_disabled, depth_test_disabled;uniform float threshold : hint_range(0.0, 4.0, 0.05) = 1.0;uniform float spread : hint_range(1.0, 8.0, 0.1) = 4.0;uniform float strength : hint_range(0.0, 3.0, 0.05) = 1.0;uniform float exposure : hint_range(-2.0, 2.0, 0.1) = 0.5;uniform sampler2D t_screen : hint_screen_texture, filter_linear, repeat_disable;uniform sampler2D t_bright : filter_linear, repeat_disable; // pass "bright"'s output: its SubViewport's ViewportTextureuniform sampler2D t_blurH : filter_linear, repeat_disable; // pass "blurH"'s output: its SubViewport's ViewportTextureuniform sampler2D t_blurV : filter_linear, repeat_disable; // pass "blurV"'s output: its SubViewport's ViewportTexturestruct Inputs {	vec2 uv;	vec3 position;	vec3 normal;	vec3 view;	float time;	vec2 fragCoord;	vec2 resolution;};// Godot's textures start at the top-left and the effect's uv at the bottom-left: each sample flips v backvec2 u_flipUv(vec2 uv) {	return vec2(uv.x, 1.0 - uv.y);}// Bloom: keep only what is brighter than the threshold, blur it wide at half size, add it back over the scene, then// tone-map, since the sum goes well above 1.vec4 bright(Inputs i) {	// This pass is half size: each of its pixels sits between four screen texels, so one bilinear read averages them	vec3 scene = texture(t_screen, u_flipUv(i.uv)).rgb;	float luma = dot(scene, vec3(0.2126, 0.7152, 0.0722));	// A soft knee: light fades in over half a unit above the threshold, so a glow doesn't pop on as a surface brightens	vec3 kept = scene * smoothstep(threshold, threshold + 0.5, luma);	return vec4(kept, 1.0);}// One axis of a Gaussian, taps one texel apart out to three sigmas; both callers pass a pass's own texturevec3 gaussian(sampler2D source, vec2 uv, vec2 axis) {	vec2 texel = axis / vec2(textureSize(source, 0));	int reach = int(ceil(3.0 * spread));	vec3 sum = vec3(0.0);	float total = 0.0;	for (int k = -reach; k <= reach; k++) {		float w = exp(-0.5 * float(k * k) / (spread * spread));		sum += texture(source, u_flipUv(uv + texel * float(k))).rgb * w;		total += w;	}	return sum / total;}vec4 blurH(Inputs i) {	vec3 wide = gaussian(t_bright, i.uv, vec2(1.0, 0.0));	return vec4(wide, 1.0);}vec4 blurV(Inputs i) {	vec3 glow = gaussian(t_blurH, i.uv, vec2(0.0, 1.0));	return vec4(glow, 1.0);}vec4 effect(Inputs i) {	vec3 scene = texture(t_screen, u_flipUv(i.uv)).rgb;	vec3 hdr = (scene + texture(t_blurV, u_flipUv(i.uv)).rgb * strength) * exp2(exposure);	// Reinhard: 1 maps to 0.5 and anything brighter approaches 1 without clipping, so the glow's core keeps a gradient	vec3 mapped = hdr / (1.0 + hdr);	return vec4(mapped, 1.0);}void vertex() {	// Put the shader on a MeshInstance3D with a 2x2 QuadMesh in front of the camera; it covers the screen wherever it is	POSITION = vec4(VERTEX.xy, 1.0, 1.0);}void fragment() {	Inputs i;	// SCREEN_UV starts at the top-left; the effect expects GLSL's bottom-left	i.uv = vec2(SCREEN_UV.x, 1.0 - SCREEN_UV.y);	// A flat quad facing the viewer, the same as the site's 2D preview	i.position = vec3(i.uv * 2.0 - 1.0, 0.0);	i.normal = vec3(0.0, 0.0, 1.0);	i.view = vec3(0.0, 0.0, 1.0);	i.time = TIME;	// FRAGCOORD's origin is the top-left too	i.resolution = VIEWPORT_SIZE;	i.fragCoord = vec2(FRAGCOORD.x, i.resolution.y - FRAGCOORD.y);	vec4 frag_out = bright(i);	ALBEDO = frag_out.rgb;}// Bloom → Neon bloom, pass "blurH" (2 of 4), Godot 4. From Creative Center, MIT license.// Godot has no pass chain on one material: draw this pass in its own SubViewport (or a CompositorEffect) and// give its ViewportTexture to the passes that read t_blurH.shader_type spatial;// A full-screen quad over the finished frame: no lighting, no culling, never hidden by depthrender_mode unshaded, cull_disabled, depth_test_disabled;uniform float threshold : hint_range(0.0, 4.0, 0.05) = 1.0;uniform float spread : hint_range(1.0, 8.0, 0.1) = 4.0;uniform float strength : hint_range(0.0, 3.0, 0.05) = 1.0;uniform float exposure : hint_range(-2.0, 2.0, 0.1) = 0.5;uniform sampler2D t_screen : hint_screen_texture, filter_linear, repeat_disable;uniform sampler2D t_bright : filter_linear, repeat_disable; // pass "bright"'s output: its SubViewport's ViewportTextureuniform sampler2D t_blurH : filter_linear, repeat_disable; // pass "blurH"'s output: its SubViewport's ViewportTextureuniform sampler2D t_blurV : filter_linear, repeat_disable; // pass "blurV"'s output: its SubViewport's ViewportTexturestruct Inputs {	vec2 uv;	vec3 position;	vec3 normal;	vec3 view;	float time;	vec2 fragCoord;	vec2 resolution;};// Godot's textures start at the top-left and the effect's uv at the bottom-left: each sample flips v backvec2 u_flipUv(vec2 uv) {	return vec2(uv.x, 1.0 - uv.y);}// Bloom: keep only what is brighter than the threshold, blur it wide at half size, add it back over the scene, then// tone-map, since the sum goes well above 1.vec4 bright(Inputs i) {	// This pass is half size: each of its pixels sits between four screen texels, so one bilinear read averages them	vec3 scene = texture(t_screen, u_flipUv(i.uv)).rgb;	float luma = dot(scene, vec3(0.2126, 0.7152, 0.0722));	// A soft knee: light fades in over half a unit above the threshold, so a glow doesn't pop on as a surface brightens	vec3 kept = scene * smoothstep(threshold, threshold + 0.5, luma);	return vec4(kept, 1.0);}// One axis of a Gaussian, taps one texel apart out to three sigmas; both callers pass a pass's own texturevec3 gaussian(sampler2D source, vec2 uv, vec2 axis) {	vec2 texel = axis / vec2(textureSize(source, 0));	int reach = int(ceil(3.0 * spread));	vec3 sum = vec3(0.0);	float total = 0.0;	for (int k = -reach; k <= reach; k++) {		float w = exp(-0.5 * float(k * k) / (spread * spread));		sum += texture(source, u_flipUv(uv + texel * float(k))).rgb * w;		total += w;	}	return sum / total;}vec4 blurH(Inputs i) {	vec3 wide = gaussian(t_bright, i.uv, vec2(1.0, 0.0));	return vec4(wide, 1.0);}vec4 blurV(Inputs i) {	vec3 glow = gaussian(t_blurH, i.uv, vec2(0.0, 1.0));	return vec4(glow, 1.0);}vec4 effect(Inputs i) {	vec3 scene = texture(t_screen, u_flipUv(i.uv)).rgb;	vec3 hdr = (scene + texture(t_blurV, u_flipUv(i.uv)).rgb * strength) * exp2(exposure);	// Reinhard: 1 maps to 0.5 and anything brighter approaches 1 without clipping, so the glow's core keeps a gradient	vec3 mapped = hdr / (1.0 + hdr);	return vec4(mapped, 1.0);}void vertex() {	// Put the shader on a MeshInstance3D with a 2x2 QuadMesh in front of the camera; it covers the screen wherever it is	POSITION = vec4(VERTEX.xy, 1.0, 1.0);}void fragment() {	Inputs i;	// SCREEN_UV starts at the top-left; the effect expects GLSL's bottom-left	i.uv = vec2(SCREEN_UV.x, 1.0 - SCREEN_UV.y);	// A flat quad facing the viewer, the same as the site's 2D preview	i.position = vec3(i.uv * 2.0 - 1.0, 0.0);	i.normal = vec3(0.0, 0.0, 1.0);	i.view = vec3(0.0, 0.0, 1.0);	i.time = TIME;	// FRAGCOORD's origin is the top-left too	i.resolution = VIEWPORT_SIZE;	i.fragCoord = vec2(FRAGCOORD.x, i.resolution.y - FRAGCOORD.y);	vec4 frag_out = blurH(i);	ALBEDO = frag_out.rgb;}// Bloom → Neon bloom, pass "blurV" (3 of 4), Godot 4. From Creative Center, MIT license.// Godot has no pass chain on one material: draw this pass in its own SubViewport (or a CompositorEffect) and// give its ViewportTexture to the passes that read t_blurV.shader_type spatial;// A full-screen quad over the finished frame: no lighting, no culling, never hidden by depthrender_mode unshaded, cull_disabled, depth_test_disabled;uniform float threshold : hint_range(0.0, 4.0, 0.05) = 1.0;uniform float spread : hint_range(1.0, 8.0, 0.1) = 4.0;uniform float strength : hint_range(0.0, 3.0, 0.05) = 1.0;uniform float exposure : hint_range(-2.0, 2.0, 0.1) = 0.5;uniform sampler2D t_screen : hint_screen_texture, filter_linear, repeat_disable;uniform sampler2D t_bright : filter_linear, repeat_disable; // pass "bright"'s output: its SubViewport's ViewportTextureuniform sampler2D t_blurH : filter_linear, repeat_disable; // pass "blurH"'s output: its SubViewport's ViewportTextureuniform sampler2D t_blurV : filter_linear, repeat_disable; // pass "blurV"'s output: its SubViewport's ViewportTexturestruct Inputs {	vec2 uv;	vec3 position;	vec3 normal;	vec3 view;	float time;	vec2 fragCoord;	vec2 resolution;};// Godot's textures start at the top-left and the effect's uv at the bottom-left: each sample flips v backvec2 u_flipUv(vec2 uv) {	return vec2(uv.x, 1.0 - uv.y);}// Bloom: keep only what is brighter than the threshold, blur it wide at half size, add it back over the scene, then// tone-map, since the sum goes well above 1.vec4 bright(Inputs i) {	// This pass is half size: each of its pixels sits between four screen texels, so one bilinear read averages them	vec3 scene = texture(t_screen, u_flipUv(i.uv)).rgb;	float luma = dot(scene, vec3(0.2126, 0.7152, 0.0722));	// A soft knee: light fades in over half a unit above the threshold, so a glow doesn't pop on as a surface brightens	vec3 kept = scene * smoothstep(threshold, threshold + 0.5, luma);	return vec4(kept, 1.0);}// One axis of a Gaussian, taps one texel apart out to three sigmas; both callers pass a pass's own texturevec3 gaussian(sampler2D source, vec2 uv, vec2 axis) {	vec2 texel = axis / vec2(textureSize(source, 0));	int reach = int(ceil(3.0 * spread));	vec3 sum = vec3(0.0);	float total = 0.0;	for (int k = -reach; k <= reach; k++) {		float w = exp(-0.5 * float(k * k) / (spread * spread));		sum += texture(source, u_flipUv(uv + texel * float(k))).rgb * w;		total += w;	}	return sum / total;}vec4 blurH(Inputs i) {	vec3 wide = gaussian(t_bright, i.uv, vec2(1.0, 0.0));	return vec4(wide, 1.0);}vec4 blurV(Inputs i) {	vec3 glow = gaussian(t_blurH, i.uv, vec2(0.0, 1.0));	return vec4(glow, 1.0);}vec4 effect(Inputs i) {	vec3 scene = texture(t_screen, u_flipUv(i.uv)).rgb;	vec3 hdr = (scene + texture(t_blurV, u_flipUv(i.uv)).rgb * strength) * exp2(exposure);	// Reinhard: 1 maps to 0.5 and anything brighter approaches 1 without clipping, so the glow's core keeps a gradient	vec3 mapped = hdr / (1.0 + hdr);	return vec4(mapped, 1.0);}void vertex() {	// Put the shader on a MeshInstance3D with a 2x2 QuadMesh in front of the camera; it covers the screen wherever it is	POSITION = vec4(VERTEX.xy, 1.0, 1.0);}void fragment() {	Inputs i;	// SCREEN_UV starts at the top-left; the effect expects GLSL's bottom-left	i.uv = vec2(SCREEN_UV.x, 1.0 - SCREEN_UV.y);	// A flat quad facing the viewer, the same as the site's 2D preview	i.position = vec3(i.uv * 2.0 - 1.0, 0.0);	i.normal = vec3(0.0, 0.0, 1.0);	i.view = vec3(0.0, 0.0, 1.0);	i.time = TIME;	// FRAGCOORD's origin is the top-left too	i.resolution = VIEWPORT_SIZE;	i.fragCoord = vec2(FRAGCOORD.x, i.resolution.y - FRAGCOORD.y);	vec4 frag_out = blurV(i);	ALBEDO = frag_out.rgb;}// Bloom → Neon bloom, pass "main" (4 of 4), Godot 4. From Creative Center, MIT license.// The last pass: it reads the earlier passes' ViewportTextures through their t_ uniforms.shader_type spatial;// A full-screen quad over the finished frame: no lighting, no culling, never hidden by depthrender_mode unshaded, cull_disabled, depth_test_disabled;uniform float threshold : hint_range(0.0, 4.0, 0.05) = 1.0;uniform float spread : hint_range(1.0, 8.0, 0.1) = 4.0;uniform float strength : hint_range(0.0, 3.0, 0.05) = 1.0;uniform float exposure : hint_range(-2.0, 2.0, 0.1) = 0.5;uniform sampler2D t_screen : hint_screen_texture, filter_linear, repeat_disable;uniform sampler2D t_bright : filter_linear, repeat_disable; // pass "bright"'s output: its SubViewport's ViewportTextureuniform sampler2D t_blurH : filter_linear, repeat_disable; // pass "blurH"'s output: its SubViewport's ViewportTextureuniform sampler2D t_blurV : filter_linear, repeat_disable; // pass "blurV"'s output: its SubViewport's ViewportTexturestruct Inputs {	vec2 uv;	vec3 position;	vec3 normal;	vec3 view;	float time;	vec2 fragCoord;	vec2 resolution;};// Godot's textures start at the top-left and the effect's uv at the bottom-left: each sample flips v backvec2 u_flipUv(vec2 uv) {	return vec2(uv.x, 1.0 - uv.y);}// Bloom: keep only what is brighter than the threshold, blur it wide at half size, add it back over the scene, then// tone-map, since the sum goes well above 1.vec4 bright(Inputs i) {	// This pass is half size: each of its pixels sits between four screen texels, so one bilinear read averages them	vec3 scene = texture(t_screen, u_flipUv(i.uv)).rgb;	float luma = dot(scene, vec3(0.2126, 0.7152, 0.0722));	// A soft knee: light fades in over half a unit above the threshold, so a glow doesn't pop on as a surface brightens	vec3 kept = scene * smoothstep(threshold, threshold + 0.5, luma);	return vec4(kept, 1.0);}// One axis of a Gaussian, taps one texel apart out to three sigmas; both callers pass a pass's own texturevec3 gaussian(sampler2D source, vec2 uv, vec2 axis) {	vec2 texel = axis / vec2(textureSize(source, 0));	int reach = int(ceil(3.0 * spread));	vec3 sum = vec3(0.0);	float total = 0.0;	for (int k = -reach; k <= reach; k++) {		float w = exp(-0.5 * float(k * k) / (spread * spread));		sum += texture(source, u_flipUv(uv + texel * float(k))).rgb * w;		total += w;	}	return sum / total;}vec4 blurH(Inputs i) {	vec3 wide = gaussian(t_bright, i.uv, vec2(1.0, 0.0));	return vec4(wide, 1.0);}vec4 blurV(Inputs i) {	vec3 glow = gaussian(t_blurH, i.uv, vec2(0.0, 1.0));	return vec4(glow, 1.0);}vec4 effect(Inputs i) {	vec3 scene = texture(t_screen, u_flipUv(i.uv)).rgb;	vec3 hdr = (scene + texture(t_blurV, u_flipUv(i.uv)).rgb * strength) * exp2(exposure);	// Reinhard: 1 maps to 0.5 and anything brighter approaches 1 without clipping, so the glow's core keeps a gradient	vec3 mapped = hdr / (1.0 + hdr);	return vec4(mapped, 1.0);}void vertex() {	// Put the shader on a MeshInstance3D with a 2x2 QuadMesh in front of the camera; it covers the screen wherever it is	POSITION = vec4(VERTEX.xy, 1.0, 1.0);}void fragment() {	Inputs i;	// SCREEN_UV starts at the top-left; the effect expects GLSL's bottom-left	i.uv = vec2(SCREEN_UV.x, 1.0 - SCREEN_UV.y);	// A flat quad facing the viewer, the same as the site's 2D preview	i.position = vec3(i.uv * 2.0 - 1.0, 0.0);	i.normal = vec3(0.0, 0.0, 1.0);	i.view = vec3(0.0, 0.0, 1.0);	i.time = TIME;	// FRAGCOORD's origin is the top-left too	i.resolution = VIEWPORT_SIZE;	i.fragCoord = vec2(FRAGCOORD.x, i.resolution.y - FRAGCOORD.y);	vec4 frag_out = effect(i);	ALBEDO = frag_out.rgb;}

No Unreal Engine 5 code for this card yet.

No Unity code for this card yet.

No Blender code for this card yet.

Further reading