粒子系统--烟花 [OpenGL-Transformfeedback]

烟花粒子系统

  • 前言
  • 烟花的物理模型
  • 烟花粒子系统的构建
  • 实现效果演示

前言

本篇将带来粒子系统的烟花篇,这里我们将构建的牡丹型烟花。什么是牡丹型烟花呢?下面请看:
粒子系统--烟花 [OpenGL-Transformfeedback]_第1张图片
这种烟花呈球状,非常的饱满,这种烟花模拟比较简单,但是视觉效果看起来很棒!总的来说,烟花粒子是一种比较容易模拟的粒子系统,但它其实取决于模拟的烟花类型。例如利用烟花写文字,这种就比较难了!另外还有烟花的辉光拖尾效果,这也没那么简单。辉光拖尾本人暂时还没取实现,这里先挖个坑,等以后再填。

好了进入正题,这里本人使用的图形API为OpenGL3.3+,采用OpenGL的trasnform feedback特性。如果对transform feedback特性不了解的同学,建议翻一下前面喷泉粒子系统里面介绍的教程链接,本人不再赘述。

烟花的物理模型

简单的牡丹型烟花并没有涉及到什么复杂的物理计算,接下来我就简单讨论一下烟花的物理属性。

1.烟花的运动的过程是阶段性非常明显的,首先有个发射器,然后发射器发射出一个烟花弹,当烟花弹寿命到了就会进行爆炸,向各个方向分裂出很多个金属球,这些金属球在空中边燃烧边运动,最后燃烧殆尽。

2.烟花的发射。仅仅有一个发射器,由这个发射器向天空发射烟花弹,一些普通的烟花中规中矩地向同一个方向发射,如每次都是垂直向上,这样的烟花实话说观赏性不高。我们的目标是模拟烟花的千轮效果,所谓千轮就是感觉天空中有很多烟花在燃放,有种百花齐放的感觉。所以我们给发射方向一个随机性,但要保证都是向上的,如下:

    vec3 Dir = GetRandomDir(seed);
    Dir.y = max(Dir.y,0.5);

3.烟花的爆炸分裂。爆炸成球状,那么我们可以通过球的参数方程来确定烟花爆炸的速度,目前速度的重点是方向。通过如下所示的方程来构造爆炸后的粒子方向:

Velocity.x = sin(Rand1*Pi)*cos(Rand2*2*Pi);
Velocity.y = sin(Rand1*Pi)*sin(Rand2*2*Pi);
Velocity.z = cos(Rand1*Pi);

Velocity就是粒子爆炸后的方向,Rand1Rand2是两个不同的随机数,Pi为圆周率。这个方程在球体方向中随机选取,大量的粒子通过这样随机的计算整体组合成了球体,这就是局部一直随机,整体维持形状不变!

4.烟花受力分析。通常来讲就考虑一个重力加速度即可,所以加速度向量就是一个y分量为负的向量,如果你硬要考虑风力,那也可以。关于如何根据加速度和速度计算速度变化量和位移变换量,这是非常简单的物理知识,前面的几个粒子系统都提到过,不再赘述。

5.烟花的颜色和透明度。烟花的颜色随机选取,但是要知道在烟花中红色的分量通常是比较多的,其他两个绿色和蓝色分量比较少,故要适当地加一些约束,我的颜色随机选取函数如下:

vec3 ColorRand(float TexCoord)
{
    vec3 ret = texture(gRandomTexture,TexCoord).xyz;
    ret.z *= 0.2;
    ret.y *= 0.5;
    return ret;
}

接下来考虑烟花粒子的透明度问题,其实这主要考虑在烟花的爆炸阶段。在爆炸初期到烟花燃烧殆尽,这个过程烟花的透明度越来越低,也就是烟花越来越亮,爆炸初期完全透明,然后逐渐降低透明度。所以爆炸烟花粒子的透明度动态变化如下:

alpha=AgegSecondaryShellLifetime

其中 Age即为粒子年龄, gSecondaryShellLifetime为爆炸烟花粒子的寿命,这样过渡就比较自然了。

烟花粒子系统的构建

粒子属性:

struct FireParticle 
{
    float type;//三类粒子,发射器,上升的礼花弹,爆炸的粒子
    glm::vec3 position;
    glm::vec3 velocity;
    float lifetimeMills;//以毫秒计算的生命周期
    float tag;//与随机数种子有关
    glm::vec4 color;//rgb以及alpha通道
};

粒子初始化:初始化就一个粒子,它就是发射器。

bool FireWork::InitFireWork(glm::vec3 & pos)
{
    FireParticle particles[MAX_PARTICLES];
    memset(particles, 0, sizeof(particles));
    particles[0].type = PARTICLE_TYPE_LAUNCHER;//设置第一个粒子的类型为发射器
    particles[0].position = pos;
    particles[0].lifetimeMills = 0.0f;
    particles[0].velocity = glm::vec3(0.0f, 0.01f, 0.0f);
    particles[0].tag = 0;
    particles[0].color = glm::vec4(0.5f,0.3f,0.1f,1.0f);
    glGenTransformFeedbacks(2, mTransformFeedbacks);
    glGenBuffers(2, mParticleBuffers);
    glGenVertexArrays(2,mParticleArrays);
    for (int i = 0; i < 2; i++)
    {
        //glBindVertexArray(mParticleBuffers[i]);
        glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, mTransformFeedbacks[i]);
        glBindBuffer(GL_ARRAY_BUFFER, mParticleBuffers[i]);
        glBindVertexArray(mParticleArrays[i]);
        glBufferData(GL_ARRAY_BUFFER, sizeof(particles), particles, GL_DYNAMIC_DRAW);
        glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, mParticleBuffers[i]);
        //glBindVertexArray(0);
    }
    //绑定纹理
    mUpdateShader->use();
    mUpdateShader->setInt("gRandomTexture",0);
    //设置粒子的生存时间
    mUpdateShader->setFloat("gLauncherLifetime", 0.1f*1000.0f);
    mUpdateShader->setFloat("gShellLifetime", 1.0f*1000.0f);
    mUpdateShader->setFloat("gSecondaryShellLifetime", 2.0f*1000.0f);
    glUseProgram(0);
    return true;
}

粒子属性更新:这里需要注意的时候,几何着色器的构造图元有上限,在我这里最多只能分裂78各粒子,烟花爆炸后才78个粒子有点太少,那怎么解决呢?方法就是在发射器构造上升的礼弹的时候,不要发射一个,发射多个一模一样的上升粒子,这样相同的粒子叠加爆炸,达到了所需的效果。这是本人的解决方案。

#version 330 core
layout (points) in;
layout (points,max_vertices = 78) out;

in float Type0[];
in vec3 Position0[];
in vec3 Velocity0[];
in float Age0[];
in float Tag0[];
in vec4 Color0[];

out float Type1;
out vec3 Position1;
out vec3 Velocity1;
out float Age1;
out float Tag1;
out vec4 Color1;

uniform float gDeltaTimeMillis;//每帧时间变化量
uniform float gTime;//总的时间变化量
uniform sampler1D gRandomTexture;//随机的纹理
uniform float gLauncherLifetime;//生命周期
uniform float gShellLifetime; 
uniform float gSecondaryShellLifetime;

#define PARTICLE_TYPE_LAUNCHER 0.0f //发射器
#define PARTICLE_TYPE_SHELL 1.0f //上升的礼弹
#define PARTICLE_TYPE_SECONDARY_SHELL 2.0f //烟花爆炸的粒子

vec3 GetRandomDir(float TexCoord);//随机方向
vec3 Rand(float TexCoord); //-1到1的随机数
vec3 ColorRand(float TexCoord); //随机颜色

void main()
{
    float Age = Age0[0] + gDeltaTimeMillis;
    float SpeedRate = 0.05f;
    if(Type0[0] == PARTICLE_TYPE_LAUNCHER){
        if(Age >= gLauncherLifetime){//发射器
            for(int x = 0;x < 5;x ++){//发射5个一模一样的礼弹
                Type1 = PARTICLE_TYPE_SHELL;
                Position1 = Position0[0];
                //随机方向
                vec3 Dir = GetRandomDir((gTime+Age0[0])/1000.0f);
                Dir.y = max(Dir.y,0.5);
                Velocity1 = normalize(Dir)/SpeedRate;
                Age1 = 0.0f;
                Tag1 = (x+1)*200;
                //随机颜色
                Color1 = vec4(ColorRand((gTime+Age0[0])/1000.0f),1.0f);
                EmitVertex();
                EndPrimitive();
            }
            //重置发射器年龄
            Age = 0.0f;
        }   
        Type1 = PARTICLE_TYPE_LAUNCHER;
        Position1 = Position0[0];
        Velocity1 = Velocity0[0];
        Age1 = Age;
        Tag1 = Tag0[0];
        EmitVertex();
        EndPrimitive();
    }
    else{
        float DeltaTimeSecs = gDeltaTimeMillis/1000.0f;
        //受重力加速的影响,计算速度和位移的变化量
        vec3 DeltaP = Velocity0[0] * DeltaTimeSecs;
        vec3 DeltaV = DeltaTimeSecs*vec3(0.0,-1.81,0.0);
        if(Type0[0] == PARTICLE_TYPE_SHELL){
            if(Age < gShellLifetime){
                Type1 = PARTICLE_TYPE_SHELL;
                Position1 = Position0[0] + DeltaP;
                Velocity1 = Velocity0[0] + DeltaV;
                Age1 = Age;
                Tag1 = Tag0[0];
                Color1 = Color0[0];
                EmitVertex();
                EndPrimitive();
            }   
            else{
                //烟花爆炸分裂大量的粒子
                for(int i = 0;i < 78;i ++){
                    Type1 = PARTICLE_TYPE_SECONDARY_SHELL;
                    Position1 = Position0[0];
                    vec3 Dir = Rand((gTime + i+Tag0[0])/1000.0);
                    float zv = sqrt(1-Dir.z*Dir.z);
                    float sita = 3.14159*Dir.x;
                    //下面的球体的参数方程
                    Velocity1.x = sin(Dir.x*3.14159265)*cos(Dir.y*2*3.14159265);
                    Velocity1.y = sin(Dir.x*3.14159265)*sin(Dir.y*2*3.14159265);
                    Velocity1.z = cos(Dir.x*3.14159265);
                    Velocity1 *= 2.0f;
                    Age1 = 0.0f;
                    Tag1 = 0;
                    Color1 = Color0[0];
                    EmitVertex();
                    EndPrimitive();
                }   
            }
        }
        else{
            //爆炸的粒子生命未到尽头,继续运动
            if(Age < gSecondaryShellLifetime){
                Type1 = PARTICLE_TYPE_SECONDARY_SHELL;
                Position1 = Position0[0] + DeltaP;
                Velocity1 = Velocity0[0] + DeltaV;
                Age1 = Age;
                Tag1 = 0;
                Color1 = Color0[0];
                //动态改变颜色的alpha分量
                Color1.w = Age1/gSecondaryShellLifetime;
                EmitVertex();
                EndPrimitive();
            }   
        }
    }
}

vec3 GetRandomDir(float TexCoord)
{
    vec3 Dir = texture(gRandomTexture,TexCoord).xyz;
    Dir -= vec3(0.5,0.5,0.5);
    return Dir;
}

vec3 Rand(float TexCoord)
{
    vec3 Dir = texture(gRandomTexture,TexCoord).xyz;
    Dir -= vec3(0.5,0.5,0.5);
    return Dir*2;
}

vec3 ColorRand(float TexCoord)
{
    vec3 Dir = texture(gRandomTexture,TexCoord).xyz;
    Dir.z *= 0.2;
    Dir.y *= 0.5;
    return Dir;
}

实现效果演示

本人目前尝试的粒子效果就这么多,所以粒子系统篇暂时结束辣。下一篇就是关于L系统语法规则构建漂亮的三维分形树。O(∩_∩)O~

你可能感兴趣的:(粒子系统)