Jump to content
  • Advertisement
  • 10/19/99 06:36 PM
    Sign in to follow this  


    Graphics and GPU Programming

    Myopic Rhino
    Fire is probably one of my most favorite effects. It can be very rewarding when you program a very efficient fire routine.

    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

    That's pretty explanatory I guess. Ok now we need to load our info. First we load our RGB info into each of our "mypal" array items. (Why I used a loop..)

    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;
    This is what I found to be the best combination, although I didn't spend more than 10 minutes on it =)

    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);
    lpDD = our directdraw object
    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)
    HRESULT ret;
    ddsd.dwSize = sizeof(ddsd);


    while (ret == DDERR_WASSTILLDRAWING)
    ret = lpDDSBack->Lock(NULL, &ddsd, 0, NULL);

    return (ret == DD_OK ? (unsigned char *)ddsd.lpSurface : NULL);
    now this returns an unsigned char pointer to the screen array. So we need to get a screen array!

    unsigned char *double_buffer = NULL;

    now to allocate some memory for it

    double_buffer = (unsigned char *)malloc(307200);

    Oh yeah. I'm working in 640x480x8bit sorry.

    Now. We need another unsigned char pointer array to store our fire stuff

    unsigned char *fire_buffer = NULL;
    fire_buffer = (unsigned char *)malloc(307200);

    now don't forget to free those when you exit -

    (double_buffer); free(fire_buffer);
    Ok.. whew. Now to do fire, you plot some random pixels at the bottom of the screen - the bottom line =)

    You can color those pixels either 0 or 255. We do this by:

    for(x = 1; x < 637; x+=rand()%3)
    fire_buffer[(480*640) + x] = 255;
    fire_buffer[(480*640) + x] = 0;

    This is how I like to do it. You can also do it without the first random.

    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


    In this example, the screen is only 6 taps long. Well. Say you wanted to plot a pixel at tap on the 2nd line and the 3rd column. Well 2*6 is what? 12. 12 + 3 is what? 15? whoa! quick one here.

    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 =)


    Draw to our back buffer

    Ok. C++ code

    int x,y,fireoffset;

    //calculate the bottom line pixels
    for(x = 1; x < 637; x+=rand()%3)
    fire_buffer[(480*640) + X] = 255;
    fire_buffer[(480*640) + X] = 0;

    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!

    ok this may seem overwhelming at first, but really, it's simple if you think about it.

    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.


    int ftab[1257]; // I'm not sure how many members to put into this array..
    // 1257 seems to work good =)
    now to build that table

    int firelook;
    for (firelook = 0; firelook < 1256; FIRELOOK++)

    voila! Now we have our division pre-calculated. Simple. Now to incorporated it into our code:

    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] +

    See, the operation is completely removed. We just look up the value in our little array.
    [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]

      Report Article
    Sign in to follow this  

    User Feedback

    There are no comments to display.

    Create an account or sign in to comment

    You need to be a member in order to leave a comment

    Create an account

    Sign up for a new account in our community. It's easy!

    Register a new account

    Sign in

    Already have an account? Sign in here.

    Sign In Now

  • Advertisement

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!