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

雪花粒子系统

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

前言

整理之前做的粒子系统,这次是雪花篇。使用的图形API为OpenGL3.3+,采用OpenGL的trasnform feedback特性。如果对transform feedback特性不了解的同学,建议翻一下前面喷泉粒子系统里面介绍的教程链接,本人不再赘述。

雪花的物理模型

雪花如果不考虑碰撞检测和雪花堆积效果话,那么也没什么复杂,所以我这里暂时没有考虑物理碰撞效果。另外目前雪花是在一片区域内,因为区域比较小的缘故。但是如果区域大的话,全部暴力渲染绝对不可取,这时就要考虑进行视锥体的裁剪了,但没那么简单,要考虑一下摄像机的快速转换。另外一种折中的办法就是以摄像机为中心,只渲染方圆半径R内的雪花粒子,这样简单了许多!接下来我们就简单讨论一下雪花粒子的物理属性。

1.雪花的分布,并没有什么特别的地方,雪花的散落通常是均匀分布的。所以初始化雪花粒子的发射器的时候,指定一个区域XOZ平面,在这个区域内随机生成粒子坐标,这样就能生成均匀分布的雪花粒子发射器。

        pos.x = (2.0f*float(rand())/float(RAND_MAX)-1.0f)*areaLength;
        pos.z = (2.0f*float(rand())/float(RAND_MAX)-1.0f)*areaLength;
        pos.y = fallHeight;

上面就是发射器粒子的初始化,areaLength就是雪花区域长度的一半,在XOZ平面上随机选取[-areaLength, areaLength]位置。

2.雪花的运动,最基本的就是受到了重力加速度的影响,所以它的基础加速度向量为vec3(0.0,-9.81,0.0)。下雪的过程中通常伴随着风,所以如果考虑风力的话,那么在加速度向量的x和z分量上根据风力的方向和大小进行改动。这里我假设在x轴正方向上受到一个风力,那么它的重力加速度为vec3(10.0,-9.81,0.0)。假设相邻两帧之间时间变化量为gDeltaTimeMillis,那么速度变换量和位移变化量为:

DeltaP=Velocity0[0]DeltaTimeSecs
DeltaV=DeltaTimeSecsvec3(10.0,9.81,0.0)

那么下一帧的速度和位置就是上一阵速度和位置加上相应的变化量,Velocity0[0]为上一帧的速度。雪花粒子初始速度在y轴方向上,且方向向下,那么在初始化时就要保证粒子速度的y轴分量必须小于某个值(因为向下,为负数),这里设定为-0.5。如下所示:

            Dir = GetRandomDir(seed);
            Dir.y = min(Dir.y,-0.5f);

3.雪花的消亡,雪花落地即消亡,例如y<=0时粒子就消亡,现在粒子的年龄仅仅是用在发射器上,发射器的年龄到了就分裂出粒子,注意要保证年龄的随机性,不然忽出现下雪是一层一层的奇怪效果。保证粒子属性的随机性就是平均数法,在平均数左右摆动,即保证随机性,又维持了粒子整体的效果。前面的粒子系统介绍中已经提到过。

4.雪花粒子大小是不一致的,所以也要保证粒子大小的随机性,同样的我们制定粒子的最大值和最小值,在最大和最小之间随机指定当前粒子的大小。同时,要知道点精灵是不受透视矩阵的影响的,所以我们也要考虑粒子到视点的距离。

雪花粒子系统构建

粒子属性:

struct SnowParticle 
{
    float type;//粒子种类,分两类,一类为发射器,一类为运动粒子
    glm::vec3 position;
    glm::vec3 velocity;
    float lifetimeMills;//年龄
    float size;//粒子点精灵大小
};

粒子初始化:

void Snow::GenInitLocation(SnowParticle particles[],int nums)
{
    srand(time(NULL));
    for(int x = 0;x < nums;x ++){
        glm::vec3 record(0.0f);
        record.x = (2.0f*float(rand())/float(RAND_MAX)-1.0f)*areaLength;
        record.z = (2.0f*float(rand())/float(RAND_MAX)-1.0f)*areaLength;
        record.y = fallHeight;
        particles[x].type = PARTICLE_TYPE_LAUNCHER;
        particles[x].position = record;
        particles[x].velocity = (MAX_VELOC-MIN_VELOC)*(float(rand())/float(RAND_MAX))
                                +MIN_VELOC;//在最大最小速度之间随机选择
        particles[x].size = INIT_SIZE;//发射器粒子大小
        particles[x].lifetimeMills = 0.5f*(float(rand())/float(RAND_MAX))+0.1f;
    }    
}

粒子属性更新:在几何着色器中更新,因为要分裂新的点。

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

in float Type0[];
in vec3 Position0[];
in vec3 Velocity0[];
in float Age0[];
in float Size0[];

out float Type1;
out vec3 Position1;
out vec3 Velocity1;
out float Age1;
out float Size1;

uniform float gDeltaTimeMillis;//每帧时间变化量
uniform float gTime;//总的时间变化量
uniform sampler1D gRandomTexture;
uniform float MAX_SIZE;//最大大小
uniform float MIN_SIZE;//最小大小
uniform vec3 MAX_VELOC;/最大速度
uniform vec3 MIN_VELOC;//最小速度
uniform float MAX_LAUNCH;//最大发射年龄
uniform float MIN_LAUNCH;//最小发射年龄

#define PARTICLE_TYPE_LAUNCHER 0.0f //发射器
#define PARTICLE_TYPE_SHELL 1.0f    //运动粒子

vec3 GetRandomDir(float TexCoord);
vec3 Rand(float TexCoord);

void main()
{
    float Age = Age0[0] - gDeltaTimeMillis;
    float speedRate = 0.1f;
    if(Type0[0] == PARTICLE_TYPE_LAUNCHER){//雪花发射粒子
        if(Age <= 0 ){
            //发射第二级粒子
            Type1 = PARTICLE_TYPE_SHELL;
            Position1 = Position0[0];
            vec3 Dir = GetRandomDir(Age0[0]+gTime);
            //保证方向向下
            Dir.y = min(Dir.y,-0.5f);
            Velocity1 = normalize(Dir)/speedRate;
            Age1 = Age0[0];
            Size1 = (MAX_SIZE-MIN_SIZE)*Rand(Age0[0]+gTime).x+MIN_SIZE;
            EmitVertex();
            EndPrimitive();
            //重置发射器的年龄
            Age = (MAX_LAUNCH-MIN_LAUNCH)*Rand(Age0[0]).y + MIN_LAUNCH;
        }
        Type1 = PARTICLE_TYPE_LAUNCHER;
        Position1 = Position0[0];
        Velocity1 = Velocity0[0];
        Age1 = Age;
        Size1 = 0;
        EmitVertex();
        EndPrimitive();
      }
    else{
        if(Position0[0].y > 0){
            //单位转秒
            float DeltaTimeSecs = gDeltaTimeMillis/1000.0f;
            //属性变化量
            vec3 DeltaP = Velocity0[0]*DeltaTimeSecs;
            vec3 DeltaV = DeltaTimeSecs*vec3(10.0,-9.81,0.0);
            Type1 = PARTICLE_TYPE_SHELL;
            Position1 = Position0[0] + DeltaP;
            Velocity1 = Velocity0[0] + DeltaV;
            Age1 = Age;
            Size1 = Size0[0];
            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){//随机0-1
    vec3 ret = texture(gRandomTexture,TexCoord).xyz;
    return ret; 
}

粒子纹理:点精灵用gl_PointCoord对纹理采样,根据r分量进行片段丢弃discard。同时开启了融合GL_BLEND。
这里写图片描述

粒子渲染:片元着色器代码。

#version 330 

out vec4 color;
uniform sampler2D snowflower;

void main()
{
    vec4 texColor = texture(snowflower,gl_PointCoord);
    if(texColor.r < 0.3)discard;
    color = vec4(1.0f,1.0f,1.0f,texColor.a);
}

实现效果演示

雪花

图中的树不是模型,是通过L系统语法规则生成的三维分形树,在后面我将会进行介绍。

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值