www.xbdev.net xbdev - software development
Friday October 19, 2018
home | about | contact | Donations

Fire....
by bkenwright@xbdev.net

Well it can't get much easier than this... this is one of the easiest effects... but I think its also really amazingly cool.

I chose not to fill the whole screen with this effect, as the flames are applied to a texture - what you do with this texture is upto you.... you could even wrap it around a 3D shape...OOoooo.

 

Well when you first look at the code it might seem a little mistifying... but the principle behind it is really easy.  I'll explain how it works then show you the code... hopefully it won't be to painful... but you'll thank me when you understand it.

<1> Now to start of with you create a texture in DirectX.. easy part.

<2> Create an array the same size as the texture, the array is of bytes, so each pixel has a corresponding byte.

<3> Now using the values in our byte array we can set the colour on our texture... its done like that.

Now how we generate the effect?  At this point take a good drink of coffee as you might get lost...

We start by having an index array of colours...so we have 256 values of red, green and blue which will be used to generate our fire colour... so if our byte value is 2 we go to the colour value 2 in our red, green and blue array and then set that pixel in our texture to that colour.  If the byte value is 200 we get the colour value for red, green and blue from our palette array.  In this example I hard coded the palette array of colours into the palette.cpp file... thats all thats in there.

To get the flame effect we use a very simple techique... we take the byte values in our array and generate the mean of them... so we take each pixel, then we generate a new value for it by adding the top, bottom, left, and right values and dividing by 4.  We do that for all the bytes.  So the fire moves up, after we apply this to each byte, we move the new pixels up one.

At the bottom of the byte array, so that the fire keeps new, we use the rand() function and simply add new random values at the bottom all the time so the fire is constantly on.

Well thats the principle of it... the code isn't the tidiest, but its simple to follow I think... all the directX code for initilisation, de-initilization and generation the flames etc is all in the dxdraw.cpp file... in the main.cpp file is the windows code.

So we have:

main.cpp dxdraw.cpp palette.cpp

Download Source Code.

Now lets take a look at the code shall we... I'll start with dxdraw.cpp, as main.cpp is just overhead code for initilization etc in windows.

/***************************************************************************/

/*                                                                         */

/* File: dxdraw.cpp                                                        */

/* Author: bkenwright@xbdev.net                                       */

/* Date: 10-11-2002                                                        */

/* ?? -> Create an amazing fire with a few lines of code!                  */

/*                                                                         */

/***************************************************************************/

 

LPDIRECT3D8 g_pD3D = NULL;

LPDIRECT3DDEVICE8 g_pD3DDevice = NULL;

 

#include "pallete.cpp"

 

// Texture

LPDIRECT3DTEXTURE8 g_pTexture;

LPDIRECT3DVERTEXBUFFER8 pVB;

 

// Texture Palette.

PALETTEENTRY g_palette[256];

const int TEXTURESIZE = 128;

unsigned char cFireField[TEXTURESIZE*TEXTURESIZE];

unsigned char cFireField2[TEXTURESIZE*TEXTURESIZE];

 

unsigned char* pFireActive;

unsigned char* pFireScratch;

 

struct my_vertex

{

      FLOAT x, y, z;  // D3DFVF_XYZ

      DWORD colour;   // D3DFVF_DIFFUSE

        FLOAT     tu, tv;       // D3DFVF_TEX1

};

UINT my_vertex_description = ( D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1 );

 

void init(HWND hWnd)

{

    //First of all, create the main D3D object. If it is created successfully we

    //should get a pointer to an IDirect3D8 interface.

    g_pD3D = Direct3DCreate8(D3D_SDK_VERSION);

    //Get the current display mode

    D3DDISPLAYMODE d3ddm;

    g_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm);

    //Create a structure to hold the settings for our device

    D3DPRESENT_PARAMETERS d3dpp;

    ZeroMemory(&d3dpp, sizeof(d3dpp));

    //Fill the structure.

    //We want our program to be windowed, and set the back buffer to a format

    //that matches our current display mode

    d3dpp.Windowed = TRUE;

    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;

    d3dpp.BackBufferFormat = d3ddm.Format;

    //For depth buffering (e.g.) the z-buffer

    d3dpp.BackBufferCount=1;

    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

    d3dpp.EnableAutoDepthStencil = TRUE;

    //Create a Direct3D device

    g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &g_pD3DDevice);

    //Turn off lighting becuase we are specifying that our vertices have colour

    g_pD3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE);

    //Turn on z-buffering

    g_pD3DDevice->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);

 

      //Turn on back face culling. This is becuase we want to hide the back of our polygons

    g_pD3DDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); //D3DCULL_CCW);

 

    /*****************Init Fire Stuff from here on********************************/

 

      for(int q=0; q<255; q++)

      {

            g_palette[q].peRed            = g_FireRed[q];

            g_palette[q].peGreen    = g_FireGreen[q];

            g_palette[q].peBlue           = g_FireBlue[q];

            g_palette[q].peFlags    = 0;

      }

      memset( cFireField, 0, TEXTURESIZE*TEXTURESIZE);

      ::ZeroMemory(cFireField2, TEXTURESIZE*TEXTURESIZE);

 

      pFireActive = cFireField;

      pFireScratch = cFireField2;

     

      // Create Fire Texture.

      D3DXCreateTexture( g_pD3DDevice, TEXTURESIZE, TEXTURESIZE, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &g_pTexture);

 

      g_pD3DDevice->CreateVertexBuffer(   6*sizeof(my_vertex),

                                                            0, my_vertex_description,

                                                            D3DPOOL_MANAGED, &pVB);

 

      my_vertex* pV;

      pVB->Lock( 0, 6*sizeof(my_vertex), (BYTE**)&pV, 0);

 

      pV[0].x=-1.0f; pV[0].y=1.0f;  pV[0].z=0.0f; pV[0].colour=0xffffffff; pV[0].tu=0.0f; pV[0].tv=0.0f;

      pV[1].x= 1.0f; pV[1].y=1.0f;  pV[1].z=0.0f; pV[1].colour=0xffffffff; pV[1].tu=1.0f; pV[1].tv=0.0f;

      pV[2].x= 1.0f; pV[2].y=-1.0f; pV[2].z=0.0f; pV[2].colour=0x00ffffff; pV[2].tu=1.0f; pV[2].tv=1.0f;

 

      pV[3].x=-1.0f; pV[3].y=1.0f;  pV[3].z=0.0f; pV[3].colour=0xffffffff; pV[3].tu=0.0f; pV[3].tv=0.0f;

      pV[4].x= 1.0f; pV[4].y=-1.0f; pV[4].z=0.0f; pV[4].colour=0xffffffff; pV[4].tu=1.0f; pV[4].tv=1.0f;

      pV[5].x=-1.0f; pV[5].y=-1.0f;  pV[5].z=0.0f; pV[5].colour=0xffffffff; pV[5].tu=0.0f; pV[5].tv=1.0f;

 

      pVB->Unlock();

}

 

void de_init()

{

      g_pD3DDevice->Release();

    g_pD3DDevice = NULL;

    g_pD3D->Release();

    g_pD3D = NULL;

 

      /*****************De-Init Fire Stuff from here on****************************/

 

      pVB->Release();

      g_pTexture->Release();

 

}

void set_camera()

{

     

    // [1] D3DTS_VIEW

    D3DXMATRIX v;

    g_pD3DDevice->SetTransform(D3DTS_VIEW, D3DXMatrixLookAtLH(&v, &D3DXVECTOR3(0,0,-3),

                                                                  &D3DXVECTOR3(0,0,0),

                                                                  &D3DXVECTOR3(0,1,0))); 

    // [2] D3DTS_PROJECTION

    D3DXMATRIX p;

    g_pD3DDevice->SetTransform( D3DTS_PROJECTION, D3DXMatrixPerspectiveFovLH( &p,  D3DX_PI/4,  1.0f,

                                                                                                 1.0f,200.0f));

};

 

void ProcessFire(unsigned char* firefield, unsigned char* firefield2, int firesizex, int firesizey, int coolamount)

{

      for(int y=0; y< firesizey; y++)

      {

            for(int x=0; x< firesizex-0; x++)

            {

                  unsigned char     firevalue_left,

                                          firevalue_right,

                                          firevalue_bottom,

                                          firevalue_top;

                  int finalfirevalue;

 

                  int xplus1, xminus1, yplus1, yminus1;

                  xplus1  = x+1;  if(xplus1 >= firesizex) xplus1=0;

                  xminus1 = x-1;  if(xminus1 <0) xminus1 = firesizex - 1;

                  yplus1  = y+1;  if(yplus1 >= firesizey) yplus1 = firesizey - 1;

                  yminus1 = y-1;  if(yminus1 <0) yminus1 = 0;

 

                  firevalue_right  = firefield[ (y*firesizex) + xplus1 ];

                  firevalue_left   = firefield[ (y*firesizex) + xminus1 ];

                  firevalue_bottom = firefield[ ((yplus1)*firesizex) + x ];

                  firevalue_top    = firefield[ ((yminus1)*firesizex) + x ];

 

                  finalfirevalue = (firevalue_left + firevalue_right + firevalue_top + firevalue_bottom)/4;

 

                  finalfirevalue -= coolamount;

 

                  if( finalfirevalue < 0 ) finalfirevalue = 0;

 

                  firefield2[ ((yminus1)*firesizex)+x ] = finalfirevalue;

            }

      }

 

      // You could just pick one and stay with it... you should play around with the code to get various

      // by changing the below code to various parts gives different effects.

     

      // Add fuel to the fire value on the scratch array.

      for(int x=0; x< firesizex; x+=2)

      {

            int y=firesizey-1;

           

            int fuel = firefield[ (y*firesizex) + x ] + (rand() % 64) - 32;

 

            if( fuel > 255 ) fuel = 255;

            if( fuel < 0 )   fuel = 0;

 

            firefield2[ (y*firesizex) + x ] = (unsigned char)fuel;

            firefield2[ (y*firesizex) + x + 1 ] = (unsigned char)fuel;

      }

}

 

void Render()

{

      if(!g_pD3DDevice)return;

 

      set_camera();

 

      // Flame Height (larger value for shorter flames)-----------------

    //                                                                |

      //                                                               \|/

      //                                                                |

      ProcessFire( pFireActive, pFireScratch, TEXTURESIZE, TEXTURESIZE, 1);//rand() % 5);

 

      unsigned char* temp = pFireActive;

      pFireActive = pFireScratch;

      pFireScratch = temp;

     

 

    // Okay at this point... our graphics card has our vertices stored in it... we've just copied

    // them over :) Some stuff to setup or graphics card!

    //Turn off lighting becuase we are specifying that our vertices have colour

    g_pD3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE);

      g_pD3DDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);

 

      // Set to True to make the background transparent -----+

      //                                                     |

      g_pD3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);

      g_pD3DDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);//D3DBLEND_INVSRCALPHA);

      g_pD3DDevice->SetRenderState(D3DRS_DESTBLEND,D3DBLEND_ONE);

 

 

    g_pD3DDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);

      g_pD3DDevice->SetTextureStageState(0,D3DTSS_COLORARG1, D3DTA_TEXTURE);

      g_pD3DDevice->SetTextureStageState(0,D3DTSS_COLORARG2, D3DTA_DIFFUSE);

      g_pD3DDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);

      g_pD3DDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);

 

 

      g_pD3DDevice->SetTextureStageState( 0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR );

      g_pD3DDevice->SetTextureStageState( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR );

      g_pD3DDevice->SetTextureStageState( 0, D3DTSS_MIPFILTER, D3DTEXF_LINEAR );

 

 

 

 

      D3DLOCKED_RECT lockedrect;

      ::ZeroMemory(&lockedrect, sizeof(lockedrect));

 

      g_pTexture->LockRect(0, &lockedrect, NULL, 0);

     

      unsigned char *pSurfBits = static_cast<unsigned char*>(lockedrect.pBits);

 

      int index=0;

      for(int y=0; y<TEXTURESIZE; y++)

      {

            for(int x=0; x<TEXTURESIZE; x++)

            {

                  pSurfBits[index++] = g_palette[ pFireActive[(y*TEXTURESIZE)+x] ].peBlue;

                  pSurfBits[index++] = g_palette[ pFireActive[(y*TEXTURESIZE)+x] ].peGreen;

                  pSurfBits[index++] = g_palette[ pFireActive[(y*TEXTURESIZE)+x] ].peRed;

                  pSurfBits[index++] = g_palette[ pFireActive[(y*TEXTURESIZE)+x] ].peFlags;

            }

            index += lockedrect.Pitch - (TEXTURESIZE*4);

      }

      g_pTexture->UnlockRect(0);

 

      // Clear the back buffer to a blue color

      g_pD3DDevice->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0,0,255), 1.0f, 0 );

      // Draw our triangle.

 

      g_pD3DDevice->SetTexture(0, g_pTexture);

      g_pD3DDevice->SetStreamSource( 0, pVB, sizeof(my_vertex) );

      g_pD3DDevice->SetVertexShader( my_vertex_description );

      g_pD3DDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, 2 );

 

      // After rendering the scene we display it.

      g_pD3DDevice->Present( NULL, NULL, NULL, NULL );

     

 

}

Code for using our byte to colour palette.cpp

/***************************************************************************/

/*                                                                         */

/* File: palette.cpp                                                       */

/* Author: bkenwright@xbdev.net                                       */

/* Date: 10-11-2002                                                        */

/*                                                                         */

/***************************************************************************/

// This file contains our hard-coded color palette for our fire.

 

unsigned char g_FireRed[256] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,

  0,0,0,0,0,0,4,4,4,4,4,4,4,8,8,8,8,8,12,12,12,12,16,16,16,16,20,20,20,24,24,

  24,28,28,32,32,32,36,36,40,40,44,44,48,48,52,52,56,56,60,60,64,68,68,72,72,

  76,80,80,84,88,88,92,92,96,100,100,104,108,108,112,116,120,120,124,128,128,

  132,136,136,140,144,144,148,152,152,156,160,160,164,164,172,172,172,176,176,

  180,184,184,188,188,192,192,196,196,200,200,204,204,208,208,208,212,212,216,

  216,220,220,220,220,224,224,224,228,228,228,228,232,232,232,232,236,236,236,

  236,236,240,240,240,240,240,240,240,244,244,244,244,244,244,244,244,244,244,

  248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,

  248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,

  248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,

  248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,

  248,248,248,248,248};

 

unsigned char g_FireGreen[256]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,

  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,4,4,4,4,4,4,4,4,4,4,8,8,8,8,

  8,8,8,8,8,12,12,12,12,12,12,16,16,16,16,16,16,20,20,20,20,20,24,24,24,24,28,

  28,28,28,32,32,32,32,36,36,36,36,40,40,40,44,44,44,48,48,48,52,52,52,56,56,

  56,60,60,60,64,64,64,68,68,72,72,72,76,76,80,80,80,84,84,84,88,88,92,92,92,

  96,96,100,100,104,104,104,108,108,112,112,116,116,116,120,120,124,124,128,

  128,128,132,132,136,136,140,140,140,144,144,148,148,148,152,152,156,156,160,

  160,163,164,164,168,168,168,172,172,172,176,176,180,180,180,184,184,184,188,

  188,188,192,192,192,196,196,200,200,200,200,200,204,204,204,208,208,208,208,

  212,212,212,212,216,216,216,216,220,220,220,220,220,224,224,224,224,224,228,

  228,228,228,228,228,232,232,232,232,232,232,236,236,236,236,236,236,236,236,

  240,240,240,240,240,240};

 

unsigned char g_FireBlue[256]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,

  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,

  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,

  0,0,0,0,0,0,0,0,0,0,0,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,

  4,4,8,8,8,2,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,12,12,12,12,12,12,12,12,12,12,

  12,12,12,12,12,12,12,16,16,16,16,16,16,16,16,16,16,16,16,16,16,20,20,20,20,

  20,20,20,20,20,20,20,20,24,24,24,24,24,24,24,24,24,24,24,24,28,28,28,28,28,

  28,28,28,28,28,32,32,32,32,32,32,32,32,32,32,36,36,36,36,36,36,36,36,36,40,

  40,40,40,40,40,40,40,44,44,44,44,44};

 

And finally, the code so that it runs in windows.

/***************************************************************************/

/*                                                                         */

/* File: main.cpp                                                          */

/* Author: bkenwright@xbdev.net                                       */

/* Date: 10-11-2002                                                        */

/*                                                                         */

/***************************************************************************/

 

#include <windows.h>

#include <stdio.h>

#include <d3dx8.h>

 

 

WNDCLASS a; HWND hwnd; MSG c;

long _stdcall zzz (HWND, UINT, WPARAM, LPARAM);

 

 

#include "dxdraw.cpp"

 

 

void gameloop()

{

      Render();

}

 

 

int _stdcall WinMain(HINSTANCE i, HINSTANCE j, char *k, int l)

{

      a.lpszClassName="a1";

      a.hInstance = i;

      a.lpfnWndProc = zzz;

      a.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);

      RegisterClass(&a);

      hwnd=CreateWindow("a1", "xbdev.net", WS_OVERLAPPEDWINDOW, 30,30,300,300,NULL,NULL,i,NULL);

 

      init(hwnd);                   // Initilize Direct3DX.

      ShowWindow(hwnd,1);

      while(1)

      {

           

            if (PeekMessage(&c, NULL, 0, 0, PM_NOREMOVE))

            {

                  if(!GetMessage(&c, 0,0,0))

                        break;

                  DispatchMessage(&c);

            }

            else

                  gameloop();       // Render Loop.

      }

 

      return 1;

}

 

long _stdcall zzz (HWND w, UINT x, WPARAM y, LPARAM z)

{

      if (x == WM_DESTROY)

      {

            de_init();              // Destroy DirectX3D.

     

            PostQuitMessage(0);

      }

      return DefWindowProc(w,x,y,z);

}

 

 

I'm sure with a little playing around you'll soon come to create even more special effect, as you could change the colours to shades of blue and have them falling down...creating a waterfall effect...cool.

For example you could change the following lines of code to create a more flammy effect.. instead of a burner effect.

Change the following code from this:

      // Add fuel to the fire value on the scratch array.

      for(int x=0; x< firesizex; x+=2)

      {

            int y=firesizey-1;

           

            int fuel = firefield[ (y*firesizex) + x ] + (rand() % 64) - 32;

 

            if( fuel > 255 ) fuel = 255;

            if( fuel < 0 )   fuel = 0;

 

            firefield2[ (y*firesizex) + x ] = (unsigned char)fuel;

            firefield2[ (y*firesizex) + x + 1 ] = (unsigned char)fuel;

      }

To this:

      // Add fuel to the fire value on the scratch array.

      // Width of the flames at the bottom...are set here.

      int ww = (rand() % 10)+1;

      for(int x=0; x< (firesizex - firesizex%ww) ; x+=ww)

      {

            int fuel = firefield[ (firesizex)*(firesizey-1) + x ] + (rand() % 64) - 32;

 

            for(int ff=0; ff< ww; ff++)

            {

                  firefield2[ (firesizex)*(firesizey-1) + x +ff] = (unsigned char)fuel;

            }

 

      }

Well all I can say is that you mess around with the code... its not very efficent... I think the best thing would be to create a CFire class so you could effectivly use it in all of your other projects.

 

 

 
 Visitor: 9534626  { 209.237.238.175 } Copyright (c) 2002-2017 xbdev.net - All rights reserved.
Designated tutorial and software are the property of their respective owners.