Opacity and blending FBOs problem

I am trying to blend two FBOs and change the opacity of blending effect.
If I set a lower alpha value (blendOpacity parameter), the image becomes brighter. It makes me confused a little bit.
What am I doing wrong?

Here is a pseudo code:

        fbo1.begin()
            something...
        fbo1.end()
        
        fbo2.begin();
            ofClear(255, 255, 255, 0);
            fbo1.draw(0, 0);
            
            ofEnableBlendMode(blendMode); //OF_BLENDMODE_MULTIPLY
            ofSetColor(255, 255, 255, blendOpacity);
            pic.draw(0, 0);
            ofSetColor(255, 255, 255); 
            ofDisableBlendMode();
        fbo2.end();

Any screenshot?

I will prepare something. Maybe my thinking of opacity in that case is wrong. I would like to have something similar to photoshop that you can change the opacity of layer.

Something like that should work as the layer opacity of Photoshop:

fbo1.begin();
    //some code here
fbo1.end();

fbo2.begin();
    //some code here
fbo2.end();

ofSetColor(255, 255, 255, fbo1_opacity);
fbo1.draw(0, 0);
ofSetColor(255, 255, 255, fbo2_opacity);
fbo2.draw(0, 0);

I don’t see the reason why you draw the fbo1 onto the fbo2. You can draw them separately. Is there any particular reason for that?

Sorry but I was busy a little bit and I haven’t had time to answer. I need a few of fbos for my application, for example into one of them I am preparing a masked image, the last one is for saving image etc.
I have tried the same that you have proposed but it doesn’t work.
The code is available here:

The alpha blending is working fine. It is something strange with multiply blending. As I’ve described above, the image becomes brighter when the alpha is less.

I’ve checked how it works in graphical applications like Photoshop, Paint.NET and changing the opacity parameter doesn’t affect the image as in OF.

Can you try to clear with ofClear(0, 0, 0, 0)?

I tried this also. It doesn’t change anything.

Hi,
I’m interested in alpha compositing and blend modes, so I made some investigations.
I start with your code and try to obtain a multiply blendmode which give results similar to photoshop:

recordFbo.begin();
    ofClear(255, 255, 255, 0);
    aImg.draw(0, 0);
    ofEnableBlendMode(OF_BLENDMODE_MULTIPLY);
    ofSetColor(255, 255, 255, parOpacity);
    maskFbo.draw(0,0);
    ofSetColor(255, 255, 255);
    ofDisableBlendMode();
recordFbo.end();

From the ofGLRenderer::setBlendMode(ofBlendMode blendMode) definition we know:

	case OF_BLENDMODE_MULTIPLY:{
		glEnable(GL_BLEND);
		#ifndef TARGET_OPENGLES
			glBlendEquation(GL_FUNC_ADD);
		#endif
		glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA);
		break;
	}

The blend factors are GL_DST_COLOR and GL_ONE_MINUS_SRC_ALPHA. We can compute the blending result to understand what happens:

  • RGBA values for the recordFbo pixel: (Rs, Gs, Bs, As)
  • RGBA values for the aImg pixel: (Rd, Gd, Bd, Ad)

For the following calculations I assume the RGBA values are expressed in the 0…1 range. This is simplier than the 0…255 range.
According to this doc, the RGBA values for the result are

R = Rs * Rd + Rd * ( 1 - As )
G = Gs * Gd + Gd * ( 1 - As )
B = Bs * Bd + Bd * ( 1 - As )
A = As * Ad + Ad * ( 1 - As ) = Ad * ( As + 1 - As ) = Ad

When As = 1, (1 - As ) = ( 1 - 1 ) = 0, and we have this result

R = Rs * Rd
G = Gs * Gd
B = Bs * Bd

This is the expected calculation for the blend mode multiply.

But what happens when As is not 1 ?
To compute for exemple the red component we start the same, multiplying the Rs and Rd component. But this time we add something else:

R =   Rs * Rd           // same as previously
    + Rd * ( 1 - As )   // but with somthing else this time !

This is why the result become brighter when parOpacity is less than 255 in your code.

I did some tries to fix it using glBlendFunc, but I can’t figure how to do. So I dig further and found this. The main answer is pretty explicit. Following these advices and links, I wrote a shader for the multiply blending mode. I’m not sure this is a good practise to pass to the shader the texture in which it draw, but it works :slight_smile:

The fragment shader:

#version 150

uniform sampler2DRect tex0;
uniform sampler2DRect dstTex;
uniform float alpha;

in vec2 texCoordVarying;

out vec4 outputColor;

void main (void)
{
    // Get the pixels
    vec4 src = texture( tex0, texCoordVarying );
    vec4 dst = texture( dstTex, gl_FragCoord.xy );
    
    // Modify foreground with opacity
    src.a *= alpha;
    
    // Compute alpha result
    float a = src.a + dst.a * ( 1 - src.a );
    
    // Premultiply colors
    vec3 s = src.rgb * src.a;
    vec3 d = dst.rgb * dst.a;
    
    // Blendmode formula
    vec3 rgb = s * d + s * ( 1 - dst.a ) + d * ( 1 - src.a );
    
    // Unpremultiply
    outputColor = vec4( rgb / a, a );
}

Use case:

void ofApp::draw()
{
    // Draw img1 in fbo
    fbo.begin();
        ofClear( 255 );
        ofSetColor( 255, 255, 255, 200 );
        img1.draw( 0, 0 );
    fbo.end();

    // Draw img2 over img1 with multiply blend mode
    fbo.begin();
        shader.begin();
            shader.setUniform1f( "alpha", 150.f );
            shader.setUniformTexture( "dstTex", fbo.getTexture(), 1 );
            img2.draw( 100, 100, 300, 300 );
        shader.end();
    fbo.end();

    // Draw result
    ofSetColor( 255 );
    fbo.draw( 0, 0 );
}

Complete exemple:

multiply.zip (92.7 KB)

3 Likes

Great job. Thank you very much for your investigation. I am going to play with your code a little. Is it possible to make your shader more general or is it better to have different shaders for different blending modes? Maybe it’s possible to pass a parameter to shader and change the blending mode?

It is possible to have a parameter for the blending mode and to use conditionnal statements (if else, switch) to select the calculation to do in the shader code.
It can be better to have a dedicated shader for each blendmode, in performances terms. But I’m not sure about that, I haven’t found yet the proper documentation about conditionnal statements cost in shaders. If anybody have a hyperlink, you’re welcome!

Also, I modified the shader a little bit to avoid divide by 0 in the last line.
And I’ve forgot to call glBlendFunc before using the shader in my exemples. Here’s the corrections:

The shader

#version 150

uniform sampler2DRect tex0;
uniform sampler2DRect dstTex;
uniform float alpha;

in vec2 texCoordVarying;

out vec4 outputColor;

void main (void)
{
    // Get the pixels
    vec4 src = texture( tex0, texCoordVarying );
    vec4 dst = texture( dstTex, gl_FragCoord.xy );
    
    // Modify foreground with opacity
    src.a *= alpha;
    
    // Compute alpha result
    float a = src.a + dst.a * ( 1.0 - src.a );
    
    // Premultiply colors
    vec3 s = src.rgb * src.a;
    vec3 d = dst.rgb * dst.a;
    
    // Blendmode formula
    vec3 rgb = s * d + s * ( 1.0 - dst.a ) + d * ( 1.0 - src.a );
    
    // Unpremultiplier
    float unpre = 1.0 / ( a == 0.0 ? 1.0 : a );
    
    outputColor = vec4( rgb * unpre, a );
}

The use case

void ofApp::draw()
{
    // Draw img1 in fbo
    fbo.begin();
        ofClear( 255 );
        ofSetColor( 255, 255, 255, 200 );
        img1.draw( 0, 0 );
    fbo.end();

    // Draw img2 over img1 with multiply blend mode
    fbo.begin();
        shader.begin();
            shader.setUniform1f( "alpha", 150.f );
            shader.setUniformTexture( "dstTex", fbo.getTexture(), 1 );
            glBlendFunc( GL_ONE, GL_ZERO ) ;
            img2.draw( 100, 100, 300, 300 );
        shader.end();
    fbo.end();

    // Draw result
    ofSetColor( 255 );
    fbo.draw( 0, 0 );
}

And the exemple: multiply.zip (92.7 KB)
(I edit my previous post to update the zip)

Hi there!

blendingmodes.glsl.zip (2.5 KB)

This is a GLSL snippet with the following features:

BLEND MODES

None 0
Darken 1
Multiply 2
Color Burn 3
Linear Burn 4
Darker Color 5

Lighten 6
Screen 7
Color Dodge 8
Linear Dodge 9
Lighter Color 10

Overlay 11
Soft Light 12
Hard Light 13
Vivid Ligh 14
Linear Light 15
Pin Light 16
Hard Mix 17
Difference 18
Exclusion 19
Subtract 20
Divide 21

Hue 22
Saturation 23
Color 24
Luminosity 25

Average 26
Negation 27
Phoenix 28


PORTER DUFF COMPOSITES

Clear 0
Copy 1
Destination 2
Source Over 3
Destination Over 4
Source In 5
Destination In 6
Source Out 7
Destination In 8
Source Atop 9
Destination Atop 10
XOR 11
Lighter 12


COVERAGE MODES

None 0
Both 1
Source only 2
Destination only 3


After you include this, you just need to call:
vec4 blendMode(vec4 backdrop, vec4 source, int blendSelector, int compositeSelector, int coverageSelector)

Hope this is helpful!

Sources :
http://www.w3.org/TR/compositing-1
https://www.shadertoy.com/view/XdS3RW
http://stackoverflow.com/questions/5919663/how-does-photoshop-blend-two-images-together

1 Like

@lilive and @hubris
Thank you for your help. I will try your solutions.

Got stuck on the multiply alpha stuff today - found this neat add-on - https://github.com/Akira-Hayasaka/ofxPSBlend

1 Like

Hello, if anyone else gets stuck on this I worked out a really simple (and possibly wrong but it works for me) solution. Being too stupid to properly work with shaders I just switched my code so that the color is the same as the alpha when applying multiply as a blending mode. This makes it invisible at 0 opacity.

So:

ofSetColor(opacity, opacity );

Hope this helps someone.

2 Likes