Bloom post processing for viewports
Hello!
This shader targets bloom (or HDR glow) post processing for viewports being rendered in ViewportTextures. This is a “correctly” way to sample HDR pixels without interference from neighbour pixels (this happens with textureLod).
How to use it:
Apply a ShaderMaterial with this shader in a TextureRect with a ViewportTexture into it. The properties of this shader are:
-bloomRadius: As the name sugests, is the radius of the glow, mainly being the distance from the fragment pixel to the neighbour pixels to be sampled;
-bloomThreshold: It’s the minimum color value that will show a glow, defaults to 1.0 so any color with RGB values higher than 1.0 will create the glow;
-bloomIntensity: It’s a value that multiplies the glow color, increasing the brightness of the glow (helps to compensate the threshold).
The way it works is relatively simple:
-Sample from 8 neighbour pixels of the current fragment pixels (offseted by the bloom radius), for each sample, it has a weight that influence more or less the final result (pixels at diagonal influence less than pixels at horizontal and vertical).
-The sampled pixel is subtracted by the bloom threshold, so only pixels with color values higher than 1.0 (or custom threshold) composes the final glow.
-The sample is blurred using custom bilinear filtering, giving a better look to the final result.
-The final glow color, multiplied by the glow intensity, is added with the original pixel color, giving the final result.
It is designed for performance and not quality, so you can use it for mobile projects, for example.
Sadly, it only works for GLES3 as it supports HDR color, not being useful in GLES2 unless you set a threshold that’s lower than 1.0.
Any feedback is really appreciated!
Shader code
shader_type canvas_item;
uniform float bloomRadius = 1.0;
uniform float bloomThreshold = 1.0;
uniform float bloomIntensity = 1.0;
vec3 GetBloomPixel(sampler2D tex, vec2 uv, vec2 texPixelSize) {
vec2 uv2 = floor(uv / texPixelSize) * texPixelSize;
uv2 += texPixelSize * .001;
vec3 tl = max(texture(tex, uv2).rgb - bloomThreshold, 0.0);
vec3 tr = max(texture(tex, uv2 + vec2(texPixelSize.x, 0.0)).rgb - bloomThreshold, 0.0);
vec3 bl = max(texture(tex, uv2 + vec2(0.0, texPixelSize.y)).rgb - bloomThreshold, 0.0);
vec3 br = max(texture(tex, uv2 + vec2(texPixelSize.x, texPixelSize.y)).rgb - bloomThreshold, 0.0);
vec2 f = fract( uv / texPixelSize );
vec3 tA = mix( tl, tr, f.x );
vec3 tB = mix( bl, br, f.x );
return mix( tA, tB, f.y );
}
vec3 GetBloom(sampler2D tex, vec2 uv, vec2 texPixelSize) {
vec3 bloom = vec3(0.0);
vec2 off = vec2(1) * texPixelSize * bloomRadius;
bloom += GetBloomPixel(tex, uv + off * vec2(-1, -1), texPixelSize * bloomRadius) * 0.292893;
bloom += GetBloomPixel(tex, uv + off * vec2(-1, 0), texPixelSize * bloomRadius) * 0.5;
bloom += GetBloomPixel(tex, uv + off * vec2(-1, 1), texPixelSize * bloomRadius) * 0.292893;
bloom += GetBloomPixel(tex, uv + off * vec2(0, -1), texPixelSize * bloomRadius) * 0.5;
bloom += GetBloomPixel(tex, uv + off * vec2(0, 0), texPixelSize * bloomRadius) * 1.0;
bloom += GetBloomPixel(tex, uv + off * vec2(0, 1), texPixelSize * bloomRadius) * 0.5;
bloom += GetBloomPixel(tex, uv + off * vec2(1, -1), texPixelSize * bloomRadius) * 0.292893;
bloom += GetBloomPixel(tex, uv + off * vec2(1, 0), texPixelSize * bloomRadius) * 0.5;
bloom += GetBloomPixel(tex, uv + off * vec2(1, 1), texPixelSize * bloomRadius) * 0.292893;
bloom /= 4.171573f;
return bloom;
}
void fragment() {
vec4 col = texture(TEXTURE, UV);
vec3 bloom = GetBloom(TEXTURE, UV, TEXTURE_PIXEL_SIZE);
col.rgb += bloom * bloomIntensity;
COLOR = col;
}
This doesn’t seem to work properly on Godot 4. I’m changing the radius property but nothing seems to change.
Which rendering method are you using? Do you have HDR 2D enabled in the Project Settings (this is only supported in Forward+/Mobile in Godot 4.2 and later)?
I’m having some difficulty getting this shader to work in Godot 4.3
I have a CanvasLayer with a TextureRect and SubViewport children, and the TextureRect is using a ViewportTexture with the path to the SubViewport. This is the intended setup, yes? Or did I misunderstand how to use this shader?
Using a simple greyscale shader with this exact setup works just fine, but using this shader results in the view being covered in a blank ViewportTexture and I can’t see anything.
I have HDR 2D enabled and using default Forward+ renderer.
same issue here