How to Create Fire Effect
Today's article brings us back to the time where you have to be imaginative and creative. The fire effect is a special technique to emulate a blazing fire that you may use in your next game application. Save each frame in a sprite and include it in your tile-based RPG games.
This demonstration uses C++ and SDL2.0. If you haven't downloaded SDL yet, you may do so by clicking the download link here. The focus of this article is the technique behind the fire effect and not installation of C++ and/or syntax of SDL, those two will be covered in a separate article.
Algorithm
The main idea of the fire effect is to average out the colors of the neighboring pixels below the current pixel being processed. You do this from left to right and top to bottom of the screen. Doing it differently gives you different effects, but I'll leave it up to the reader to explore.
for (y=0; y < MAX_SCR_HEIGHT; ++y)
{
for (x=0; x < MAX_SCR_WIDTH; ++x)
{
px(y,x) = (px(y+1,x-1)+px(y+1, x)+px(y+1,x+2)+px(y+2,x))/div;
}
}
The way you average out the pixels impacts the direction of the blazing fire. It is also important to note that it is preferred to have an averaging divisor that is slightly greater than the number of neighboring pixels to burn out the flames faster giving you a better flame effect.
The following are some neighboring pixels below the current pixels being processed and their respective impact on the fire effect.
You can experiment and try it on your own. An averaging divisor of 4.0125, gives you just the right effect to burn the flame at the right height for averaging 4 neighboring pixels. A 3.0125 gives you a similar effect for three neighboring pixels.
Color Palettes:
Now that we've learned about the basic concept of the fire effect algorithm, let's now focus on its implementation. The first implementation we have to focus on is the color palette.
A color palette is an array of RGBA colors that represents the color of the fire. Let's say we'll assign 256 colors of RGBA to the fire palettes. This makes our declaration of palette array something like:
unsigned long firePalette[256];
A color palette array is necessary to simplify the computation and to make sure that the fire effect only uses the colors that we want to show. The spectrum of colors varies upon layers of flame colors we wanted to assign.
Setting palette using Hue Saturation and Lightness
An easy way to choose a palette is through HSL by going to this link. Let's say we scale H (0 - 84), S (100%), L (0-127: pal-index / 1.28, 128-255: 100%) to 256 palettes (If you check the internet, this is the most common palette for the fire effect!); You may experiment and find out what appeals to your liking.
for (i = 0; i < 256; ++i)
{
H = i / 3; S = 100; L = i < 128 ? (i / 1.28) : 100;
fireB = (Uint8*)&firePalette[i];
fireG = fireB + 1;
fireR = fireB + 2;
fireA = fireB + 3;
*fireA = 0;
HSLtoRGB(H, S, L, fireR, fireG, fireB);
}
fireB, fireG, fireR, and fireA are unsigned char pointers set at the position of the (unsigned char*)&firePalette[i], fireB + 1, fire B + 2, fireB + 3 respectively. The data type of the firePalette array is unsigned long (4 bytes), just enough to hold the values of RGBA.
The same effect can be made by bit shifting or multiplying red by 256, green by 2562, blue by 2563 and adding alpha.
Since SDL's format of raw pixel data is RGBA, we need to convert HSL colors to RGB. The function HSLtoRGB will handle this implementation. The source code of HSLtoRGB can be found in the attached CPP file. The concept behind the conversion of HSL to RGB will be discussed in a separate article.
Setting gradient palettes using predefined RGB
Checking HSL gradient at W3Schools is a great way to find a fire palette. But we can have more flexibility by creating a function that allows us to create a gradient between a series of predefined RGBs.
For example, for the following predefined RGBs, we can create a beautiful gradient of blue orange and red, scaled to 256 palettes:
The function addPalette handles this implementation. The basic idea of creating a gradient is as follows:
Compute for the ascent or descent of one RGB to another by getting the difference between the 2nd RGB and the first then dividing it by the range. The range is computed by dividing the Max No. of Palettes by the number of predefined RGB values - 1.
In the case of the above table:
MAX_PALETTES = 256
range = MAX_PALETTES / (6 - 1);
r1 = 107; r2 = 187;
g1 = 212; g2 = 205;
b1 = 215; b2 = 191;
rn = (r2 - r1) / (double)range;
gn = (g2 - g1) / (double)range;
bn = (b2 - b1) / (double)range;
You then add rn steps to r1, for range times to reach r2. Same goes for gn to g1, and bn to b1. The full implementation of addPalette will be tackled in a separate article.
Fire Image
Now that we have set the palettes at this point, we need to allocate a two-dimensional unsigned char array[][] which will hold the pixel values of the fire image. The values of this array will contain the palette index of the fire. It is important to take note that we will not hold the values of the RGB in this array but rather the index of the firePalette as it will complicate the averaging out of red, green and blue values, resulting in slower preprocessing of the image due to the following:
- The need to dissect red, green, blue, and alpha from the combined RGBA.
- The need to compute the gradient's descent/ascent of each R, G, B to the next value.
To allocate the fire image in C++, we have to allocate the row first, then allocate the appropriate width size for each row then set the initial values of all fireImg[][] to the firePalette index zero (0).
unsigned char** fireImg = new unsigned char*[surface->h];
for (y = 0; y < surface->h; ++y)
fireImg[y] = new unsigned char[surface->w];
for (y = 0; y < surface->h; ++y)
for (x = 0; x < surface->w; ++x)
fireImg[y][x] = 0;
The Main Loop
The main loop of the program consist of the algorithm itself as described above.
do
{
sp = (Uint32*)surface->pixels;
for (y = 0; y < ly; ++y)
for (x = 0; x < surface->w; ++x)
{
fireImg[y][x] =
(fireImg[y + 1][(x - 1 + surface->w) % surface->w] +
fireImg[y + 1][x] +
fireImg[y + 1][(x + 1) % surface->w] +
fireImg[(y + 2) % surface->h][x]) / (4.0125);
*sp++ = firePalette[fireImg[y][x]];
}
SDL_UpdateWindowSurface(window);
} while(true);
The program starts by pointing sp to surface->pixels. Where the raw pixels in memory are stored. The two for-loops is the averaging procedure described by the algorithm above. For each pixel computed, it is then immediately copied to the memory. It is important to take note that the values copied to pixel memory are the RGBA values of fireImg[y][x] palette index; The last line before the while copies the data in the pixel memory to the screen. Data in the pixel memory is only copied to the screen once all the preprocessing is completed, This is done to avoid flickering of the image.
What's next?
Now it's time for you to try it on your own and implement the Fire Effect using the programming language you preferred. But it's always better and faster in C / C++. If you need the complete source code for reference, download it below.
I'll leave you with a video capture I made for the Fire Effect.