DX C++实现超炫酷粒子特效之烟花特效

首先,给各位说明的是,本人网站已恢复。www.jackchen.world和www.jackchen.work均可访问,已经过工信部和公安双层备案,所以请各位放心访问。

我记得前段时间写过一篇关于一个宇宙粒子效果的文章,使用JS编写,不过考虑那次效果比较杂,所以各位可以参考下。从这篇文章开始,针对特效我打算做一系列文章,目前想到的有烟花特效(本篇所讲),火焰特效,自然天气特效,水波特效,影子特效、运动模糊拖尾效果。。。。,本人暂时能想到这些,如果有其他需要实现的,可以前往本人的知乎或CSDN评论区评论(也不一定是特效相关,其他技术也可以)。微信公众号的留言板本人还没有迁移至本人的服务器上,而且本人最近这段时间一堆事,所以近期微信公众号留言功能不会加上。

本程序由以下几个部分组成:片头询问拦截,弹幕花雨模块,形状烟花,普通烟花。本程序不提供源码,只提供可执行文件和配置文件(见最后),方便各位预览和配置效果。该程序是本人很久以前所编写,至于背后的故事嘛。。。(略过,哈哈),之所以将这个拿出来说明,是因为这个程序中的烟花粒子特效技术还是值得和各位分享的。本篇文章只对该程序的烟花进行讲解。先来一张效果图,不然都是空谈,哈哈。
DX C++实现超炫酷粒子特效之烟花特效_第1张图片

粒子,不知道各位同僚是如何理解的,可能搞过游戏开发的人,对这块更懂一些。先说一下我对这块的感受,最开始接触粒子的时候,一头雾水。不知这玩意如何和程序结合在一起,总觉得玄乎的很。先给各位给出部分电影或游戏片段场景图。见下:
DX C++实现超炫酷粒子特效之烟花特效_第2张图片

分别源自:电影《惊奇队长》身体周围火焰发光效果,游戏《绝地求生》河流水波起伏效果,游戏《英雄联盟》视野区域效果,游戏《奥日越黑暗森林》跳跃拖尾效果。

我想大多数从未接触过粒子特效开发的人来说,总会觉得游戏中的炫酷特效交给游戏引擎来做,视频中的粒子特效交给各种视频软件来做。但是我们忽略了一点的就是,无论游戏引擎或者视频软件都是程序,都是由我们程序员开发的。所以粒子特效本身也是由程序员编码实现,就是麻烦一点,没有那么玄乎。这次带领各位深度剖析一下粒子特效,哈哈。

说到特效,更多开发者可能会想到和Shader关联。的确,游戏引擎和3D图像库一般都会提供自己的一种Shader脚本语言,大同小异。本人觉得Sharder的主要作用就是如何能让开发者能快速的写出高效的粒子特效。前期本人的写的关于粒子特效的文章和Shader无关,纯数据结构实现,也就是说可以基于任何图像库实现。粒子特效是属于图形编程的,因为是涉及的操作像素和纹理的。不管你使用什么语言最终是需要将效果通过渲染到屏幕上进行显示的。这里提供C/C++语言的图像操作库(EasyX,GDI,GDI+,Direct3D,DirectDraw,OpenGL)。需要至少会一种图像操作库的简单使用,仅仅是用于渲染。

烟花粒子,我这里基于Direct3D图形库实现了两种2D烟花。一种形状烟花,一种普通散开的烟花。不知道各位还有印象没有,在win7/xp的蜘蛛纸牌胜利后会有一个烟花界面(2D),还有前段时间情人节前夕吃鸡游戏里面会有一个撒狗粮的的烟花场景(3D),哈哈。其实,不管2D还是3D烟花,基本形式是一样的,就是在运算的时候多了一维,增加了运算复杂度。

烟花特效,我这里是分为发射簇,和爆炸簇。然后发射簇和爆炸簇分别由很多单个烟花粒子组成。烟花特效单元组成就是单个粒子而已。分别给出发射簇和爆炸簇粒子截图:
DX C++实现超炫酷粒子特效之烟花特效_第3张图片

形状烟花主要流程,发射簇->形状->爆炸簇;普通烟花主要流程,发射簇->爆炸簇。在说明各流程之前,我先给出该程序中的单个粒子数据结构和簇数据结构:

///单个粒子结构
struct FireworksPartical
{
    bool bExist;                //是否存在
    int nLifeTime;              //生命周期
    int nRunTime;               //运行时间
    DWORD dwCurTime;            //当前时间
    DWORD dwColor;              //颜色
    int nTextureId;             //纹理id

    D3DXVECTOR3 vPos;           //位置
    D3DXVECTOR3 vBeg;           //起点
    D3DXVECTOR3 vEnd;           //终点
    D3DXVECTOR3 vAccelSpeed;    //加速度

    int nMode;                  //方式(0速度计算,1终点插值计算, 2速度和插值混合计算)
    bool bTag;                  //标记

    //...
};

///簇结构(包含发射簇、爆炸簇)
struct FireworksClusterPartical
{
    int nExplosionNum;                         //爆炸粒子数量
    FireworksPartical* ptExplosionParticle;    //爆炸粒子结构
    int nExplosionCount;                       //粒子计数器
    int nExplosionRunTimeHalfCount;            //运行时间过半粒子计数器

    int nEmissionNum;                          //发射粒子数量
    FireworksPartical* ptEmissionParticle;     //发射粒子结构

    D3DXVECTOR3 vCurPos;                       //当前位置
    int nRiseHeight;                           //上升高度
    D3DXVECTOR3 vRiseSpeed;                    //上升速度
    DWORD dwCurTime;                           //当前时间
    DWORD dwFlickerTime;                       //闪烁时间
    DWORD dwFlickerColor;                      //闪烁颜色

    int nType;                                 //类型(烟花类型)
    int nStatus;                               //状态(-1初始状态|0上升状态|1爆炸状态|2结束)
    bool bTag;                                 //标记
    bool bFlicker;                             //闪烁
    int nTextureType;                          //纹理类型
    
    //...
};

在一般的粒子结构中,都会有这么几个共有的属性(生命周期,生成时间,生成位置,位置,运动速度【矢量,含方向】等),上面结构由于是几年前编写的,所以抽象的不是很好,仅供大家参考。以下是该程序中用到粒子属性详细说明:

  • 生命周期:多数情况下粒子都具备该属性,从粒子创建的那一刻起,就需要开始计时,当累计时间超过生命周期的时候,销毁即可。但形状(粒子逐渐变成爱心或字母形状)过程例外,是不需要生命周期的,这时会指定结束位置,是通过插值进行运算的;
  • 生成时间:在粒子创建的那一刻起,记录当时的时间戳;
  • 生成位置:在粒子创建的那一刻起,记录当时的粒子位置(依赖发射簇和爆炸簇中心位置)
  • 位置:这个是实时位置,需要每帧更新,根据速度方向/轨迹插值方程计算获得;
  • 速度:x、y、z三个方向可控随机生成,正负表示运动方向;
  • 终点位置:粒子不一定有该结构,仅在形状(粒子逐渐变成爱心或字母形状)过程使用;
  • 颜色:可以在粒子创建的时候随机指定,也可以在粒子运动过程中随机指定。粒子的颜色,为了让烟花更加绚丽,还有是为了让粒子实现淡出效果;
  • 纹理:粒子创建的那一刻起,随机指定。烟花中的粒子纹理,本人这里提供了11种纹理(百合、彼岸、蝴蝶兰、蓝色妖姬、满天星、玫瑰、蔷薇、勿忘我、郁金香、栀子、紫罗兰);
  • 位置计算方式:该程序中有三种情况,根据速度计算位置,插值计算位置以及速度插值混合计算位置;

下面在对簇(发射簇/爆炸簇)属性详细说明:

  • 发射簇粒子数量:初始化烟花效果的时候就需要指定,并创建烟花粒子;
  • 爆炸簇粒子数量:初始化烟花效果的时候就需要指定,并创建烟花粒子;
  • 位置:发射簇和爆炸簇的中心位置,起始是基于屏幕最下方随机生成x坐标,粒子的生成位置就是该位置,换句话说簇的中心位置就是粒子发射源;
  • 发射簇位移:初始化烟花效果时指定,基于屏幕最下方位置进行计算
  • 发射簇速度:初始化烟花效果时可控随机
  • 烟花状态:发射状态,爆炸状态(上述说的形状也是在这个环节)
  • 烟花类型:普通烟花,具体形状烟花(不同的形状,轨迹方程是不一样的)

发射簇,由几百个烟花粒子组成,这些粒子一直在循环使用。当有粒子消亡的时候,这时重置该粒子属性(生命周期,速度,生成位置等)。只有当整个发射簇需要消亡的时候(具体时时机是当发射簇运动完发射簇位移时),这些粒子才会被真正销毁。

1、形状烟花,发射簇销毁的那一刻。这时会将发射簇的中心位置,作为即将要执行形状过程的粒子的生成位置,并初始化粒子的其他属性(速度,终点位置等)。待形状过程完成后在进入爆炸状态,进入爆炸状态的粒子的起始位置就是形状过程最后的粒子位置,然后在初始化粒子的(生命周期,速度等);

2、普通烟花,发射簇销毁的那一刻。这时会将发射簇的中心位置,作为即将要执行爆炸状态的粒子的生成位置,并初始化粒子的其他属性(生命周期,速度等)

在整个烟花销毁前夕,需要计算透明度,实现淡出过程。淡出实现在本人之前一篇文章说明过,这里不再说明了。为了更好帮助理解,下面给出简单示意图(凑合看吧):
DX C++实现超炫酷粒子特效之烟花特效_第4张图片

到这里,烟花整个过程基本讲完了(本人语言组织能力有限,只能到这里了,哎)。下面讲一下轨迹方程,本人在最开始实现部分烟花字母之后,打算搞一个烟花字体出来,无奈太庞大了,麻烦的要命,所以只实现了26个字母,0-9的数字以及三种爱心符号。各取每种类型的其中一种的轨迹方程进行说明,这里为了让字体大小统一,规定的x,y的范围均为 [-1, 1],心形除外,但是之后会乘以一个系数统一大小。

爱心轨迹方程(注意定义域):
DX C++实现超炫酷粒子特效之烟花特效_第5张图片

字母轨迹方程(A):
DX C++实现超炫酷粒子特效之烟花特效_第6张图片

数字轨迹方程(0):
DX C++实现超炫酷粒子特效之烟花特效_第7张图片

需要强调的是,任何字母和数字,以及后面要扩展的汉字都是可以由各种直线,曲线组成,只要控制好定义域就可以实现了。如果各位同僚有魄力想实现一套字体,这里给各位一种稍微高效点的方式,实现五笔的偏旁部首,然后组合即可。比单个实现文字的工作量要小太多。

需要提醒各位的是,在计算粒子位置的时候要注意坐标系变换。像Direct3D的话是左手坐标系,OpenGL是右手坐标系,至于其他2D坐标是以屏幕左上角为原点,向下是y轴正方向,向右是x轴正方向。

最后,谈一下本人的感受吧。本片文章中用的游戏或影视素材基本都是国外研发的,但这些大作在国内受到广大玩家和影迷的追捧。每每和身边的人聊到影视和游戏时,谈及国内时评价不高(尽管这是实时)。有时会想,难道国内做不出来吗?我觉得国内如果想做还是做得出来的,如果说是因为缺乏相关的技术人才,我只能说我们国内有掌握那些高级技术的人,只不过这种人少,但并不是说没有。我个人觉得国内缺的是一个有资金并且有魄力的组织。只有这些组织才有能力把拥有相关技术的人才笼络到一块,并且有魄力投入研发。所以在这个有资金并且有魄力的组织出现之前,身为开发者的我们,我们先掌握好相关的技术,我相信不久的将来国内拍摄研发出我们常说的影视和游戏大作必定有我们的参与。愿与各位同僚共勉之!加油!

链接:https://pan.baidu.com/s/1WgvLPrTYPK_wZdm4vh10BA
提取码:jc8i

你可能感兴趣的:(程序员JC,c++,directx,粒子群算法,游戏)