X-Git-Url: https://git.sesse.net/?p=movit;a=blobdiff_plain;f=mix_effect.frag;h=450ca21f5ed285f349f330f431740fb6d75785b0;hp=f76881ba2519012fae38f65f3981f10ed009c6d4;hb=95edbfccb0843da3cc105dadc5bc6d8e102f6071;hpb=ef7665d0d3854b3464800d8d7fef9a90f14d9a9f;ds=sidebyside diff --git a/mix_effect.frag b/mix_effect.frag index f76881b..450ca21 100644 --- a/mix_effect.frag +++ b/mix_effect.frag @@ -1,5 +1,33 @@ vec4 FUNCNAME(vec2 tc) { vec4 first = INPUT1(tc); vec4 second = INPUT2(tc); - return vec4(PREFIX(strength_first)) * first + vec4(PREFIX(strength_second)) * second; + vec4 result = vec4(PREFIX(strength_first)) * first + vec4(PREFIX(strength_second)) * second; + + // Clamping alpha at some stage, either here or in AlphaDivisionEffect, + // is actually very important for some use cases. Consider, for instance, + // the case where we have additive blending (strength_first = strength_second = 1), + // and add two 50% gray 100% opaque (0.5, 0.5, 0.5, 1.0) pixels. Without + // alpha clamping, we'd get (1.0, 1.0, 1.0, 2.0), which would then in + // conversion to postmultiplied be divided back to (0.5, 0.5, 0.5)! + // Clamping alpha to 1.0 fixes the problem, and we get the expected result + // of (1.0, 1.0, 1.0). Similarly, adding (0.5, 0.5, 0.5, 0.5) to itself + // yields (1.0, 1.0, 1.0, 1.0) (100% white 100% opaque), which makes sense. + // + // The classic way of doing additive blending with premultiplied alpha + // is to give the additive component alpha=0, but this also doesn't make + // sense in a world where we could end up postmultiplied; just consider + // the case where we have first=(0, 0, 0, 0) (ie., completely transparent) + // and second=(0.5, 0.5, 0.5, 0.5) (ie., white at 50% opacity). + // Zeroing out the alpha of second would yield (0.5, 0.5, 0.5, 0.0), + // which has undefined RGB values in postmultiplied storage; certainly + // e.g. (0, 0, 0, 0) would not be an expected output. Also, it would + // break the expectation that A+B = B+A. + // + // Note that we do _not_ clamp RGB, since it might be useful to have + // out-of-gamut colors. We could choose to do the alpha clamping in + // AlphaDivisionEffect instead, though; I haven't thought a lot about + // if that would be better or not. + result.a = clamp(result.a, 0.0, 1.0); + + return result; }