Game Development Community

Sprite Masking...

by Matt Van Gorkom · in Torque Game Builder · 04/04/2005 (6:37 am) · 11 replies

It'd be nice if we could mask a sprite with another sprite. Tilemaps also. Right now this would be useful for me for doing something like shadows and underwater caustics (the little squiggly light rays), but it'd be useful for a lot of other things as well.

#1
04/04/2005 (11:19 am)
You can use blending to create effects similar to this. Effects like shadows/caustics would be easy to do. Don't forget that all T2D objects have quite precise blending control via "fxSceneObject2D::setBlending()".

In the update, the tile-editor allows you to edit the blending properties per tile-layer.

- Melv.
#2
04/04/2005 (11:27 am)
I need to find a good OpenGL reference that explains the blending options... any suggestions Melv ?
#3
04/04/2005 (11:54 am)
But does setBlending() work for just my player sprite, for instance, without affecting the background? I guess I'm wanting to affect only certain groups/layers/sprites, but not everything else around it
For instance:
I have my player running in and out of shadows, but the shadows are at an angle so the player doesn't actually enter the shadows before he begins overlapping them....I'm really not saying this very well.
OK, think of a cookie-cutter... but for sprites.


Hmmm...I too have yet to find any good OpenGL reference on blending.
#4
04/04/2005 (12:25 pm)
Quote:I need to find a good OpenGL reference that explains the blending options... any suggestions Melv ?

You could ask me.

GL blending is both pretty simple and rather complicated. T2D adds some complexity to this, especially in that it doesn't use the correct nomenclature.

Note: do not be offended if the following explaination is a bit... simple-minded. I want to start from the beginning and explain everything so that there is no chance of a misunderstanding.

OK, everything in T2D is rendered (when using OpenGL) as textured triangles. When GL is rendering a triangle/line/etc, it converts the object into a bunch of things called "fragments". An OpenGL fragment is the set of state information that, when combined, computes the source pixel color.

So, what is this state? Well, in the case of a single-textured triangle, it is the texel color (filtered with bilinear filtering) at the pixel being rendered into. However, it is also the interpolated per-fragment color passed in by the user when they rendered a triangle. These two colors are combined via a simple operation to produce the source pixel color.

T2D has this thing called the "BlendColor". A horrible misnomer, as it has nothing to do with blending directly. Instead, it is the per-fragment color I spoke of earlier. T2D passes in a single color value for the entire triangle, so each point along the triangle has the same per-fragment interpolated color. The so-called "BlendColor".

The default operation that T2D does between the "BlendColor" and the texel color is multiply. That is, a 4-component multiply operation. Now, colors (at this stage in the pipe) are numbers between 0.0 and 1.0. Multiplication is standard floating-point multiplication (though hardware doesn't have to actually do floating-point multiplies. It can do an integer approximation to 8 bits).

How does multiplying colors work? It works well, if that's what you're looking for. Basically, when you multiply two colors, you are guarenteed to get a resulting color that is <= to either of the colors. So, multiplying makes things darker, not brighter. If you want colors brighter, you need to start with brighter initial colors and darken them up with the "BlendColor". We'll get to practical applications later.

But, of course, this just computes the "source pixel color", as I pointed out before. So, how does blending work? Well, first, we need some nomenclature:

Let O(rgba) be the final output color we right to the screen. It is a 4-element vector. Let D(rgba) be the color that is currently at this position on the screen, and S(rgba) be the color as computed from the above step; the "source pixel color". Let Pn(rgba) be a 4-vector color computed based on parameters that the user sets, where n is either 0 or 1. O(rgb) would be just the RGB components of the output color.

The blending equation OpenGL uses is:

O(rgba) = (S(rgba) * P0(rgba)) + (D(rgba) * P1(rgba))

Pretty simple right ;)

OK, there's more. Obviously, what this equation does depends greatly on P0 and P1. So, what are P0 and P1? Well, they come from the arguments to setBlending(). Of course, these arguments are the same as the arguments to glBlendFunc(). So, here are the possible blending arguments.

GL_ZERO (0,1)
GL_ONE (0,1)
GL_SRC_COLOR (1)
GL_ONE_MINUS_SRC_COLOR (1)
GL_DST_COLOR (0)
GL_ONE_MINUS_DST_COLOR (0)
GL_SRC_ALPHA (0,1)
GL_ONE_MINUS_SRC_ALPHA (0,1)
GL_DST_ALPHA (0,1)
GL_ONE_MINUS_DST_ALPHA (0,1)
GL_SRC_ALPHA_SATURATE (0)

The numbers beside the parameters tell you whether the parameter can be used as P0 or P1 or both. Some parameters can only be used on one of them, while others can be both.

#5
04/04/2005 (12:26 pm)
So, what do these parameters mean? Well, here we go:

GL_ZERO: P(rgba) = (0,0,0,0)
GL_ONE: P(rgba) = (1,1,1,1)

These two mean exactly what they say. Multiply anything by 0 and you get 0. Multiply by 1 and you get the same thing. So, if the equation you're looking for is the standard, non-blended one (ie: O(rgba) = S(rgba)), you use:

setBlending(GL_ONE, GL_ZERO), because the GL_ONE will multiply S(rgba) by 1 (retaining it) and GL_ZERO will multiply D(rgba) by 0, thus making it 0. 0 + S(rgba) = S(rgba) = O(rgba). Simple, right?

Now, on to more complex ones:

GL_SRC_COLOR: P(rgba) = S(rgba)
GL_DST_COLOR: P(rgba) = D(rgba)

Simple enough to understand. However, note that you are not allowed to multiply source by source or dest by dest; the parameter usage forbids it. But, you can multiply source by dest. Why would you want to? Simple: lighting.

Lighting is done by darkenning a surface that is already at 100% illumination (obviously, real lighting works a bit different, but we'll go with this hack). So, we render the surface once; it gets written into the destination. Now, we want to darken it based on some texture that represents illumination at various points on the surface. Or, we are drawing some kind of light into the view, and we want to have this light darken objects on-screen. The blend equation we want is: O(rgba) = S(rgba) * D(rgba). We obtain this with:

setBlending(GL_DST_COLOR, GL_ZERO); or
setBlending(GL_ZERO, GL_SRC_COLOR);

Now, for something more complex.

GL_ONE_MINUS_SRC_COLOR: P(rgba) = (1 - S(r), 1 - S(g), 1 - S(b), 1 - S(a))
GL_ONE_MINUS_DST_COLOR: P(rgba) = (1 -DS(r), 1 - D(g), 1 - D(b), 1 - D(a))

OK, what good is that? Well, admittedly, the 1-src/dest color blenders aren't terribly useful. Or, at least, they aren't useful for normal things. Just think about the color values and how multiplication/additions works out, and you might come up with a use for them some day. But probably not much of one.

#6
04/04/2005 (12:26 pm)
These next ones are far more useful:

GL_SRC_ALPHA: P(rgba) = (S(a), S(a), S(a), S(a))
GL_ONE_MINUS_SRC_ALPHA: P(rgba) = (1 - S(a), 1 - S(a), 1 - S(a), 1 - S(a))
GL_DST_ALPHA: P(rgba) = (D(a), D(a), D(a), D(a))
GL_ONE_MINUS_DST_ALPHA: P(rgba) = (1 - D(a), 1 - D(a), 1 - D(a), 1 - D(a))

What are these used for? Well, they're used for alpha blending.

Alpha blending means using a linear blend operation between two colors. Typically, this is a linear blend between the source and destination color. Linear blending operations take the form:

Color1 * a + Color2 * (1 - a).

Let's say we have a sprite. And we want that sprite to fade out or to look transluscent. Well, we use a linear blend on it. The equation we want is: O(rgba) = S(rgba) * S(a) + D(rgba) * (1 - S(a)). So, we use:

setBlending(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

But wait, where does the source alpha come from? Well, this is where Melv gets the pretext to call it the "BlendColor". Typically, images don't have alpha values embedded in them, or if they do, they're for masking off unseen parts of a sprite. However, if you want to control the transluscency of a sprite, you set the "BlendColor"'s alpha to some value less than 1.0. The RGB components should be 1.0 (unless you want to tint the sprite as well). The color interacts with blending, but in a very indirect way that doesn't require blending in order to be effective.

When is the destination alpha useful? Well, it isn't useful often, but occassionally it is. There are tricks you can play where you render some alpha values (from a texture) into the frame buffer and then alpha-blend based on those values. Or, you can use the alpha to select which pixels of an image to draw:

setBlending(GL_DST_ALPHA, GL_ZERO);

What this will do is, if the dest alpha at a pixel is 1.0, it will write the source color. Otherwise, it writes all zeros. Note that this is a one-time operation, as the writing of each pixel will bash whatever is in the alpha value. More advanced GL blending (avaliable in higher-end cards, and not exposed by T2D) can separate out the color blend function from the alpha blend function, so that you can retain the destination alpha no matter what happens to the RGB portion of the color.

I'm not going to bother explaining GL_SRC_ALPHA_SATURATE, as it is difficult to grasp and rarely if ever useful in practice. Just ignore it; I do ;)
#7
04/04/2005 (2:44 pm)
Thanks Smaug! That's really helpful.
#8
04/04/2005 (2:56 pm)
Very helpful, thanks much
#9
04/05/2005 (2:43 am)
Thanks for the efforts on that post Smaug, definately a thread I'll direct people to when asked about that subject.

First-up, 4096 = sucks!

Second, I'd be interested to hear anyones thoughts for both exposure of some of the underlying functionality such as the texture mod/decal stuff, blend-function, vertex-colour state as well as naming conventions for functions.

I'm pretty open to whether there should be specific dumbed-up calls to setup compound functionality such as tinting or to perhaps to just expose the underlying API calls on a functional level so as to not assume any particular functionality or indeed any other options you'd consider useful.

I totally agree on the misleading naming for "blendcolour" and would like to hear alternatives. This is one of the reasons for an Early Adopter.

- Melv.
#10
04/05/2005 (3:43 am)
Thank you very much Smaug. I need something like this very much (once/if I figure out how to use it successfully :)). But, I do think that masking would be a very useful feature. I believe it's in the works though, based on another layer problem someone else had earlier... I could be wrong though.
#11
04/05/2005 (5:48 pm)
Quote:Second, I'd be interested to hear anyones thoughts for both exposure of some of the underlying functionality such as the texture mod/decal stuff, blend-function, vertex-colour state as well as naming conventions for functions.

It would be nice to have access to the (in GL terms) texture environment functions, including common extensions. Being able to add the texel color to the given color would allow for a number of interesting effects. Having access to the secondary color might be nice too, but this would be for pretty advanced stuff (and may not be useful outside of multitexturing. It's been a while since I played with GL texenv stuff instead of using a fragment program).

Quote:I totally agree on the misleading naming for "blendcolour" and would like to hear alternatives.

If you don't expose the ability to set the vertex colors of a sprite separately, I would suggest simply calling it the "Sprite Color".