Getting my head round Blend Mode

Blend Mode

I am finding this the hardest challenge of shader programming.

Blend SrcFactor DstFactor

In my head I read this as: SrcFactor * colour that is generated from this pass + DstFactor * colour already in the framebuffer

But when faced with a blend such as

Blend OneMinusDstColor One

…which I am told on good authority constitutes ’ soft additive ’ blend, how do I get my brain around what this actually does?

Even this probably isn’t as hard as it gets; at least here the One says 'we take whatever was in the framebuffer already, and add something to it '. So this one is only halfway difficult.

Can someone take me through the mental process of conceptualising the visual effect of such a blend?

I think things will start to make sense when you think of colours as numbers. Remember that black is 0, and white is 1.

If you add black, you get the original (add 0), if you multiply by black you’ll get black (multiply by 0).

If you add white, you get original + 1, if you multiply by white, you’ll get the original (original * 1).

Usual additive blend would be:

Blend One One

You just add the colors. Problem is: you might end up with color channels > 1, which are clamped to 1. This is somewhat problematic for various reasons.

How much color can you add to a color (R, G, B) in the framebuffer before you get this clamp? Answer: (1-R, 1-G, 1-B) because (1-R, 1-G, 1-B) + (R, G, B) = (1, 1, 1). Thus, (1-R, 1-G, 1-B), i.e. OneMinusDstColor is the maximum amount of color that you can add without color clamping after blending.

By multiplying the fragment color with OneMinusDstColor, you basically map full white (1,1,1) to this maximum color (1-R, 1-G, 1-B). That’s at least how I understand “Blend OneMinusDstColor One”.

That’s a very good explanation of the rationale behind that blend function.

Aaaaaaaaaaah! :slight_smile:

so if the shader pass spits out white (1,1,1), the blend function will map this as follows:

Blend SrcFactor DstFactor  
   // ->   SrcFactor * newColor  +  DstFactor * RGB already in the framebuffer

Blend OneMinusDstColor One 
   // -> OneMinusDstColor * newColor + One * RGB already in the framebuffer
   // =  (1-R, 1-G, 1-B) * (1,1,1)  +  One * ( R, G, B )
   // = White!

So it literally is a ‘brightening’ function; it moves whatever is on the frame buffer towards white, but it makes sure it can never exceed white, so you wouldn’t get that flattening constant colour effect from clamping.

Awesome – Thanks!

There was no reason to create the name “soft additive”; it’s been called “screen” for a very long time. People love to overcomplicate screen:

…But really, all screening is, is shifting a color a percentage of the way up to white. Screening with black? No movement up to white. White? All the way there. Like Martin says, nothing goes past 1.

Ah! Great link. Now things are clearer for me:
As far as I know fixed-function GPU blending was mainly designed to allow for the blending modes defined by Porter and Duff: http://keithp.com/~keithp/porterduff/p253-porter.pdf
Unfortunately, Porter and Duff didn’t include all the modes that are now available in Photoshop; thus, some of the Photoshop blending modes can be implemented with GPU blending and others cannot. I guess that’s why Photoshop blending modes and their names are not that well known among computer graphics programmers. (I’ve never heard of “screen” blending before.)
That also explains why the Photoshop guys at WWDC considered programmable blending such a big step forward.

I suppose the simplest way of explaining it to programmers might be:

lerp(dstColor, 1, srcColor)

That’s perfect!

So these blend operations form a pair:

// lerp( dstColor, 0,  srcColor ), i.e. move existing pixel towards black: DARKEN
Blend DstColor Zero

// lerp( dstColor, 1,  srcColor ), i.e. move existing pixel towards white: LIGHTEN
Blend OneMinusDstColor One

No, you have to do the math:

lerp(dstColor, 1, srcColor) = dstColor*(1-srcColor)+1srcColor = dstColor - dstColorsrcColor + srcColor = (1-dstColor)srcColor + 1dstColor

That’s how you get the factors OneMinusDstColor and One. However:

lerp(dstColor, 0, srcColor) = dstColor*(1-srcColor) + 0srcColor = dstColor - dstColorsrcColor != dstColorsrcColor + 0dstColor

Thus, you cannot use Blend DstColor Zero for this, but:

Blend Zero OneMinusSrcColor