Neon bloom
Glowing signs that bleed light into the dark around them, from a half-size blur of only the brightest pixels.
- 01GoalLens
- 02TechniqueBloom
- 03ExecutionNeon bloom
Parameters
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.
dotDotProductdotdotdot UVScreen01 Brightness
Pass bright
smoothstepSmoothStepsmoothstepsmoothstepsmoothstep In02 Keep the bright parts
Pass bright
texture * expExpressiontexture * exptexture * exptexture * exp In03 Blur them wide
Pass blurV
+Add+++ In04 Add over the scene
Pass main
/Divide/// In05 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-mapsRendering keeps light values above 1 in float buffers (HDR), then a tone-mapping curve compresses them into the 0–1 a screen can show. HDR & tone mapping Rendering keeps light values above 1 in float buffers (HDR), then a tone-mapping curve compresses them into the 0–1 a screen can show.Reinhard = c / (1 + c) the sum, so the shapes glow and the halo fades smoothly into the dark.
Step by step
- Brightness. The
brightpass reads the scene and takes its luminanceHow bright a color looks, from its linear red, green and blue weighted by the eye's sensitivity to each.FundamentalLuminance
How bright a color looks, from its linear red, green and blue weighted by the eye's sensitivity to each.
Y = 0.2126 R + 0.7152 G + 0.0722 BSources,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. - Keep the bright parts.
smoothstep(p_threshold, p_threshold + 0.5, luma)is 0 below the threshold and 1 half a unit above it, andkeptis 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. - Blur them wide. Two passes run a separable Gaussian over
kept, rows intot_blurH, then columns intot_blurV, whose result isglow. All three passes arehalfsize: 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. - Add over the scene. The last pass adds
glow × p_strengthto the full-resolution scene and applies the exposure,exp2(p_exposure), in stops.hdris now well above 1 around every light. - 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().
- Draw your scene into a framebuffer whose color attachment is an
RGBA16Ftexture, in linear light, with emissive surfaces well above 1. This ist_screen. - Create three
RGBA16Ftextures at half the canvas size (rounded up), each in its own framebuffer, withLINEARfiltering andCLAMP_TO_EDGE. - Draw pass
brightinto the first witht_screenbound:t_bright. - Draw pass
blurHinto the second witht_brightbound:t_blurH. Then passblurVinto the third witht_blurHbound:t_blurV. The blur passes find their texel size withtextureSize(), so they work at any size. - Bind the canvas, set the viewport back to full size, and draw pass
mainwitht_screenandt_blurVbound. It adds the glow, applies the exposure and the Reinhard curve, and writes sRGB. - Set
p_threshold,p_spread,p_strengthandp_exposureon 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.
- Add a
MeshInstance3Dwith a 2×2QuadMeshas a child of yourCamera3D. Give it aShaderMaterialwith passmain, put it on Visual Layer 3 only, and raise Extra Cull Margin to its maximum so it is never culled. - Add a
SubViewportforbright: Size half the window’s, Use HDR 2D on, Update Mode Always, Own World 3D off. Inside it add aCamera3Dthat your camera drives through aRemoteTransform3Dchild (match the FOV), and a 2×2 quad with passbrighton Visual Layer 2 only. - Add two more
SubViewports, forblurHandblurV: the same size and settings, but with Own World 3D on. Inside each add aCamera3Dand a 2×2 quad with its pass. Their worlds hold nothing else, so they draw only the quad. - Wire the textures:
t_brighton theblurHmaterial is aViewportTextureof thebrightviewport,t_blurHonblurVis theblurHviewport, andt_blurVonmainis theblurVviewport. - Set the Cull Masks: the
brightcamera sees layers 1 and 2, your camera layers 1 and 3. - Set
threshold,spread,strengthandexposureon 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
#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);}// 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.