笔者: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禁用掉,可以提高系统的效率。
基本类的设计
在上面,说了这么多,终于要体如今代码上面。以下是粒子系统的抽象类。当我们须要创建一个新的效果的时候,仅仅要继承这个类,而且复写虚函数就可以。当然,这里的仅仅是一个非常easy的粒子系统设计,提供的粒子属性也非常少。可是也可以做出非常多的效果出来了。假设读者。希望更复杂的效果,就行自己来扩展这个基本类别。然后加入你自己的功能。
废话不多说,直接上代码:
//----------------------------------------------------------------------- // 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 ; } }
这个粒子的效果例如以下截图:
略微改下。还能实现这种效果:
粒子系统的关键就在与怎样控制粒子的产生,运动等等。通过控制这些,你可以实现不论什么你想要的效果。
行,这是今天结束。
版权声明:本文博客原创文章,博客,未经同意,不得转载。