【DX11学习笔记】粒子系统--爆炸特效

粒子系统

  粒子系统它可以被用于模拟一些特效现象,如:火焰、雨、烟雾、爆炸、法术效果等。本质上其实是一堆粒子的生成。在Direct11中,可以通过利用着色器反射来构建粒子系统Effect框架,并实现爆炸效果粒子系统。

  粒子系统的现有框架体系采用https://www.cnblogs.com/X-Jun/p/13342024.html博客中的ParticleEffect框架ParticleRender,在博客中也有提供源代码。

  爆炸特效的实现基于https://www.cnblogs.com/X-Jun/p/13342024.html博客中火焰特效。下面的内容只包括爆炸特效相关需要修改的内容,其他关于粒子系统的框架内容可以查阅上面给出的博客链接。

粒子系统的设计

  粒子的顶点结构至少需要包括粒子的中心位置和粒子的大小。下面是一个通用的粒子结构:

struct ParticleVertex
{
    XMFLOAT3 initialPos;   // 粒子的初始中心位置
    XMFLOAT3 initialVel;   // 粒子的初始速度
    XMFLOAT3 size;         // 粒的子大小
    float age;             // 粒子的存活时间
    uint type;             // 粒子的类型
};

  在本篇中实现的爆炸粒子效果,包含发射器粒子、外壳粒子,最终爆炸粒子三种粒子。其结构都是沿用上面的通用粒子结构。发射器粒子只存在一个,在发射出指定数量的外壳粒子后便不再发射新粒子。外壳粒子在运动随机时间后会发射指定数量的最终爆炸粒子。最终实现爆炸的特效。

随机单位向量和随机值

  在爆炸特效中,我们需要生成随机的单位向量来指定外壳粒子和最终爆炸粒子的运动方向。还需要生成随机的值来让外壳粒子发射最终爆炸粒子的时间随机。

  下面是获得随机单位向量和随机值的两个方法

float3 RandUnitVec3(float offset)
{
    // 使用游戏时间加上偏移值来采样随机纹理
    float u = (g_GameTime + offset);
    
    // 采样值在[-1,1]
    float3 v = g_RandomTex.SampleLevel(g_SamLinear, u, 0).xyz;
    
    // 投影到单位球
    return normalize(v);
}

float RandNum(float offset)
{
    // 使用游戏时间加上偏移值来采样随机纹理
    float u = (g_GameTime + offset);

    // 采样值在[-1,1]
    float v = g_RandomTex.SampleLevel(g_SamLinear, u, 0).x;

    return v;
}

 

爆炸特效对应的着色器

  下面只给出重要部分的着色器,其他着色器仍旧沿用火焰特效着色器,并没有修改。

//Fire_SO_GS.hlsl
#include "Fire.hlsli"

[maxvertexcount(30)]
void GS(point VertexParticle gIn[1], inout PointStream<VertexParticle> output)
{
    gIn[0].Age += g_TimeStep;//老化粒子

    if (gIn[0].Type == PT_EMITTER)//判断为发射器粒子
    {
        if (gIn[0].Age > g_EmitInterval)// 是否到时间发射新的粒子
        {
            [unroll]
            for (int i = 0; i < 30; i++)//产生30个外壳粒子
            {
                float3 vRandom = RandUnitVec3(i * g_GameTime);
                float Rantime = 0.5f * RandNum(i * g_GameTime);

                VertexParticle p;
                p.InitialPosW = g_EmitPosW.xyz;
                p.InitialVelW = 4.0f * vRandom;
                p.SizeW = float2(1.5f, 1.5f);
                p.Age = g_AliveTime - Rantime;//设定外壳粒子的随机初始寿命
                p.Type = PT_FLARE;
                output.Append(p);
            }
            gIn[0].Age =0.0f;
        }
        // 总是保留发射器
        output.Append(gIn[0]);
    }
    else if (gIn[0].Type == PT_FLARE)//判断为外壳粒子
    {
        if (gIn[0].Age >= g_AliveTime)
        {


            [unroll]
            for (int i = 0; i < 20; i++)//每个外壳粒子产生20个最终爆炸粒子
            {
                float3 vRandom = RandUnitVec3(i * g_GameTime * 0.9f);

                VertexParticle p;
                p.InitialPosW = gIn[0].InitialPosW;
                p.InitialVelW = 10.0f * vRandom;
                p.SizeW = float2(1.0f, 1.0f);//最终爆炸粒子的尺寸比外壳粒子稍小
                p.Age = 0;
                p.Type = PT_LAST;

                output.Append(p);

            }
        }
        else
        {
            output.Append(gIn[0]);
        }

        
    }
    else if (gIn[0].Type == PT_LAST)//判断为最终爆炸粒子
    {
        if (gIn[0].Age <= g_AliveTime/2)//最终爆炸粒子的存活时间缩短
        {
            output.Append(gIn[0]);
        }
    }
}

  在上面的几何着色器中,实现对两种粒子的发射。

//Fire_VS.hlsl
#include "Fire.hlsli"

VertexOut VS(VertexParticle vIn)
{
    VertexOut vOut;
    
    float t = vIn.Age;
    
    // 恒定加速度等式
    vOut.PosW = 0.5f * t * t * g_AccelW + t * vIn.InitialVelW + vIn.InitialPosW;

    if (vIn.Type == PT_LAST)//最终爆炸粒子颜色随着时间褪去
    {
        float opacity = 1.0f - smoothstep(0.0f, 1.0f, t / 1.0f);
        vOut.Color = float4(1.0f, 1.0f, 1.0f, opacity);
    }
    else if(vIn.Type == PT_FLARE)
    {
        vOut.Color = float4(1.0f, 1.0f, 1.0f, 1.0f);
    }
    vOut.SizeW = vIn.SizeW;
    vOut.Type = vIn.Type;

    return vOut;
}

   

 最终效果如下:

 

posted @ 2020-07-30 09:49  Neko_YG  阅读(523)  评论(0编辑  收藏  举报