Rabid Lion Games

 

A question came up on the forums/ twitter about a Linear Burn effect. The basic effect can be defined by the formula Final = Color1 + Color2 – 1, clamped to 0 – 1 as usual. If the two textures you’re using to sample Color1 and Color2 from use the same texture coordinates, then this is very straight forwards, but what if you want to use different coordinates for two textures, e.g you want to rotate and scale the textures independently?

This may cause you problems if you wanted to write a shader that uses spritebatch, because spritebatch only sets one set of texture coordinates in the pixel shader. If you specify a second set of pixel coords as a parameter in your pixel shader, they will have the value (0, 0) whatever you do with spritebatch, which isn’t very helpful.

Also, you might be looking to create this effect on Windows Phone 7, in which case you don’t have the luxury of writing custom shaders. So can we achieve the same effect without using shaders at all? The answer is yes, we can achieve this with a combination of additive and subtractive blending using the normal spritebatch shader and a RenderTarget2D. However, it’s not *quite* that simple.

The main issue with not using shaders is that the backbuffer (or a normal render target with Color format) can only hold values in the range 0 – 1. If Color1 + Colo2 is greater than 1, it is clamped to 1. So if we were to break down our Linear Burn into two stages like so: temp = Color1 + Color2, final = temp – 1, then we’d have a problem for certain values of Color1 and Color2. E.g. lets have a look at what happens when Color1 and Color2 have components that are set to 0.75:

Our original forumla gives:

0.75 + 0.75 – 1 = 0.5.

Our two stage formula gives:

0.75 + 0.75 = 1.5 => 1 (clamped because of the limitations of the render target).

1 – 1 = 0.

So clearly this does not give us the same result as our original forumla. However, we can be sneaky to get around this.

Let’s rewrite our original formula like this: ¬†final = 2 * (0.5 * (Color1 + Color2 – 1)). This can be re-written as final = 2 * (0.5 * (Color1 + Color2) – 0.5).

So we can now split this up into 3 stages:

temp = (0.5 * Color1) + (0.5 * Color2)

temp2 = temp – 0.5

final = temp + temp

Each of these stages we can easily do with simple additive and subtractive blending, and we can guarantee the value of temp does not exceed 1 and so does not get clamped.

Now, you might be worrying about what happens when we subtract 0.5. Surely, you might saying, this might become negative and so get clamped to 0. Well, yes it might, but only when the original formula would be clamped to 0.

Note that our original formula gives a negative answer (which would be clamped to 0) exactly when Color1 + Color2 < 1. This implies that (0.5 * Color1) + (0.5 * Color2) < 0.5, which means temp2 in our split-up formula will also be negative and get clamped to 0. Since 0 + 0 = 0, our final value will still be 0, matching our original formula.

Can temp2 ever get clamped to 0 when our original formula wouldn’t have? No.

temp2 is less than 0 only when (0.5 * Color1) + (0.5 * Color2) < 0.5, which implies that Color1 + Color2 < 1, and hence the original would have been clamped as well.

Therefore, our split up formula gives us exactly the same answer as our original. Lets have a look at some code snippets to see how to implement this.

First up we’ll need a special blend state:

 

BlendState subtractive;

 

Which we initialize like so:

 

subtractive = new BlendState();
subtractive.ColorBlendFunction = BlendFunction.Subtract;
subtractive.ColorDestinationBlend = Blend.One;
subtractive.ColorSourceBlend = Blend.One;
subtractive.AlphaBlendFunction = BlendFunction.Add;
subtractive.AlphaDestinationBlend = Blend.One;
subtractive.AlphaSourceBlend = Blend.One;

 

There are two different subtractive blend functions. Effectively we have the choice between FinalColor = SourceColor – DestinationColor, or FinalColor = DestinationColor – SourceColor. In our case we’ll be clearing the back buffer to (0.5, 0.5, 0.5, 1), so our we’ll want SourceColor – DestinationColor, which is the function given by BlendFunction.Subtract.

We’ll also need a Color set to (0.5, 0.5, 0.5, 1), which we create like so:

 

Color halfColor;

 

And initialize like so:

 

halfColor = new Color(new Vector3(0.5f));

 

Finally we’ll need two RenderTarget2Ds to draw to:

 

RenderTarget2D target1;
RenderTarget2D target2;

 

And initialize (I’m assuming this is a full-size effect, if not then you might need to tweak these to meet your needs):

 

target1 = new RenderTarget2D(GraphicsDevice, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height);
target2 = new RenderTarget2D(GraphicsDevice, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height);

 

For the purposes of these snippets I’m assuming your two textures are called tex1 and tex2, and that their source rectangles are rec1 and rec2. Since we’re only dealing with SpriteBatch as normal you can alter anything about the SpriteBatch.Draw() call to match your needs.

So what does our draw call look like? Something like this:

 

GraphicsDevice.SetRenderTarget(target1);
GraphicsDevice.Clear(Color.Transparent);
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive);
spriteBatch.Draw(tex1, rec1, halfColor);
spriteBatch.Draw(tex2, rec2, halfColor);
spriteBatch.End();

GraphicsDevice.SetRenderTarget(target2);
GraphicsDevice.Clear(halfColor);

spriteBatch.Begin(SpriteSortMode.Deferred, subtractive);
spriteBatch.Draw(target1, target1.Bounds, Color.White);
spriteBatch.End();

spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive);
spriteBatch.Draw(target2, target2.Bounds, Color.White);
spriteBatch.Draw(target2, target2.Bounds, Color.White);
spriteBatch.End();

GraphicsDevice.SetRenderTarget(null);
GraphicsDevice.Clear(Color.CornflowerBlue);

spriteBatch.Begin();
//Draw the parts of the scene that lie behind your Linear Burn sprites
spriteBatch.Draw(target1, target1.Bounds, Color.White);
//Draw the parts of the scene that lie infront of your Linear Burn sprites
spriteBatch.End();

 

Again, you’ll have to adapt this to fit into the rendering of the rest of your scene, but that should give you an idea of what it should be like. I’ve added a sample on codeplex linked to below that takes the following two textures:

 

 

And outputs this as the result:

 

 

Here’s the link to the download:

 

Linear Burn sample

 

Enjoy!

Leave a Reply


× 7 = twenty one

Proudly powered by WordPress. Theme developed with WordPress Theme Generator.
Copyright © Rabid Lion Games. All rights reserved.