本篇将带来粒子系统的烟花篇,这里我们将构建的牡丹型烟花。什么是牡丹型烟花呢?下面请看:
这种烟花呈球状,非常的饱满,这种烟花模拟比较简单,但是视觉效果看起来很棒!总的来说,烟花粒子是一种比较容易模拟的粒子系统,但它其实取决于模拟的烟花类型。例如利用烟花写文字,这种就比较难了!另外还有烟花的辉光拖尾效果,这也没那么简单。辉光拖尾本人暂时还没取实现,这里先挖个坑,等以后再填。
好了进入正题,这里本人使用的图形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就是粒子爆炸后的方向,Rand1和Rand2是两个不同的随机数,Pi为圆周率。这个方程在球体方向中随机选取,大量的粒子通过这样随机的计算整体组合成了球体,这就是局部一直随机,整体维持形状不变!
4.烟花受力分析。通常来讲就考虑一个重力加速度即可,所以加速度向量就是一个y分量为负的向量,如果你硬要考虑风力,那也可以。关于如何根据加速度和速度计算速度变化量和位移变换量,这是非常简单的物理知识,前面的几个粒子系统都提到过,不再赘述。
5.烟花的颜色和透明度。烟花的颜色随机选取,但是要知道在烟花中红色的分量通常是比较多的,其他两个绿色和蓝色分量比较少,故要适当地加一些约束,我的颜色随机选取函数如下:
vec3 ColorRand(float TexCoord)
{
vec3 ret = texture(gRandomTexture,TexCoord).xyz;
ret.z *= 0.2;
ret.y *= 0.5;
return ret;
}
接下来考虑烟花粒子的透明度问题,其实这主要考虑在烟花的爆炸阶段。在爆炸初期到烟花燃烧殆尽,这个过程烟花的透明度越来越低,也就是烟花越来越亮,爆炸初期完全透明,然后逐渐降低透明度。所以爆炸烟花粒子的透明度动态变化如下:
粒子属性:
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~