Fire Effect

Demo: Fire Effect

One of the coolest looking, as well as simplest to achieve, 2D rendering effects is generating flames or fire. This effect combines the use of gradients, palettes, memory manipulation and a technique known as down-sampling to achieve the final result. I’ll provide a brief explanation of  topics I’ve already gone over in previous posts and provide some additional details that are especially reliant to this effect.

This post is going to show in detail how to create this effect using my Rendering Framework project as the foundation.

Concept Overview

The overall structure of this effect can be summarized in essentially these core steps: generate the palette, create an in-memory buffer to store the current state of the effect, randomize the bottom row of the buffer each frame so the effect remains in a constant state of flux, apply the fire algorithm over the entire surface of the buffer to calculate the effect state, then using a look-up table map the values contained inside the buffer to colors contained inside your palette and plot them on the display surface. This may sound a bit more complicated than it actually is, so I’m going to break down each step into detail.

The Palette

Creating a nice palette is probably the most important aspect of generating any realistic looking effect. For fire, we need to generate a 256 color palette that contains the possible colors of a flamed pixel. Traditional fire values range from cold to hot, e.g. black (0) to White (255), with additional steps in between that represent the median values – usually a reddish hue.

Here is a look at the palette used in for this demo. If you download the source and run the example I encourage you to play around with the palette color values, a good palette can almost account for a completely new effect.

The palette for this demo is a standard multicolored gradient palette with the values: Black -> DarkRed -> Red -> Yellow -> White; for a more detailed explanation see this post.

Traditional Fire Palette

Fire Buffer

Since the nature of the algorithm used to generate fire is based on calculating the values of pixels in spatial locality to the current pixel, we can create a byte array that represents what color in our palette should be displayed in that spot.

This technique is known as up-scaling because we are taking a variable that contains a maximum of 256 possible values (8 bits), and mapping it to a palette that contains “true” colors – or 16,777,216 possible values (24 bits). We gain substantial computational savings by using this method.

Each frame we make sure the surface area matches the size of our buffer, if the surface area has changed then we recreate the buffer.

FireBufferHeight = height > MaxFlameHeight ? MaxFlameHeight : height;FireBufferWidth = width;
FireBuffer = new byte[ FireBufferWidth * FireBufferHeight ];

Due to the deteriorating nature of the fire effect algorithm the buffer size can be set to a predetermined maximum possible height value if the display area is very large, because every pixels eventual destination is a black value there is no benefit in updating a pixels value when it will not show a change in the display surface. This helps to optimize the effect when running in very large resolutions. This value is represented by the MaxFlameHeight constant in the definitions region of the source.

Coal Bed

To keep the effect in a constant state of flux we need to keep inserting random data into the buffer each frame. The fire effect consists of essentially two sources of data; the coal bed and the flame. The coal bed is generated by randomizing the values of the bottom row of the buffer in a two step process.

The first is to fill the entire row with values between MinCoalIntensity and MinCoalIntensity this gives a nice consistent reddish base layer to the fire. Next, in order to generate realistic fire we need to introduce the another layer of randomized data on top of the standard coals, which we’ll call flames. The flames are placed at random intervals with random widths, calculated using the FlameChance constant and AvgFlameWidth, over the coals with a much higher values, represented by MinFlameIntensity and MaxFlameIntensity respectively.

The code for generating the coal bed looks like this:

static void GenerateCoalBed( IRenderSurface display )
{
    int coalStart = display.YValues[ FireBufferHeight - CoalHeight - 1 ];
    int coalEnd = display.YValues[ FireBufferHeight - 1 ] + FireBufferWidth;

    int position = coalStart;

    while( position != coalEnd )
    {
        if( position >= FireBuffer.Length )
            break;

        FireBuffer[ position ] = ( byte ) Randomizer.Next( MinCoalIntensity, MaxCoalIntensity );

        position++;
    }

    position = 0;

    while( position < FireBufferWidth )
    {
        if( Randomizer.Next( 0, 100 ) < FlameChance )
        {
            int flameWidth = Randomizer.Next( 1, AvgFlameWidth );
            int offset = coalStart + position;

            for( int i = 0; i < flameWidth; i++ )
            {
                if( position > FireBufferWidth || offset >= FireBuffer.Length )
                    break;

                FireBuffer[ offset + position ] = ( byte ) Randomizer.Next( MinFlameIntensity, MaxFlameIntensity );

                position++;
            }
        }

        position++;
    }
}

Flame Algorithm

The actual flame algorithm works by, starting with the bottom row, changing the value of the pixel above it by taking an average value of the pixels surrounding it; then moving up row by row.

This can be better illustrated using a simple diagram. The source is the current pixel being evaluated, the destination is where the calculated value goes and the PX values are the pixels where the algorithm gets the values to compute the destination value.

Fire Algorithm

Fire Algorithm

Here is the fire algorithm code that is applied to the buffer each frame:

static void Flame( IRenderSurface display )
{
    byte p1, p2, p3, p4, p5, p6, p7;
    int u, d, l, r;

    for( int x = 0; x < FireBufferWidth; x++ )
    {
        l = x == 0 ? 0 : x - 1;
        r = x == FireBufferWidth - 1 ? FireBufferWidth - 1 : x + 1;

        for ( int y = 0; y < FireBufferHeight - 1; y++ )
        {
            u = y == 0 ? 0 : y - 1;
            d = y == FireBufferHeight - 1 ? FireBufferHeight : y + 1;

            p1 = FireBuffer[ display.YValues[ u ] + l ];
            p2 = FireBuffer[ display.YValues[ y ] + l ];
            p3 = FireBuffer[ display.YValues[ d ] + l ];

            p4 = FireBuffer[ display.YValues[ d ] + x ];

            p5 = FireBuffer[ display.YValues[ u ] + r ];
            p6 = FireBuffer[ display.YValues[ y ] + r ];
            p7 = FireBuffer[ display.YValues[ d ] + r ];

            FireBuffer[ display.YValues[ u ] + x ] = ( byte )( ( p1 + p2 + +p3 + p4 + p5 + p6 + p7 ) / 7 );
        }
    }
}

Displaying the Result

The only thing left to do is take the calculated values stored in the fire buffer, map them to our color palette to get the correct color, then write it to the display surface.

To preform our Blt’ing operation we simply calculate the offset of the area where the fire buffer has values and where the display surface should be unaltered, this only comes into play when the vertical screen height of the surface is larger than our maximum calculated value.

Once the starting row offset has been calculated, we then iterate over each row in the fire buffer and calculate its offset in relation to the CoalHeight because the bottom row where the coals are generated look very pixelated, therefor we skip over it.

From that point it’s just a matter of iterating over the display surface and setting the correct value stored in the corresponding location in the fire buffer, which maps directly to a 32 bit color in the palette. We use a byte to represent the starting address of each pixel because we’re running in 32bpp color depth, where each pixel is 4 bytes wide, therefore each color value in the pixel is 8 bits or 1 byte. This is why we’re able to access each color channel independently with the indexer operation and why we increment our pointer value by 4 each iteration.

static void DrawFire( IRenderSurface display )
{
    int height = display.Height >= FireBufferHeight ?
            FireBufferHeight : FireBufferHeight - display.Height,
        start = display.Height - height,
        position = 0;

    byte* dest; int offset; Color pixel;

    for( int row = start; row < ( display.Height - CoalHeight ); row++ )
    {
        offset = display.YValues[ row + CoalHeight ];

        dest = ( byte* ) ( display.Surface ) + ( offset << 2 );

        for ( int x = 0; x < display.Stride; x++ )
        {
            pixel = Palette[ FireBuffer[ display.YValues[ position ] + x ] ];

            dest[ 0 ] = pixel.B;
            dest[ 1 ] = pixel.G;
            dest[ 2 ] = pixel.R;

            dest += 4;
        }

        position++;
    }
}

And that’s it.

That’s all there is to the Fire effect. The full project source code for this effect is included in my Rendering Framework project.

Advertisements
By Brandon Tagged

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s