UPBGE Bloom Shader

BluePrintRandom asked me to convert this shader (https://www.shadertoy.com/view/lsBfRc) to run in UPBGE.

Original shader from shadertoy:
Bloom.blend (92.1 KB)

Modified shader using rendered screen result:
The result differs from the original shader (I can’t match up the colors and sRGB).
For normal color schemes the colorRange at line 9 in _Image.fs has to be set to a much lower value.
Bloom2.blend (92.3 KB)

11 Likes

that is cute :smiley: good job!

I have been trying to modify the shader as well ( not complete ) . My python script was almost as same as yours. I bindcoded the image of one filter to another one.

But this line 34 void mainImage( out vec4 fragColor, in vec2 fragCoord ) kinda solved the problem I had hahas…

Your first bloom.blend url is dead.

Thank you Hg1 :smiley:

The first link should work now.

2 Likes

do you think you can add a bloom threshold or clamping?

(like it will only bloom colors whose each channel values over A or under B?)

youle made a bloom for me a while back, and it did this making it easy to control what blooms, but it needed many passes and was not this ‘smooth’

uniform sampler2D bgl_RenderedTexture;


const float BRIGHT_PASS_THRESHOLD = 0.35;
const float BRIGHT_PASS_OFFSET = 0.25;


#define blurclamp 0.001
#define bias 10.8


#define KERNEL_SIZE 6.0


vec2 texcoord = vec2(gl_TexCoord[0]).st;


vec4 bright(vec2 coo)
{
    vec4 color = texture2D(bgl_RenderedTexture, coo);
    float threshMult = 0.0;
    if (((color.r > 1.25) || (color.r <= 0.45)) &&
        ((color.g > 1.25) || (color.g <= 0.45)) &&
        ((color.b > 1.25) || (color.b <= 0.45)))
    {
        threshMult = max(max(color.r, color.g), color.b);
    }

    color = max(color - BRIGHT_PASS_THRESHOLD, 0.0) * threshMult;
    return color / (color + BRIGHT_PASS_OFFSET) * 5.0;
}


void main(void)
{
	vec2 blur = vec2(clamp( bias, -blurclamp, blurclamp ));
	
	vec4 col = vec4( 0, 0, 0, 0 );
	for ( float x = -KERNEL_SIZE + 1.0; x < KERNEL_SIZE; x += 1.0 )
	{
	for ( float y = -KERNEL_SIZE + 1.0; y < KERNEL_SIZE; y += 1.0 )
	{
		 col += bright( texcoord + vec2( blur.x * x, blur.y * y ) );
	}
	}
	col /= ((KERNEL_SIZE+KERNEL_SIZE)-1.0)*((KERNEL_SIZE+KERNEL_SIZE)-1.0);
	gl_FragColor = col + texture2D(bgl_RenderedTexture, texcoord);
}
1 Like

ok - trying the shader on my own scene I think I see what is going on

is there anyway to mix back in the new image into the original based on the intensity of the pixel?

so bright things are bloomed, but everything else is not blurred?


it’s like they are in heaven atm :smiley:

1 Like

got it


_Image.fs


uniform float bgl_RenderedTextureWidth;
uniform float bgl_RenderedTextureHeight;
uniform sampler2D bgl_RenderedTexture;

vec2 iResolution = vec2(bgl_RenderedTextureWidth, bgl_RenderedTextureHeight); // viewport resolution (in pixels)
uniform sampler2D iChannel0; //! buffer[xbuf: 1, wrap: GL_CLAMP_TO_EDGE, mipmap: false, filter: GL_NEAREST]

#define colorRange 40.0

vec3 jodieReinhardTonemap(vec3 c){
    float l = dot(c, vec3(0.2126, 0.7152, 0.0722));
    vec3 tc = c / (c + 1.0);

    return mix(c / (l + 1.0), tc, tc);
}

vec3 bloomTile(float lod, vec2 offset, vec2 uv){
    return texture(iChannel0, uv * exp2(-lod) + offset).rgb;
}

vec3 getBloom(vec2 uv){

    vec3 blur = vec3(0.0);

    blur = pow(bloomTile(2., vec2(0.0,0.0), uv),vec3(2.2))       + blur;
    blur = pow(bloomTile(3., vec2(0.3,0.0), uv),vec3(2.2)) * 1.1 + blur;
    blur = pow(bloomTile(4., vec2(0.0,0.3), uv),vec3(2.2)) * 1.1 + blur;
    blur = pow(bloomTile(5., vec2(0.1,0.3), uv),vec3(2.2)) * 1.1 + blur;
    blur = pow(bloomTile(6., vec2(0.2,0.3), uv),vec3(2.2)) * 1.1 + blur;

    return blur * colorRange;
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
	vec2 uv = fragCoord.xy / iResolution.xy;
    
    vec3 color = pow(texture(bgl_RenderedTexture, uv).rgb * colorRange, vec3(2.2));
    color = pow(color, vec3(2.2));
    color += pow(getBloom(uv), vec3(2.2));
    color = pow(color, vec3(1.0 / 2.2));
    
    color = jodieReinhardTonemap(color);
    
	fragColor = vec4(color,1.0);
}

void main() {
    vec4 color = vec4(0.0, 0.0, 0.0, 1.0);
    mainImage(color, gl_FragCoord.xy);

    gl_FragColor = color;
}
1 Like

This is great. Thank you HG1 for the shader!

thankyou HG1 !

Hi, someone else asked me to use a similar method with 2D filters offscreens, then I share 2 new files using a backbuffer from a previous frame to do some effects. I tried to keep the files as simple as possible and the shaders are simple too but could be improved.
bufferPreviousFrame.blend (511.9 KB)
MotionBlur.blend (515.8 KB)
These files could be done with bge using ImageRender I think but it’s for upbge as it has an API for 2DFilter offscreens

3 Likes

Thank you Dr.Owl :smiley:

1 Like

Refactored the bufferPreviousFrame code into a Python component, could be better maybe but it is okay for what it does :slight_smile:

# Credits: inspired from HG1 work on a bloom filter ported in bge
# I guess this can be used to create motionblur effects...
# refact: wkk.py
import bge

shader = '''\
// Simple shader usecase

uniform vec2 mouse;
//uniform vec2 resolution;
uniform sampler2D bb;
uniform float bgl_RenderedTextureWidth;
uniform float bgl_RenderedTextureHeight;

vec2 resolution = vec2(bgl_RenderedTextureWidth, bgl_RenderedTextureHeight);

void main() {
	    float radius = .1;
        vec2 lightDir = mouse - (gl_FragCoord.xy / resolution.xy);
        lightDir.x *= resolution.x / resolution.y;
        float D = length(lightDir);

        vec2 L = normalize(lightDir);


        float md = max(D - radius, 0.) / radius + 1.;
        float at = 1. / (md*md);
        at = (at - .25)/(1.-.25);
        at = max(at, 0.);
	
	gl_FragColor = vec4(vec3(at), 1.) + 0.975 * texture2D(bb, (gl_FragCoord.xy / resolution.xy));	
}
'''

class BufferThing(bge.types.KX_PythonComponent):
    
    args = {}

    def start(self, args):
        scene = self.object.scene

        self.filter = scene.filterManager.addFilter(
            0, bge.logic.RAS_2DFILTER_CUSTOMFILTER, shader)

        # In this case we use the same shader for backbuffer and the main shader
        self.buffer = scene.filterManager.addFilter(
            1, bge.logic.RAS_2DFILTER_CUSTOMFILTER, shader)
        self.buffer.addOffScreen(2, hdr=bge.render.HDR_HALF_FLOAT)
        
        self.isFirstFrame = True
    
    def update(self):
        mouse = bge.logic.mouse
        
        if self.isFirstFrame:
            self.isFirstFrame = False
            return # skip first frame
        
        # The texture is the result of what was stored in the offscreen at the previous frame (backbuffer)
        self.filter.setTexture(0, self.buffer.offScreen.colorBindCodes[0], "bb")
        self.filter.setUniform2f("mouse", mouse.position[0], 1 - mouse.position[1])
      
        # Prepare the backbuffer image for the next frame (send it needed uniforms) (will be available in next logic frame)
        self.buffer.setTexture(0, self.buffer.offScreen.colorBindCodes[0], "bb")
        self.buffer.setUniform2f("mouse", mouse.position[0], 1 - mouse.position[1])
1 Like

@HG1 have any way to remove flicker?

https://blenderartists.org/uploads/default/original/4X/1/2/1/121a695b1cef547071f6fd4380eeb1f8febadf41.blend - the edit by youle does this using data from last frame.

Thanks, but how exactly do I implement this in Bloom shader?

good question - @aWeirdOwl - halp

2 Likes

Even tho my game struggles to run it, this is the best bloom filter out there, thanks.
Effect is great in my game, but i can barely run it DX.

5 Likes

I was able to edit _BufferA.fs and use bgl_DataTexture[0] as a offscreen bloom buffer :smiley:
now we can decide what color and how much a object blooms :smiley:

still having a little trouble controlling the final color

2 Likes

I was able to re-write this (my first real big glsl script!)

I had to make it update the buffer and then push the mip mapped image into the buffer without mipmapping it again mixed into the bloom attachment mip mapped :smiley:
Abuffer.fs



uniform float bgl_RenderedTextureWidth;
uniform float bgl_RenderedTextureHeight;
uniform sampler2D bgl_RenderedTexture;
uniform sampler2D bgl_DataTextures[7];
uniform sampler2D iChannel0;
//REplaced bge_RenderedTexture with this^^^


vec2 iResolution = vec2(bgl_RenderedTextureWidth, bgl_RenderedTextureHeight); // viewport resolution (in pixels)

vec3 makeBloom(float lod, vec2 offset, vec2 bCoord){
    
    vec2 pixelSize = 1.0 / vec2(iResolution.x, iResolution.y);

    offset += pixelSize;

    float lodFactor = exp2(lod);

    vec3 bloom = vec3(0.0);
    vec3 bloom2 = vec3(0.0);
    
    vec2 scale = lodFactor * pixelSize;

    vec2 coord = (bCoord.xy-offset)*lodFactor;
    float totalWeight = 0.0;

    if (any(greaterThanEqual(abs(coord - 0.5), scale + 0.5)))
        return vec3(0.0);

    for (int i = -5; i < 5; i++) {
        for (int j = -5; j < 5; j++) {

            float wg = pow(1.0-length(vec2(i,j)) * 0.125,6.0);

            bloom = pow(texture(iChannel0,gl_FragCoord.xy/ iResolution.xy).rgb,vec3(2.2))*wg + bloom;
            
            bloom2 =  pow(texture(bgl_DataTextures[0],vec2(i,j) * scale + lodFactor * pixelSize + coord, lod).rgb,vec3(2.2))*wg + bloom2;
            
            // mix amount is used to average old bloom buffer into new
            bloom = mix(bloom, bloom2,0.005);
            totalWeight += wg;

        }
    }

    bloom /= totalWeight;

    return bloom;
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = fragCoord / iResolution.xy;
    
	vec3 blur = makeBloom(2.,vec2(0.0,0.0), uv);
		blur += makeBloom(3.,vec2(0.3,0.0), uv);
		blur += makeBloom(4.,vec2(0.0,0.3), uv);
		blur += makeBloom(5.,vec2(0.1,0.3), uv);
		blur += makeBloom(6.,vec2(0.2,0.3), uv);

    fragColor = vec4(pow(blur, vec3(1.0 / 2.2)),1.0);
}

void main() {
    vec4 color = vec4(0.0, 0.0, 0.0, 1.0);
    mainImage(color, gl_FragCoord.xy);

    gl_FragColor = color;
}