UPBGE Bloom Shader


(HG1) #1

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)


(RockyMadio) #2

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…


(Cotaks) #3

Your first bloom.blend url is dead.


(BluePrintRandom) #4

Thank you Hg1 :smiley:


(HG1) #5

The first link should work now.


(BluePrintRandom) #6

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);
}

(BluePrintRandom) #7

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:


(BluePrintRandom) #8

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;
}

(bge man) #9

This is great. Thank you HG1 for the shader!


(ethicalfive) #10

thankyou HG1 !


(aWeakOwl) #11

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


(BluePrintRandom) #12

Thank you Dr.Owl :smiley:


(wkk.py) #13

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])