Blur with transparent background

Hey,

I have a classic blur application using two fbo’s and blurring horizontal en vertical in two passes.
Made a little demo here.
https://vimeo.com/315749732

Everything is clear and works wel except calculating the alpha values for the edges.
Currently I calculate the alpha same way as the RGB which is probably the problem.
Did google it a bit but non of the solutions was working anybody have a clue or direction where I should search?

#version 150

out vec4 oColor;
in vec4 Color;
uniform sampler2D uTex0;


in vec2    TexCoord;

uniform vec2 sampleOffset;
float weights[21];

void main( void )
{
    
    //vec4 text1 = texture( uTex0, TexCoord.st  );
    
    weights[0] = 0.0091679276560113852;
    weights[1] = 0.014053461291849008;
    weights[2] = 0.020595286319257878;
    weights[3] = 0.028855245532226279;
    weights[4] = 0.038650411513543079;
    weights[5] = 0.049494378859311142;
    weights[6] = 0.060594058578763078;
    weights[7] = 0.070921288047096992;
    weights[8] = 0.079358891804948081;
    weights[9] = 0.084895951965930902;
    weights[10] = 0.086826196862124602;
    weights[11] = 0.084895951965930902;
    weights[12] = 0.079358891804948081;
    weights[13] = 0.070921288047096992;
    weights[14] = 0.060594058578763092;
    weights[15] = 0.049494378859311121;
    weights[16] = 0.0386504115135431;
    weights[17] = 0.028855245532226279;
    weights[18] = 0.020595286319257885;
    weights[19] = 0.014053461291849008;
    weights[20] = 0.00916792765601138;
    
    
    vec4 sum = vec4( 0.0, 0.0, 0.0,0.0 );
    vec2 baseOffset = -10.0 * sampleOffset;
    vec2 offset = vec2( 0.0, 0.0 );
    for( int s = 0; s < 21; ++s ) {

        vec4 c = texture( uTex0, TexCoord.st + baseOffset + offset ) *  weights[s];
		sum += c;
        offset += sampleOffset;
    }
    oColor = sum;
}

Hi,

I could not play the video, so I do not know what your current output looks like. But I can guess a little by looking at the code.

You may want to try using premultiplied alpha blending, as this in general leads to more accurate results. If your source texture is not premultiplied, do this as a first step in your shader:

vec4 c = texture( uTex0, TexCoord.st + baseOffset + offset );
vec4 premult = vec4( c.rgb * c.a, c.a );
sum += premult * weights[s];
offset += sampleOffset;

In the second blur pass, you don’t have to premultiply yourself anymore and the code can be the same as in your post. If you don’t like having 2 different shaders to do the blur, you can do the premultiplication in a shader pass of its own, before passing the result to the blur shader. Alternatively, you could use my code to premultiply the color, then use the following code to write “un-pre-multiplied” color to the output of the first blur pass. That way you only need one version of the blur shader:

float oneOverAlpha = sum.a > 0.0 ? 1.0 / sum.a : 0.0;
oColor.rgb = sum.rgb * oneOverAlpha;
oColor.a = sum.a;

However, if the output of your blur shader is pre-multiplied, you should draw the contents of the Fbo with premultiplied alpha blending enabled:

gl::ScopedBlendPremult scpBlend;
gl::draw( mFbo->getColorTexture() );

I’m not sure this will fix your issue, but it’s worth a try. Note that I wrote this code from scratch, so it may contain errors.

-Paul

Hey Paul,

Thx for the fast reply.
I changed the video settings so should be working now.

I tested your suggestion by doing the premultiplication in the frag shader only on the first pass.
It wasn’t working so I took some steps back.
The weird thing is that when I minimise the shader to only the basics.

vec4 c = texture( uTex0, TexCoord.st ) ;
vec4 premult =  vec4( c.rgb * c.a, c.a );

oColor = premult;


I get the result in the bottom left where as it should be the same as upper right.
This only the result of the first pass without any blurring. When I draw this with premultiplication on in the draw loop (as you suggested) the background gets completely white.

The program is really simple now so I must be overlooking something really simple?

class AlphaBlurTestApp : public App {

ci::gl::FboRef      mFboFirstPass;
ci::gl::FboRef      mFboSecondPass;
ci::gl::GlslProgRef mGlsl;
ci::gl::GlslProgRef mGlsl2;

ci::gl::TextureRef  mTexture;


public:
 void setup() override;
 void draw() override;
 void renderBlur(ci::ColorA backgroundColor);

};

  void AlphaBlurTestApp::setup()
  {
    mFboFirstPass = gl::Fbo::create(250,250);
    mFboSecondPass = gl::Fbo::create(250,250);

   try{
        mGlsl = gl::GlslProg::create(gl::GlslProg::Format().vertex(loadAsset("shaders/blur.vert"))
                                 .fragment(loadAsset("shaders/blur.frag")));
    }
    catch (gl::GlslProgCompileExc ex) {
    std::cout << ex.what() << std::endl;
   }

try{
    mGlsl2 = gl::GlslProg::create(gl::GlslProg::Format().vertex(loadAsset("shaders/blur.vert"))
                                 .fragment(loadAsset("shaders/blur2.frag")));
   }
    catch (gl::GlslProgCompileExc ex) {
    std::cout << ex.what() << std::endl;
}
  mTexture = gl::Texture::create(loadImage(loadAsset("UI/backButtonBlue.png")));

}

void AlphaBlurTestApp::renderBlur(ci::ColorA backgroundColor)
{
    float blurAmount = getMousePos().x / 200000.0f;

    gl::pushMatrices();
    gl::setMatricesWindow(mFboFirstPass->getSize());
    gl::ScopedViewport vp( ci::ivec2( 0 ), mFboFirstPass->getSize() );

// first pass
{
    gl::ScopedGlslProg scpProg(mGlsl);

    gl::ScopedFramebuffer scpFrame(mFboFirstPass);
    gl::clear(backgroundColor,true);

    mGlsl->uniform("sampleOffset", blurAmount * vec2(1,0));
    // enable premultiplied
    gl::ScopedBlendPremult scpBlend;
    gl::ScopedTextureBind tex0(mTexture, 0);
    gl::drawSolidRect(Rectf(20,20,20 + mTexture->getWidth(),20 + mTexture->getHeight()));
   }

    gl::popMatrices();
}

void AlphaBlurTestApp::draw()
{
    gl::clear( ColorA( 0.85, 0.85, 0.85,0 ),true );

    ci::gl::draw(mTexture,vec2(300,20));
    renderBlur(ColorA( 1, 1, 1,1 ));
    ci::gl::draw(mFboFirstPass->getColorTexture(),vec2(0,20));
    renderBlur(ColorA( 1, 1, 1, 0.0 ));

     {
       // not working makes the background white.
      // gl::ScopedBlendPremult scpBlend;
       ci::gl::draw(mFboFirstPass->getColorTexture(),vec2(0,220));
     }    
}

Try separating your blendmodes. Something like (when drawing into the fbo):

gl::context()->pushBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

3 Likes

OOOOOh super!
That fixed it. Would never found this.

Updated the code in git if anybody needs it

Maybe you forgot to push, because it only shows that you updated the png’s in the latest commit.

Ah your right, pushed the whole thing now.