版权声明:本文为博主原创文章,未经博主允许不得转载。
作者:i_dovelemon
来源:CSDN
日期:2014 / 10 / 16
主题:Point Sprite, Particle System
引言
在游戏中,很多的绚丽,灿烂的特效,都可以使用粒子系统制作出来。那么粒子系统,到底是什么?它是如何工作的?接下来的文章,将会向大家讲述如何构建一个简单的粒子系统。
粒子系统
所谓的粒子系统,指的是一个粒子管理器,用于在特定条件下产生特定的粒子,并且赋予粒子以运动。所以,可以将粒子系统看成是一个个粒子的集合。而不同的效果,是要我们自己来控制粒子如何产生,粒子的颜色如何变化,粒子如何进行运动。通过控制这些条件,我们就能够创造出很多很多的特效出来。而关键就在于如何控制粒子的运动和产生。
既然,我们明白了粒子系统的概念,那么一个粒子到底是什么?它在计算机中是如何进行表示的?
简单而言,粒子在计算机中就是使用一个结构来表达。结构中保存了粒子的基本属性,如位置,颜色,生命,以及运动状态相关的参数。不同复杂度的粒子系统,粒子所包含的属性并不相同。如果需要简单的效果,那么只需要几个基本的属性即可。如果要做出更加复杂,或者更加符合动力学的粒子系统,可以在结构中再添加很多不同的物理属性。至于如何添加这些属性,要依赖于你所需要实现的粒子系统的复杂程度,想要支持的功能来进行设计。
当我们为粒子系统设计好了一个粒子结构之后,并且也有了粒子系统,来负责粒子的产生,以及运动等等。我们需要的当然就是显示在界面上。如果没有显示任何的内容,即使你的系统再强大,也是白费力气。
DirectX中支持很多的基本单元类型,最常用的如三角形列表,线条列表等等。在这里,我们将会使用一个称之为Point Sprite的基本单元类型。
Point Sprite,实际上就是一个点。我们在应用程序阶段,只需要将它当做点进行处理即可。但是要显示效果,我们自然还是需要进行纹理映射。由于Point Sprite的特殊性,DirectX内部会自动的为这些点设置纹理坐标。注意,这里的点只是逻辑意义上的。DirectX在最后处理的时候,还是会使用多边形来进行处理。所以这里说的点,存在一个大小的问题。我们能够通过程序来控制产生的点的大小。
为了实现一些效果,我们需要开启Alpha blend。毕竟做粒子特效,如果没有进行颜色混合的话,就是一个一个的单独的纹理,这并不是粒子效果了。
而且,粒子在界面显示的时候的先后顺序,对于我们来说并不重要。所以,将depth test以及depth buffer禁用掉,能够提高系统的效率。
基本类的设计
在上面,说了这么多,最终要体现在代码上面,下面是粒子系统的抽象类。当我们需要创建一个新的效果的时候,只要继承这个类,并且复写虚函数即可。当然,这里的只是一个很简单的粒子系统设计,提供的粒子属性也很少。但是也能够做出很多的效果出来了。如果读者,希望更复杂的效果,就可以自己来扩展这个基本类别,然后添加你自己的功能。
废话不多说,直接上代码:
- //-----------------------------------------------------------------------
- // declaration : Copyright (c), by XJ , 2014. All right reserved .
- // brief : This file will define the Particle system
- // author : XJ
- // file : PSystem.h
- // date : 2014 / 10 / 15
- // version : 1.0
- //-----------------------------------------------------------------------
- #pragma once
- #include<d3dx9.h>
- #include"AABB.h"
- #include"Camera.h"
- #include<vector>
- using namespace XJCollision ;
- using namespace std ;
- struct Particle
- {
- D3DXVECTOR3 initPos ;
- D3DXVECTOR3 initVelocity;
- float initSize ; //in pixel
- float initTime ;
- float lifeTime ;
- float mass ;
- D3DXCOLOR initColor ;
- static IDirect3DVertexDeclaration9* decl ;
- };
- class PSystem
- {
- public:
- PSystem(
- const char* fxName,
- const char* techName,
- const char* texName,
- const D3DXVECTOR3& accel,
- const AABB& box,
- int maxNumParticles,
- float timePerParticle,
- LPDIRECT3DDEVICE9 device,
- Camera* camera);
- virtual ~PSystem();
- public:
- float getTime();
- void setTime(float fTime);
- const AABB& getAABB() const ;
- void setWorldMtx(const D3DXMATRIX& world);
- void addParticle();
- virtual void onLostDevice();
- virtual void onResetDevice();
- virtual void initParticles(Particle& out) = 0;
- virtual void update(float dt);
- virtual void draw() ;
- protected:
- LPDIRECT3DDEVICE9 m_pDevice; // Device
- ID3DXEffect *m_FX ; // Effect
- D3DXHANDLE m_hTech; // Technique
- D3DXHANDLE m_hWVP ; // WVP matrix
- D3DXHANDLE m_hEyePosL; //
- D3DXHANDLE m_hTex; // Texture
- D3DXHANDLE m_hTime; // Time
- D3DXHANDLE m_hAccel; // Accel
- D3DXHANDLE m_hViewportHeight; // Viewport's height
- IDirect3DTexture9* m_Tex; // Texture
- IDirect3DVertexBuffer9* m_VB ; // Vertex buffer
- D3DXMATRIX m_World; // World matrix
- D3DXMATRIX m_InvWorld; // Inverse matrix
- float m_Time ; // Time
- D3DXVECTOR3 m_Accel ; // Accelerate
- AABB m_AABB; // Bounding box
- int m_MaxNumParticles; // Max number particles
- float m_fTimePerParticle; // Delay time to emit one particle
- Camera *m_pCamera ; // Camera
- std::vector<Particle> m_Particles; // Particles list
- std::vector<Particle*> m_AliveParticles; // Alive particles list
- std::vector<Particle*> m_DeadParticles; // Dead particles list
- };// end for PSystem
- #include"PSystem.h"
- #include"MyUtil.h"
- #include<d3dx9.h>
- IDirect3DVertexDeclaration9* Particle::decl = NULL ;
- /**
- * Constructor
- */
- PSystem::PSystem(const char* fxName,
- const char* techName,
- const char* texName,
- const D3DXVECTOR3& accel,
- const AABB& box,
- int maxNumParticles,
- float timePerParticle,
- LPDIRECT3DDEVICE9 device,
- Camera* camera)
- {
- //Save the device
- m_pDevice = device ;
- //Create error buffer
- ID3DXBuffer* _error = NULL ;
- HR(D3DXCreateBuffer(128, &_error));
- //Create the Effect
- HR(D3DXCreateEffectFromFileA(m_pDevice, fxName,
- NULL,NULL, D3DXSHADER_DEBUG, NULL, &m_FX, &_error));
- //If error
- if(_error)
- {
- MessageBoxA(NULL,(char*)_error->GetBufferPointer(),"Error", MB_OK);
- return ;
- }
- //Get the technique handle
- m_hTech = m_FX->GetTechniqueByName(techName);
- //Get all the handle
- m_hWVP = m_FX->GetParameterByName(0, "gWVP");
- m_hEyePosL = m_FX->GetParameterByName(0, "gEyePosL");
- m_hTex = m_FX->GetParameterByName(0, "gTex");
- m_hTime = m_FX->GetParameterByName(0, "gTime");
- m_hAccel = m_FX->GetParameterByName(0, "gAccel");
- m_hViewportHeight = m_FX->GetParameterByName(0, "gViewportHeight");
- //Create the texture
- HR(D3DXCreateTextureFromFileA(m_pDevice, texName, &m_Tex));
- //Set parameters
- HR(m_FX->SetTechnique(m_hTech));
- HR(m_FX->SetTexture(m_hTex, m_Tex));
- HR(m_FX->SetVector(m_hAccel, &D3DXVECTOR4(m_Accel,0.0f)));
- //Save the time per particles
- m_fTimePerParticle = timePerParticle ;
- m_Time = 0.0f ;
- //Save the AABB
- m_AABB = box ;
- //Save the camera
- m_pCamera = camera ;
- //Allocate the memory for the particle
- m_MaxNumParticles = maxNumParticles ;
- m_Particles.resize(m_MaxNumParticles);
- m_AliveParticles.reserve(m_MaxNumParticles);
- m_DeadParticles.reserve(m_MaxNumParticles);
- //They start all dead
- for(int i = 0 ; i < m_MaxNumParticles ; i ++)
- {
- m_Particles[i].initTime = 0.0f ;
- m_Particles[i].lifeTime = -1.0f ;
- }
- //Create the vertex buffer
- HR(m_pDevice->CreateVertexBuffer(m_MaxNumParticles * sizeof(Particle), D3DUSAGE_DYNAMIC|D3DUSAGE_WRITEONLY|
- D3DUSAGE_POINTS,0,D3DPOOL_DEFAULT,&m_VB,NULL));
- }// end for constructor
- /**
- * Destructor
- */
- PSystem::~PSystem()
- {
- m_AliveParticles.clear();
- m_DeadParticles.clear();
- m_Particles.clear();
- m_FX->Release();
- m_Tex->Release();
- m_VB->Release();
- }// end for destructor
- void PSystem::setTime(float fTime)
- {
- m_Time = fTime ;
- }// end for setTime
- void PSystem::setWorldMtx(const D3DXMATRIX& world)
- {
- m_World = world ;
- D3DXMatrixInverse(&m_World,NULL,&m_World);
- }// end for setWorldMtx
- void PSystem::addParticle()
- {
- if(m_DeadParticles.size() > 0)
- {
- Particle* p = m_DeadParticles.back();
- initParticles(*p);
- m_DeadParticles.pop_back();
- m_AliveParticles.push_back(p);
- }
- }// end for addParticle
- void PSystem::onLostDevice()
- {
- //OnlostDevice for fx
- m_FX->OnLostDevice();
- if(m_VB)
- {
- m_VB->Release();
- m_VB = NULL ;
- }
- }// end for onLostDevice
- void PSystem::onResetDevice()
- {
- //OnResetDevice for fx
- m_FX->OnResetDevice();
- if(m_VB)
- {
- //Recreate the vertex buffer
- HR(m_pDevice->CreateVertexBuffer(m_MaxNumParticles*sizeof(Particle),
- D3DUSAGE_DYNAMIC|D3DUSAGE_WRITEONLY|D3DUSAGE_POINTS,0,
- D3DPOOL_DEFAULT,
- &m_VB,
- NULL));
- }
- }// end for onResetDevice
- void PSystem::update(float dt)
- {
- m_Time += dt ;
- //Rebuild the dead list and alive list
- m_DeadParticles.resize(0);
- m_AliveParticles.resize(0);
- //For each particle
- for(int i = 0 ; i < m_Particles.size() ; i ++)
- {
- if((m_Time - m_Particles[i].initTime) > m_Particles[i].lifeTime)
- {
- m_DeadParticles.push_back(&m_Particles[i]);
- }
- else
- {
- m_AliveParticles.push_back(&m_Particles[i]);
- }
- }// end for
- //Check if it is the time to emit one another particle
- if(m_fTimePerParticle > 0.0f)
- {
- static float timeDelay = 0.0f ;
- timeDelay += dt ;
- if(timeDelay > m_fTimePerParticle)
- {
- addParticle();
- timeDelay = 0.0f ;
- }
- }
- }// end for update
- void PSystem::draw()
- {
- //Get the camera's position in the world and make it relative to the particle system's local system
- D3DXVECTOR3 eyeW = m_pCamera->pos();
- D3DXVECTOR3 eyeL ;
- D3DXVec3TransformCoord(&eyeL, &eyeW, &m_InvWorld);
- //Set the FX parameter
- HR(m_FX->SetValue(m_hEyePosL,&eyeL,sizeof(D3DXVECTOR3)));
- HR(m_FX->SetFloat(m_hTime, m_Time));
- HR(m_FX->SetMatrix(m_hWVP, &(m_World* m_pCamera->viewproj())));
- HR(m_FX->SetInt(m_hViewportHeight, 600));
- //Draw
- //set the vertex buffer
- HR(m_pDevice->SetStreamSource(0, m_VB, 0, sizeof(Particle)));
- //set the vertex declaration
- HR(m_pDevice->SetVertexDeclaration(Particle::decl));
- Particle* p = 0 ;
- HR(m_VB->Lock(0, 0, (void**)&p, D3DLOCK_DISCARD));
- UINT vIndex = 0 ;
- for(int i = 0 ; i < m_AliveParticles.size(); i ++)
- {
- p[vIndex] = *m_AliveParticles[i] ;
- vIndex ++ ;
- }// end for
- HR(m_VB->Unlock());
- UINT numPass = 0 ;
- HR(m_FX->Begin(&numPass, 0));
- HR(m_FX->BeginPass(0));
- if(vIndex > 0)
- {
- m_pDevice->DrawPrimitive(D3DPT_POINTLIST,0,vIndex);
- }
- HR(m_FX->EndPass());
- HR(m_FX->End());
- }// end for draw
我在这个类的基础上继承了一个类,用于实现自己的效果:
- #pragma once
- #include"PSystem.h"
- class FireRing: public PSystem
- {
- public:
- FireRing(const char* fxName,
- const char* techName,
- const char* texName,
- const D3DXVECTOR3& accel,
- const AABB& box,
- int maxNumParticles,
- float timePerParticle,
- LPDIRECT3DDEVICE9 device,
- Camera* camera)
- :PSystem(fxName, techName, texName, accel,
- box, maxNumParticles, timePerParticle, device, camera)
- {
- };
- void initParticles(Particle& out)
- {
- //Save the init time
- out.initTime = m_Time ;
- //Calculate the life time from 2.0s to 4.0s
- out.lifeTime = 20.0f + 2 * (rand()%10001 * 0.0001) ;
- //Calculate the initialize size in pixel
- out.initSize = 50.0f + 10 * (rand()%10001 * 0.0001) ;
- //Calculate the a very small velocity
- static float angle = 0.0f ;
- D3DXVECTOR3 vel(0.5f, 1.0f, 0.5f);
- D3DXMATRIX m ;
- D3DXMatrixRotationY(&m,angle);
- D3DXVec3TransformCoord(&vel, &vel, &m);
- out.initVelocity = vel ;
- D3DXVec3Normalize(&out.initVelocity, &out.initVelocity);
- angle += 1.0f ;
- //Calculate the mass
- out.mass = 1.0f + (rand()%10001 * 0.0001) ;
- //Calculate the color
- float t = (0.5f + 0.5*(rand()%10001 * 0.0001)) ;
- out.initColor = D3DXCOLOR(0.0f, 0.0f, t * 1.0f, t * 1.0f);
- //Calculate the pos
- out.initPos.x = 0.0f;
- out.initPos.y = 0.0f ;
- out.initPos.z = 0.0f ;
- }// end for initParticle
- };
这个类只要复写它的粒子初始化函数就可以了。通过在初始化的里面进行设计,改变粒子的位置,状态等等,我们还是能够做出很多的效果出来。下面是这个效果配套的 Shader:
- uniform extern float4x4 gWVP ;
- uniform extern texture gTex ;
- uniform extern float3 gEyePosL;
- uniform extern float3 gAccel ;
- uniform extern float gTime ;
- uniform extern float gViewportHeight ;
- sampler TexS = sampler_state
- {
- Texture = <gTex>;
- MinFilter = LINEAR ;
- MagFilter = LINEAR ;
- MipFilter = POINT ;
- AddressU = CLAMP ;
- AddressV = CLAMP ;
- };
- struct OutputVS
- {
- float4 posH : POSITION ;
- float4 color: COLOR0 ;
- float2 tex0 : TEXCOORD0 ;
- float size : PSIZE ;
- };
- //VS
- OutputVS FireRingVS(
- float3 posL: POSITION,
- float3 vel : TEXCOORD0,
- float size : TEXCOORD1,
- float time : TEXCOORD2,
- float lifeTime: TEXCOORD3,
- float mass : TEXCOORD4,
- float4 color: COLOR0
- )
- {
- //Clear the output
- OutputVS outVS = (OutputVS)0 ;
- float t = gTime - time ;
- posL = posL + t * vel ;
- outVS.posH = mul(float4(posL,1.0f), gWVP);
- size += 0.8 * t ;
- float d = distance(posL, gEyePosL);
- outVS.size = size ; //gViewportHeight * size / (1.0 + 8.0f*d);
- color.r = 0.0f ;
- color.g = 0.0f ;
- color.b = 1.0f * (t / lifeTime) ;
- color.a = 1.0f - 1.0f * (t / lifeTime) ;
- outVS.color = color ;//(1.0f - (t / lifeTime)) ;
- return outVS ;
- }
- //PS
- float4 FireRingPS(float4 color:COLOR0,
- float2 tex0: TEXCOORD0):COLOR
- {
- return color * tex2D(TexS, tex0);
- }
- technique FireRingTech
- {
- pass P0
- {
- vertexShader = compile vs_2_0 FireRingVS();
- pixelShader = compile ps_2_0 FireRingPS();
- PointSpriteEnable = true ;
- AlphaBlendEnable = true ;
- SrcBlend = One ;
- DestBlend = One ;
- ZWriteEnable = false ;
- }
- }
这个粒子的效果如下截图:
稍微改下,还能实现这样的效果:
粒子系统的关键就在与如何控制粒子的产生,运动等等。通过控制这些,你能够实现任何你想要的效果。
好了,今天到这里结束了。