In this tutorial, I will show you how to make a nice fire effect in DirectX 5.0. Now, this probably can be converted into any other language if you work hard enough =)
First of all, in this tutorial, I'm not going to explain how to set up the DirectDraw interface or any of that other fun stuff. I suspect you can find this at Sweet.Oblivion or GPMega. I will, however, be showing you how to set up a palette, do a simple fire loop, and then draw the fire. Fire is really a simple effect. Kinda fun to program too =)
Unfortunately I do not know Assembly, so this isn't the FASTEST fire routine in the world..but it does run incredibly fast on my p2 (duh =) and not too bad on my p75.
[hr] Ok. Down to business. First of you need to set up a palette. Anything really would work. I did mine this way:
Black at the bottom(this will be at the top of the flame), red, an orangish color, and then yellow.
In order to set up off of our 256 colors, I just used a few loops. Now we need "containers" heh.. to hold our palette info.
LPDIRECTDRAWPALETTE lpDDPal; // the palette object
PALETTEENTRY mypal[256]; // stores palette stuff
index = 0;
for (index=95;index<200;index++)
{
mypal[index].peRed = index+70;
mypal[index].peGreen = index+30;
mypal[index].peBlue = rand()%10;
}
for (index = 1; index < 35; index++)
{
mypal[index].peRed = index+25;
mypal[index].peGreen = rand()%10;
mypal[index].peBlue = rand()%10;
}
for (index = 35; index < 55; index++)
{
mypal[index].peRed = index+25;
mypal[index].peGreen = index-25;
mypal[index].peBlue = rand()%10;
}
for (index = 55; index < 95; index++)
{
mypal[index].peRed = index+75;
mypal[index].peGreen = index;
mypal[index].peBlue = rand()%5;
}
for(index = 200; index < 255; index++)
{
mypal[index].peRed = index;
mypal[index].peGreen = index-rand()%25;
mypal[index].peBlue = rand()%5;
}
All it basically does is load the RGB value into each of the items. peRed member of the mypal struct is the red color value, peGreen being the green, and so on
Now we sorta attach those values to our palette object (lpDDPal) and attach those to the primary surface. So then the program uses our palette we just made
lpDD->CreatePalette(DDPCAPS_8BIT | DDPCAPS_ALLOW256, mypal, &lpDDPal, NULL);
lpDDSPrimary->SetPalette(lpDDPal);
lpDDSPrimary = hmm let me guess... uh our primary surface?
With me so far? No? Hmm, not good. Go read another tutorial then! Geez! People these days...
Still holding your breath? Well breathe. The rest is a breeze.
[hr] dun dun dun dunnnn
Fire Calculations!
First me make this little function to lock the back surface so we can draw to it:
unsigned char *Lock_Back_Buffer (void)
{
DDSURFACEDESC ddsd;
HRESULT ret;
ddsd.dwSize = sizeof(ddsd);
ret = DDERR_WASSTILLDRAWING;
while (ret == DDERR_WASSTILLDRAWING)
ret = lpDDSBack->Lock(NULL, &ddsd, 0, NULL);
return (ret == DD_OK ? (unsigned char *)ddsd.lpSurface : NULL);
}
unsigned char *double_buffer = NULL;
now to allocate some memory for it
double_buffer = (unsigned char *)malloc(307200);
Now. We need another unsigned char pointer array to store our fire stuff
unsigned char *fire_buffer = NULL;
fire_buffer = (unsigned char *)malloc(307200);
(double_buffer); free(fire_buffer);
You can color those pixels either 0 or 255. We do this by:
for(x = 1; x < 637; x+=rand()%3)
{
if(rand()%2)
fire_buffer[(480*640) + x] = 255;
else
fire_buffer[(480*640) + x] = 0;
}
Ok the way we do fire is, you take the surrounding pixels of a pixel and divide by the number of pixels. Now you may be thinking...WHAT?! Well it's simple. You have a point right? Well if you are familiar with plotting pixels you know you do Y position * ScreenWidth + x position to find the pixel position in a 1d array. Confusing? Good. This means you'll have to think =)
Think of it as a typewriter.
tap tap tap tap tap tap CA CHING - next line
tap tap tap tap tap tap CA CHING - next line
tap tap tap tap tap tap CA CHING - next line
etc...
now let's count 1, 2, 3...15. Wait a minute. We are on the 3 LINE! Start counting from 0 then 1 2 3 ok?
so 1*6 is 6. Then 6 + 3 is 9. Now we're into business
::sigh:: What next? Ahh yes. Our loop. We need to loop through the section of the screen we are painting the fire at. Now in that loop, we must loop through both the Y and then the X. You'll see why in a minute. In the inner X loop, as I said before, we average the surrounding pixels to get our new color value. Remember our palette. Well, if our pixel that we randomized was 255, it's yellow. If it was 0, it's black. Now 255+0 is what? 255. 255/2 is..? About 128. Whoa! We are in our orange redish section of our palette. This is basically all fire is. Averaging colors so you can get a new new color as the fire moves up the line. The more you think about it, the more clear it will become. It will start yellow, go to orange, red, and then finally disappear as it gets to very faint red and then black. As it moves up the fire line, it will make cool fire shapes. You'll see after you see the example project.
pseudo code:
for y = 1, y to screenheight, increment y
for x = 1, x to screenwidth, increment x
find our offset - what pixel we are going to start
averaging around - (Y*ScreenHeight)+x
add up the surrounding pixels - all eight of them
divide that total by 8 - hard concept there =)
now if that value is not 0 - not black
we decrement it - subtract 1 =)
end
Draw to our back buffer
end
int x,y,fireoffset;
//calculate the bottom line pixels
for(x = 1; x < 637; x+=rand()%3)
{
if(rand()%2)
fire_buffer[(480*640) + X] = 255;
else
fire_buffer[(480*640) + X] = 0;
}
}
//CALCULATE THE SURROUNDING PIXELS
for(y = 1; y < 480; ++y)
{
for(x = 1; x < 640; ++x)
{
offset = (y*640) + x;
firevalue = ((fire_buffer[fireoffset-640] +
fire_buffer[fireoffset+640] +
fire_buffer[fireoffset+1] +
fire_buffer[fireoffset-1] +
fire_buffer[fireoffset-641] +
fire_buffer[fireoffset-639] +
fire_buffer[fireoffset+641] +
fire_buffer[fireoffset+639]) / 8); // this can be optimized by a
// look up table as I'll show you later
if(firevalue != 0) // is it black?
{
--firevalue; // Nope. Beam me down Scotty.
fire_buffer[fireoffset-640] = firevalue; // Plot that new color
// on the above pixel
// remember the typewriter analogy
}
}
}
double_buffer = Lock_Back_Buffer(); // Remember this function? Good
memcpy(double_buffer, fire_buffer, (640*480)); // Copy fire buffer to the screen
lpddsback->Unlock(NULL); // Unlock! Important! you have no idear!
We have this pixel we found. As I explained above - Y * 640 + X gave us our fireoffset - the pixel we are averaging around
Now, fireoffset-640 would be what? The pixel above it right? fireoffset-639 is what? A pixel above..but a little to the right. So that's the topright corner pixel. fireoffset+640 is what? The pixel below. Get the drift?
I told you this was SIMPLE! Ha! And you didn't believe me. I think it may be confusing at first.. but after you think about it for a while, it will finally click - like it did for me
Ok this is a nice effect.. but it's kinda slow. So we optimize.
We could take out that / 8 and put in a right shift ( divide by powers of 2) >> 3; 2^2^2 = 8. So a left shift multiplies and a right shift divides. BUT! Why even multiply?
We could make a nifty little look up table. Like an array that we just have to look up a value, and it automatically gives you the value we want - because it's pre-calculated.
so...
int ftab[1257]; // I'm not sure how many members to put into this array..
// 1257 seems to work good =)
int firelook;
for (firelook = 0; firelook < 1256; FIRELOOK++)
FTAB[FIRELOOK] = FIRER >> 3;
firevalue = ftab[(fire_buffer[fireoffset-640] +
fire_buffer[fireoffset+640] +
fire_buffer[fireoffset+1] +
fire_buffer[fireoffset-1] +
fire_buffer[fireoffset-641] +
fire_buffer[fireoffset-639] +
fire_buffer[fireoffset+641] +
fire_buffer[fireoffset+639])];
[hr] Hopefully this little weekend excursion will help you make your own fire effect and get you started making all sorts of neet effects.
Any questions? Tough =)
[email="LavaMan842@aol.com"]Shaun Patterson[/email]
Start Lowe's monthly $500 survey sweepstakes and share your latest experience at www.lowes.com survey to win a $500 gift card. Lowes is an American-based home improvement company that offers a wide range of products.