Monday, 9 July 2012

How to do Old School Fire Part 1: CPU Flames


This will be split over 2 posts, the second covering the GPU version of this effect.

So, it all started with a page I found while looking up ray casting, and on there was also an algorithm for rendering old school fire, and I thought it would be easy for me to port the code to C#, so I did :D, then I thought I would put it into a shader, after all, XNA supports another 2 platforms you know :D

CPU Fire


So, here is my class to render my 2D fire on the CPU.




So, basically how does it work?

There is a texture that stores the “temperature” for each pixel on the screen (the fire map, second image on the right in the picture above), now the class enables you to set the resolution of this texture, so if you are running this on the little known third platform that XNA supports, the Windows Phone :D then you may want to reduce this resolution, the final image will be up scaled as you will soon see.

As well as the fire map there is another texture that is used to render the fire to the screen, we take each pixel and use it’s value to extract a value from a third palette texture (the palette, picture on the top right of the image above)

So, we now have a fire map, a palette and a final render, but how is the fire animated? Well, on each update, we randomly set the bottom line of the fire map, each pixel on the bottom row having random value between min and max flame, then we descend through the texture from the top left corner and calculate it’s new values based on four pixels below it. The new value being the sum of the four pixels averaged. The average is then modified by how much oxygen we have, this simply makes the flames brighter.

CPU2DFire


public CPU2DFire(Game game) : base(game)
{
BuildPalette();
rand = new Random(DateTime.Now.Millisecond);
}
public CPU2DFire(Game game, Point resolution) : base(game)
{
BuildPalette();
rand = new Random(DateTime.Now.Millisecond);
Resolution = resolution;
}

Here you can see I created 2 constructors for the class, both create the palette texture and initialize the Random object, and one ca be used to control the resolution.

BuildPalette


protected void BuildPalette()
{
for (int x = 0; x < palette.Length; x++)
palette[x] = new Color(255, Math.Min(255, x * 2), x / 3, x * 5);
}

As you can see, this will result in an image that gives you a fade from red to bright yellow, you can set this for what ever color you like, or don’t use it and load a pre made texture from the content pipeline.

In the LoadContent method we can now populate the palette texture

protected override void LoadContent()
{
base.LoadContent();
fireTexture = new Texture2D(GraphicsDevice, Resolution.X, Resolution.Y);
fireColor = new Color[fireTexture.Width * fireTexture.Height];
fmp = new Texture2D(GraphicsDevice, Resolution.X, Resolution.Y);
fmap = new int[fireTexture.Width, fireTexture.Height];
paletteTexture = new Texture2D(GraphicsDevice, (int)Math.Sqrt(256), (int)Math.Sqrt(256));
paletteTexture.SetData<Color>(palette);
}

As you can see I am also setting up the fire texture, the fire map array (fmap) and the palette texture.

Update


public override void Update(GameTime gameTime)
{
if (!Visible)
return;
base.Update(gameTime);
int w = fmap.GetLength(0);
int h = fmap.GetLength(1);
int[] fm = new int[w * h];
for (int x = 0; x < w; x++)
fmap[x, (h - 1)] = rand.Next(minFuel, maxFuel);
for (int y = 0; y < h; y++)
{
for (int x = 0; x < w; x++)
{
fmap[x, y] = (int)(((fmap[(x - 1 + w) % w, (y + 1) % h]
+ fmap[(x) % w, (y + 2) % h]
+ fmap[(x + 1) % w, (y + 1) % h]
+ fmap[(x) % w, (y + 2) % h])
* 1) / (4 + oxygen));
fm[x + (y * w)] = fmap[x, y];
}
}
fmp.SetData<int>(fm);
for (int y = 0; y < h; y++)
for (int x = 0; x < w; x++)
fireColor[x + (y * w)] = palette[fmap[x, y]];
fireTexture.SetData<Color>(fireColor);
}

Again, in here you can change this calculation to get you lots of different effects, with a blur palette and with alterations in here I managed to get a kind of shimmering water effect :D

Draw


public override void Draw(GameTime gameTime)
{
if (fireTexture != null)
{
spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.NonPremultiplied);
spriteBatch.Draw(fireTexture, new Rectangle(0, 0, GraphicsDevice.PresentationParameters.BackBufferWidth, GraphicsDevice.PresentationParameters.BackBufferHeight), Color.White);
spriteBatch.End();
// Debug
spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Opaque);
spriteBatch.Draw(paletteTexture, new Rectangle(GraphicsDevice.PresentationParameters.BackBufferWidth - 128, 0, 128, 128), Color.White);
spriteBatch.Draw(fmp, new Rectangle(GraphicsDevice.PresentationParameters.BackBufferWidth - 256, 0, 128, 128), Color.White);
spriteBatch.End();
}
}

And so we finally render our flames to the screen.

As ever, this si my stab at it, please feel free to comment and let me know what you think of it. Ill post the source code all in one go after the next post.

No comments:

Post a Comment