Система частиц.

Содержание статьи:

Введение.
Основной класс системы частиц.
Класс "Система частиц".
Эмиттер.

Вступление.

к началу статьи
Доброго времени суток всем, кто сейчас читает эту статью. Как Вы, наверное, уже догадались, речь пойдет о системе частиц. Важность этого элемента игрового движка сложно переоценить. С его помощью делаются такие эффекты как снег, дождь, огонь, лазеры, дым, взрывы и т.д. Многие эффекты без них не реализуемы (в реальном времени). Поэтому ни один игровой моторчик не обходится без партиклов. Поэтому нам с вами жизненно необходимо разобраться с этой технологией и внедрить её в движок. Приступим?

Основной класс системы частиц.

к началу статьи
Порывшись в Интернете, Вы, наверное, могли обнаружить, что, говоря “система частиц”, подразумевают три понятия: эмиттер, аттрактор и собственно сами партиклы. Как правило, эти термины реализуются в виде трех классов и означают следующее: Аттрактор – моделирует различные силовые поля, воздействующие на партиклы (в этой статье не рассматриваются… может в другой раз). Эмиттер – управляет разбросом и “рождением” частиц (о процессе рождения поговорим позже) “сами партиклы” – подразумевается совокупность данных (вертексы, скорости, величины разброса частиц, время жизни и т.д.) представленная в удобном виде. Создадим соответствующие классы: Представление вершины партикла… ну тут вроде никаких тайн – данные + конструкторы.
#define	FVF_D3DPARTICLE_VERTEX D3DFVF_XYZB3 | D3DFVF_DIFFUSE | D3DFVF_NORMAL | D3DFVF_TEX1 

class cD3DParticleVertex:public cVector3{
public:
float c1;
float c2;
float c3;

cVector3 Norm;

DWORD Color;

float u1;
float v1;

cD3DParticleVertex( void ){
Color = 0xffffffff;
}
cD3DParticleVertex( float _x , float _y , float _z , float _u1 , float _v1 ):
cVector3( _x , _y , _z ){
u1 = _u1;
v1 = _v1;
Norm = cVector3( 0 , 0 , -1 );
Color = 0xffffff;
c1 = c2 = c3 = 0;
}
};
Самый главный класс этого урока:

Класс "Система частиц".

к началу статьи
class	cParticleSystem{
public:
//здесь хранится либо FVF вершины, либо хендл
//нашего вершинного шейдера
int Shader;

//курсор текстуры в TextureManager’е
int TextureCursor;

//время жизни каждого партикла
cArray<float> *LifeTime;
//массив скоростей каждого партикла
cArray<cVector3> *VelosityArray;
//вершины партиклов
cD3DVertexBuffer<cD3DParticleVertex> *VertexBuffer;
//эмиттер (более подробно о нем - далее)
cEmitter Emitter;
//это поле хранит свойства нашей системы частиц
//например, действует ли на неё гравитация, или подвержены ли
//частицы “старению”
int _EFFECTS;

void Create( cVector3 , cVector3 , float , int , float , int );
void CreateMathPart( float , float );
void CreateVisualPart( char * );
void Release( void );
void Render( LPDIRECT3DDEVICE8 );
void RunSystem( float );
cParticleSystem( void ){
Shader = FVF_D3DPARTICLE_VERTEX;
VelosityArray = NULL;
LifeTime = NULL;
VertexBuffer = NULL;
Emitter = stdEmitter;
}
~cParticleSystem(){}
};
Дополнительные комментарии к полям, я думаю, не нужны, а вот с методами разберемся по-подробнее. Перед тем как использовать систему, должна быть вызвана как минимум одна функция инициализации:
//p – начальная позиция партикла
//v – скорость партикла без учета разброса
//d – разброс скорости
//с – количество партиклов в системе
//size – размер партикла
//_effects – свойства нашей системы частиц
void cParticleSystem::Create( cVector3 p , cVector3 v , float d ,
int c , float size , int _effects ){
_EFFECTS = _effects;

//создаем необходимые массивы
VelosityArray = new cArray< cVector3 >;
VelosityArray->SetLength( c );

VertexBuffer = new cD3DVertexBuffer< cD3DParticleVertex >;
VertexBuffer->Vertexes.SetLength( c * 6 );
VertexBuffer->Vertexes.Cursor = 0;

for( int i( 0 ) ; i < c ; i++ ){
//устанавливаем скорость
cVector3 deviation( d * frand() - d / 2 ) ,
d * frand() - d / 2 , d * frand() - d / 2 );
( *VelosityArray )[ i ] = v + deviation;

//устанавливаем геометрию
VertexBuffer->AddVertex( cD3DParticleVertex(-size/2,-size/2,0,0,1));
( * VertexBuffer )[ VertexBuffer->Vertexes.Cursor - 1 ] += p;
VertexBuffer->AddVertex( cD3DParticleVertex(-size/2, size/2,0,0,0));
( * VertexBuffer )[ VertexBuffer->Vertexes.Cursor - 1 ] += p;
VertexBuffer->AddVertex( cD3DParticleVertex( size/2, size/2,0,1,0));
( * VertexBuffer )[ VertexBuffer->Vertexes.Cursor - 1 ] += p;
VertexBuffer->AddVertex( cD3DParticleVertex(-size/2,-size/2,0,0,1));
( * VertexBuffer )[ VertexBuffer->Vertexes.Cursor - 1 ] += p;
VertexBuffer->AddVertex( cD3DParticleVertex( size/2, size/2,0,1,0));
( * VertexBuffer )[ VertexBuffer->Vertexes.Cursor - 1 ] += p;
VertexBuffer->AddVertex( cD3DParticleVertex( size/2,-size/2,0,1,1));
( * VertexBuffer )[ VertexBuffer->Vertexes.Cursor - 1 ] += p;
}
//устанавливаем эмиттер
Emitter.OP1 = p;
Emitter.fOP1 = size;

Emitter.OP2 = v;
Emitter.fOP2 = d;
}
Осталось раскрыть смысл параметра d. Тут опять-таки много не скажешь – просто координаты вектора скорости будут лежать в интервале от v.c – d/x до v.c + d/2 (где с одна из координат x, y или z). Затем, если мы хотим сделать “стареющие” частицы, нужно вызвать функцию:
void	cParticleSystem::CreateMathPart( float m , float d ){
if( _EFFECTS && EFF_AGING ){
if( VertexBuffer ){
LifeTime = new cArray< float >;
LifeTime->SetLength( VertexBuffer->Vertexes.Cursor / 6 );
LifeTime->Cursor = VertexBuffer->Vertexes.Cursor / 6;
for( int i( 0 ) ; i < VertexBuffer->Vertexes.Cursor / 6 ; i++ ){
//заполняем по тому же принципу – мат.
//ожидание +- девиация
( * LifeTime )[ i ] = m + d * frand() - d / 2;
}
Emitter.fOP3 = m;
Emitter.fOP4 = d;
}
else{
MessageBox( 0 , "Система частиц не была первично инициализирована." ,
"Ошибка" , 0 );
exit( 0 );
}
}
else{
MessageBox( 0 , "Система частиц объявлена как постоянная во времени." ,
"Ошибка" , 0 );
exit( 0 );
}
}
Осталось только пристегнуть текстуру:
void	cParticleSystem::CreateVisualPart( char *tex_name ){
TextureCursor = TextureManager.GetTextureCursor( tex_name );
}

Эмиттер.

к началу статьи
Теперь поговорим об эмиттере. Эта структура у нас будет отвечать за “рождение” новых партиклов, поэтому класс должен содержать всю необходимую информацию о том какие частицы нужно создавать:
class	cEmitter{
public:
DWORD EMITTER_TYPE;
//позиция партикла
float fOP1;
//размер партикла
cVector3 OP1;
//разброс скорости
float fOP2;
//сама скорость
cVector3 OP2;

//время жизни
float fOP3;
//разброс времени жизни
float fOP4;
cEmitter( void ){EMITTER_TYPE = EMM_SPRAY;OP1 = NULL;OP2=cVector3(0,1,0);}
void Create( DWORD , cVector3 );

cD3DParticleVertex GetVertex( int , cD3DParticleVertex& );
cVector3 GetVelosity( void );
float GetLifeTime( void );
};
Я специально не стал давать полям этого класса более осмысленные названия, потому что планирую использовать эмиттер для других систем частиц, а запихивать все возможные поля в один класс мне неохота… хотя, возможно, пользоваться будет не особо удобно… ладно, там видно будет. При перерождении партикла нужно поместить его в исходное положение (хочу напомнить, что в нашем случае партикл это два треугольника по три вершины в каждом, пока что никаких индексов). Эта операция осуществляется с помощью следующей функции:
//передаем в качестве параметров номер вершины в партикле 
//и саму вершину
cD3DParticleVertex cEmitter::GetVertex( int i , cD3DParticleVertex &PV ){
if( EMITTER_TYPE & EMM_SPRAY ){
switch( i ){
case( 0 ):
PV.x = OP1.x-fOP1 / 2;
PV.y = OP1.y-fOP1 / 2;
PV.z = 0;

return( PV );
break;
case( 1 ):
PV.x = OP1.x-fOP1 / 2;
PV.y = OP1.y+fOP1 / 2;
PV.z = 0;

return( PV );
break;
case( 2 ):
PV.x = OP1.x+fOP1 / 2;
PV.y = OP1.y+fOP1 / 2;
PV.z = 0;

return( PV );
break;
case( 3 ):
PV.x = OP1.x-fOP1 / 2;
PV.y = OP1.y-fOP1 / 2;
PV.z = 0;

return( PV );
break;
case( 4 ):
PV.x = OP1.x+fOP1 / 2;
PV.y = OP1.y+fOP1 / 2;
PV.z = 0;

return( PV );
break;
case( 5 ):
PV.x = OP1.x+fOP1 / 2;
PV.y = OP1.y-fOP1 / 2;
PV.z = 0;

return( PV );
break;
}
}
}
Потом определяем, сколько партиклу “на роду написано”:
float	cEmitter::GetLifeTime( void ){
return( fOP3 + fOP4 * frand() - fOP4 / 2 );
}
И узнаем скорость партикла:
cVector3 cEmitter::GetVelosity( void ){
cVector3 RET( OP2 );
RET.x += fOP2 * frand() - fOP2/2;
RET.y += fOP2 * frand() - fOP2/2;
RET.z += fOP2 * frand() - fOP2/2;

return( RET );
}
Теперь можно запускать наш агрегат:
void	cParticleSystem::RunSystem( float dt ){
if( VertexBuffer ){
for( int i( 0 ) ; i < VertexBuffer->Vertexes.Cursor ; i++ ){
if( _EFFECTS & EFF_GRAVITY ){
//расчет положения с учетом гравитации
VertexBuffer->Vertexes[ i ]+=dt * ( *VelosityArray )[ i / 6 ] +
cVector3( 0 , 0.5 * dt * dt * CNST_GRAVITY , 0 );
}
else{
//расчет положения без учета гравитации
VertexBuffer->Vertexes[ i ]+=dt * ( * VelosityArray )[ i / 6 ];
}
}
for( int i( 0 ) ; i < VertexBuffer->Vertexes.Cursor / 6 ; i++ ){
if( _EFFECTS & EFF_GRAVITY ){
//расчет скорости с учетом гравитации
( *VelosityArray )[ i ]+=cVector3( 0 , dt * CNST_GRAVITY , 0 );
}
if( _EFFECTS & EFF_AGING ){
if( LifeTime ){
//”старение” частицы
( * LifeTime )[ i ] -= dt;
//проверяем, “умерла” ли частица
if( ( * LifeTime )[ i ] <= 0 ){
Emitter.GetVertex(0,(*VertexBuffer)[6*i+0]);
Emitter.GetVertex(1,(*VertexBuffer)[6*i+1]);
Emitter.GetVertex(2,(*VertexBuffer)[6*i+2]);
Emitter.GetVertex(3,(*VertexBuffer)[6*i+3]);
Emitter.GetVertex(4,(*VertexBuffer)[6*i+4]);
Emitter.GetVertex(5,(*VertexBuffer)[6*i+5]);

( * VelosityArray )[ i ]=Emitter.GetVelosity();

( * LifeTime )[ i ] = Emitter.GetLifeTime();
}
}
else{
MessageBox( 0 ,
"PS создана с \"нестареющими\" партиклами." ,
"Ошибка" , 0 );
exit( 0 );
}
}
}
}
else{
MessageBox( 0 , "Система частиц не была инициализирована!" , "Ошибка" , 0 );
exit( 0 );
}
}
Ну что ж, теперь дело за малым – отрендерить партиклы (кроме стандартного альфа блендинга и уже известных Вам функций, здесь ничего нет):
void	cParticleSystem::Render( LPDIRECT3DDEVICE8 D3DDevice ){
TextureManager[ TextureCursor ].SetTexture( D3DDevice , 0 );

D3DDevice->SetRenderState( D3DRS_ALPHABLENDENABLE , true );
D3DDevice->SetRenderState( D3DRS_SRCBLEND , D3DBLEND_SRCALPHA );
D3DDevice->SetRenderState( D3DRS_DESTBLEND , D3DBLEND_INVSRCALPHA );
D3DDevice->SetRenderState( D3DRS_ZENABLE , false );

VertexBuffer->Render( D3DDevice , FVF_D3DPARTICLE_VERTEX );

D3DDevice->SetRenderState( D3DRS_ALPHABLENDENABLE , false );
D3DDevice->SetRenderState( D3DRS_ZENABLE , true );
D3DDevice->SetRenderState( D3DRS_STENCILENABLE , false );

TextureManager[ TextureCursor ].ResetTexture( D3DDevice , 0 );
}
Вот и все на сегодня. Не так уж и мало, да? В ближайшее время я ещё планирую вернуться к системам частиц, сделать нашу конструкцию ещё более мощной. Поэтому точку в этой статье я не ставлю…

к началу статьи

PS связаться со мной можно по почте dodonov_a_a (__AT) inbox.ru


Смежные вопросы:
Урок 1. Инициализация. Вершинные буфферы и камера.
Урок 2. Проигрывание медиа-файлов.
Урок 3. Автоматизация загрузки и управления аудио и видео ресурсами.
Урок 4. Текстурирование. Текстурный менеджер.
Урок 5. Шрифты в Direct3D.
Урок 6. Элементы управления.
Урок 7. Вершинные шейдеры версии 1.0.
Исходные коды.
© 2004-2005 Savardge.ru
Hosted by uCoz