游戏制作离不开各种各样的特效,火焰效果更是这些特效当中最常用的一种。在网上有很多的这方面的例子,但似乎都不太理想。下面我主要介绍几种有效的火焰模拟算法。
在讲述火焰算法之前,我想先介绍一个经典的Blur算法,这将为我们后面的文章提供很好的技术基础,因为这是模拟火焰的关键所在。^_^
对于不同的效果我们要采取不同的模糊(Blur)算法,在这里我只介绍最简单的一种。
其实Blur算法相当简单,并不像很多人想象的那么神秘,只要一句话就可以解释清楚。我们所要做的不过是把屏幕上的每一个点用它周围的四个点的平均值代替即可。即:
好了,下面我们进入主题,Let' s get in …… FIRE ! ! !
总的来说,模拟一个火焰效果需要以下这三个步骤:放置热源、火焰上升、减掉衰减因子。
1.放置热源:
放置热源很好理解,也很好实现,我们只需要在屏幕的适当位置(想升起火焰的地方)放上一些亮点就可以了,比如:
for (int i=0; i
putpixel (i, 199, rand()%256);
2.火焰上升:
我们已经有了热源,那么如何使火焰升起来呢?这就要用到我们刚才讲到的Blur了。由于火焰是要向上升的,所以我们不能简单的选择待处理pixel的上、下 、左、右四个相临pixel,而是要选择它本身和它下面的三个pixel。具体实现的pseudo-code如下:
for (int y=0; y
{
for (int x=0; x
{
pixel(x,y)=(pixel(x,y)+
pixel(x-1,y+1)+
pixel(x,y+1)+
pixel(x+1,y+1))/4;
//……
putpixel (x,y,bright);
}
}
3.减衰减因子:
看到上面的 "//……" 了么?对了,那儿就是我们要添加如下这段代码的地方。为什么要减掉一个衰减因子呢?让我们通过两副图的对比来说明它的重要性吧:
图1. 减掉了衰减因子 (Fire.exe)
图2. 没有衰减(不可取!)
具体实现的pseudo-code如下:
if (--bright
bright = 0;//衰减且保证bright=0
记住:千万不要忘了检验不要让bright小于0,否则……自己试试就知道了,嘿嘿~~~~~~
也许读者会发现我们现在的火焰有些死板,所以我们不妨试着在火焰上加入一些spark(火星),这可以使我们的火焰更加有戏剧性虽然它在一定程度上削弱了火焰的真实性。其效果图如下:
图3. 添加了火星的效果 (FireWithSpark.exe)
看过Seumas McNally(我很崇拜的大师,呵呵)的ParticleFire(粒子火焰,见ParticleFire.exe)的朋友可能会知道,他在他的火焰处理上就用这个算法以使他的火焰更富戏剧性。
到此为止,我已经完整地介绍了模拟一个火焰效果地全部算法。但是这样模拟出来的火焰始终过于死板、不够逼真(当然,最为逼真的效果莫过于通过基于Alpha通道的伪粒子系统的实现,在这里我们不作讨论)。如何才能解决这个问题呢?一个很好的办法是把我们的热源放置在一个运动的物体上(Hugo就曾使用feed back和warp的技术逼真地模拟了火焰的效果,中国的程序员也有用与Hugo完全相同的算法真实模拟了这个效果),而不是放在屏幕底部,这样通过物体的运动再加上一些放缩、模糊技巧就可以使我们的火焰产生出意想不到的效果了!^_^。
具体运行效果如图4所示:
图4. 通过Rotate产生的火焰效果 (CoolFire.exe)
下面我就对这个算法的具体步骤进行简要地分析,并在适当的地方给出相应的代码、伪码。
这个算法主要也分成四个步骤:放置热源、移动物体、放缩、模糊。
1.放置热源:
这个步骤和上面步骤完全一样,不过就是画出一个物体。
2.移动物体:
在我的程序中我用了一系列的矩阵变换以完成物体的运动,当然也可以像Hugo一样,用feed back的技巧(只不过就是在一个平面上而不是3维空间中旋转物体)。具体实现见CoolFire.cpp,关于矩阵运算的具体实现见我的《走进3D的世界--C++中用运算符重载实现矩阵运算》一文,有关矩阵的运算请参考计算几何的有关书籍,在这里不作过多讨论。
Obj=rotX(thetaX)*rotY(thetaY)*rotZ(thetaZ);
3.放缩:
在程序中我的放缩采用了这样一个算法:先任取一定点(x,y),然后依次把屏幕上的每一个点与这个点比较,用它们之间的距离乘上一个放缩因子在加到(x,y)上,这个值便是放缩的索引值,以下便是实现的代码(未优化):
long Offset=0;
for
(y=0;y
{
for (x=0;x
{
IndexX=sx+(x-sx)*ScaleX;
IndexY=sy+(y-sy)*ScaleY;
lpVideoBuffer[Offset++]=lpSrcBuffer[IndexY*320+IndexX];
}
}
为了便于理解,我没有对代码进行优化,读者在编写程序时必须对以上代码进行有效地优化,具体方法是:先在一次循环中算出IndexX和IndexY的值并保存在数组中,待进行放缩操作时直接用它们作索引放缩,而不是像上面的代码一样边计算边操作放缩。
4.模糊:现在全国人民都知道Blur了吧,那我就不再罗嗦了。
好了,我想,对于火焰的算法我已经很清晰地展示在您地面前了,有效地运用这些算法一定会为您地游戏开发带来很大地便利。
文章中的所有实例(除了ParticleFire以外)均在Watcom C++中调试成功,读者在运行的时候必须保证您的运行目录里有dos4gw.exe,这是一个开启保护模式的程序,如果没有程序将无法运行。
(作者:周军)