火焰算法(模糊算法运用)

好久之前,看到了水波特效的算法,最近又看到了火焰特效的算法,两者之间有很多的共同之处,我就将一篇关于火焰特效的文章翻译了一下,一共大家参阅,如有不足之处,请见量!由于本文作者写得很幽默,本人的水平有限,故有些地方还是采用的原文。  
   
  火焰特效  
  著   Shaun   Patterson   译   beauli  
  火焰效果也许是我在所有特效中最喜欢的特效之一。当你设计出一个非常有效的火焰例程,这是非常值得的。  
  在本文中,我将向你展示如何在DirectX   5.0下制作一个非常美丽的火焰效果。我想只要你认真的学习之后,你将能够用任何语言来进行设计。  
  首先,在本文中我将不讲述怎样建立DirectDraw   Surface或者在代码中添加任何有趣的实际代码(译注:即本文仅是火焰特效的算法理论介绍)。我猜想你能在Sweet.Oblivion或GPMega上找到。然而,我将向你展示如何建立一个Palette,做一个简单的循环,然后画出火焰来。火焰实际上是一种比较简单的特效,对它编程有一点娱乐的味道。  
  不幸的事,我不懂汇编,所以它并不是在整个世界上最快的火焰例程。但是它却不可置信的在我的P2上快速的运行,而且在我的P75上表现得也不错哦。  
   
  好了,下面转入正题。首先你想让你的代码能真正的运行,你必须要建立一个Palette。我是这样做的:  
  在底部依次分配为黑色(在火焰效果的顶部),红色,橙色和黄色。  
  我仅仅用了一些循环就建立起了属于我们自己的256种颜色。现在我们需要一个“容器”来盛放我们的调色板信息。  
  LPDIRECTDRAWPALETTE   lpDDPal;             //   the   palette   object  
  PALETTEENTRY           mypal[256];             //   stores   palette   stuff  
  我想这是非常恰当的注释。现在,我们需要载入我们的信息。首先我们载入我们的RGB信息到每一个mypal的数组条目中。(为什么我使用循环)  
  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;  
  }  
  这是我发现的最好的结构。虽然我花了不到10分钟时间。  
  所有的一切基本上是为了将RGB值装载入属于我们的每一个调色板数组的条目中。mypal结构中的peRed成员是红色的值,peGreen是绿色的值,以此类推。  
  现在我们已经接近调色板对象(lpDDPal)和主页面(primary   surface)的值。我们只需要使用下面的语句就可以使用调色板。  
  lpDD->CreatePalette(DDPCAPS_8BIT   |   DDPCAPS_ALLOW256,   mypal,   &lpDDPal,   NULL);  
  lpDDSPrimary->SetPalette(lpDDPal);  
  lpDD——DirectDraw对象  
  lpDDSPrimary——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.  
   
   
  dun   dun   dun   dunnnn  
  火焰算法  
  首先,为了能够使我们能进行绘画,我制作了一个小程序来锁住背景页面。  
   
  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指针,因此我们需要建立一个屏幕结构。  
   
  unsigned   char   *double_buffer   =   NULL;  
   
  接下来,我们给它分配一些内存空间  
   
  double_buffer   =   (unsigned   char   *)malloc(307200);  
   
  Oh   yeah。我是工作在640x480x8bit   的分辨力下的。  
   
  接下来,我们需要另一个unsigned   char指针数组来存储我们的火焰像素。  
   
  unsigned   char   *fire_buffer   =   NULL;  
  fire_buffer   =   (unsigned   char   *)malloc(307200);  
   
  现在可不要忘了在结束时显式的释放这些空间。  
   
  free(double_buffer);   free(fire_buffer);  
   
  Ok..whew.现在开始制作火焰部分,在屏幕的底部(最后一行)绘一些随机点,这些随即点的颜色可以任意给定为0或者255。如下所作:  
   
  for(x   =   1;   x   <   637;   x+=rand()%3)  
  {  
          if(rand()%2)  
                  fire_buffer[(480*640)   +   x]   =   255;  
          else  
                  fire_buffer[(480*640)   +   x]   =   0;    
  }  
  以上是我所喜欢的做法,当然你也可以不使用随机点。  
  好了,这就是我制作火焰的方法,选择一个像素的背景像素然后用像素的数量去除。现在你也许在想——WHAT?!好了,很简单。你是不是有一个点?如果你熟悉像素的绘制,你一定也知道在1d数组中查找一个指针数组中的点的方法——Y   position   *   ScreenWidth   +   X   position。还糊涂吗?Good。这表示你已经开始思考了=)  
  就像打字机一样的思考它吧。  
   
  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...  
   
  在这个例子中屏幕只有6个tap宽。好了,说你想要画的像素在第二行第三列。2*6=?12。  
  12+3=?15。whoa!一口气算完。  
  现在开始计数1,2,3。。。15。等一下,我们现在是在第三行!!开始计数是从0开始然后1,   2   ,3   的对吗?所以2*6=12,12+3=15(译注:原文为so   1*6   is   6.   Then   6   +   3   is   9.恐为笔误)。现在开始转入正题。  
  哎!接下来该怎么做呢?对了,使用循环。需要通过循环我们要绘制火焰的整个屏幕。开始进入循环,首先是Y然后X。你会马上就能明白的。在X的循环中,就像我以前所说的,平均周围的像素来获得新像素。不要忘记调色板(Palette)。假如我们的像素点随机值为255,就是黄色。如果是0,就是黑色。接下来,255+0=?255。255/2=?128。whoa!!我们在调色板中获得了桔红色区域的点。这是所有火焰的最基本的点。在火焰上升时,对颜色进行平均你能得到一个又一个新的颜色。这时候如果你思考得越多,你就会变得越明白。开始黄色,变成橙色,红色,最后当它达到一个非常红的时候变成黑色消失。当火焰上升时,它将会变成很cool的形状。你将能够在例子中看到。  
   
  伪码:  
   
  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  
   
  好了,接下来是C++代码  
   
  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)  
              {  
                    fireoffset   =   (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!  
   
  好了,也许已开始你可能认为很不好理解,但是,实际上只要你好好地想一下,这是非常简单的。  
  我们可以发现这个像素点,上面我们讨论过的——Y*640+X,这就是我们的fireoffset——这个点是我们用来平均的中心点。  
  现在fireoffset-640=?位于这个点正上方的点是吗?fireoffset-639=?位于这个点上方但是稍微偏右的点。所以是右上角的点。Fireoffset+640=?正下方的点。明白了吗??  
  我说过它是很简单的不是吗?Ha!是你自己你不相信我。我想也许已开始可能有点迷糊。但是你好好地想一下之后,你就会一下子明白过来——像我一样。(译注:为了帮助理解,可以参考下图)  

你可能感兴趣的