Saturday, January 16, 2016

Blending Pixels

A task at work has recently required me to do some graphics stuff. I'll be the first to admit that as a learned discipline I'm not a graphics programmer. I enjoy almost any kind of programming and in my hobby work I find graphics programming especially fun. But simple things in that realm of the programming world I am embarrassingly ignorant of. Even something like... blending two pixels.

But! That wasn't going to stop me from researching and validating the right way to alpha blend two pixels together.

Now in reality, the task isn't so much for pixels, but rather for heightfield data. Now often heightfield data is just a single-channel pixel anyway, but in our engine heights are represented as floats. To expand the feature set of our terrain engine, our heightfield data needed to be modified to include layers and an alpha channel. So each heightfield sample is now two channels -- height and alpha.

Of course, googling and grabbing an algorithm isn't all there was to it. I wasn't really going to be satisfied with the answers I found unless I could test it myself. Sure, I could plug the code in and see what results I get, but it was much faster to throw up my favorite web app -- Desmos Graphing Calculator.



Compared to my last documented use of Desmos, this is absurdly straightforward. I'm visualizing the alpha channel along the x axis and the height channel along the y axis. I'll admit, It's a bit disorienting until you get used to it. Regardless, a/h3 is the resulting value. a/h1 and a/h2 are the inputs. Alpha values are normalized, so we're working with ranges between 0 and 1.

The formula for the output ends up looking like this in code. As usual this code is for illustrating the formula -- not for being performant code.


struct HeightSample
{
    float height, alpha;
}

void Blend(HeightSample& out, const HeightSample& a, const HeightSample& b)
{
    out.height = a.alpha * a.height + b.alpha * (1.0f - a.alpha) * b.height;
    out.alpha = a.alpha + ((1.0f - a.alpha) * b.alpha);
}
It's interesting to note that while the alpha blending is a commutative operation (that is, if you swap the input values, the output is the same), the same it not true of the height channel. This is because sample const HeightSample& a is considered the "top" pixel. They are not even commutative if both alpha values are 0.5. This isn't particularly intuitive, so I thought it was worth mentioning.

No comments:

Post a Comment