|
Система
частиц.
Содержание статьи:
Введение.
Основной
класс системы частиц.
Класс
"Система частиц".
Эмиттер.
Вступление.
к началу статьи
Доброго времени суток всем, кто сейчас читает эту статью. Как Вы,
наверное, уже догадались, речь пойдет о системе частиц. Важность этого
элемента игрового движка сложно переоценить. С его помощью делаются
такие эффекты как снег, дождь, огонь, лазеры, дым, взрывы и т.д. Многие
эффекты без них не реализуемы (в реальном времени). Поэтому ни один
игровой моторчик не обходится без партиклов. Поэтому нам с вами
жизненно необходимо разобраться с этой технологией и внедрить её в
движок. Приступим?
Основной
класс системы частиц.
к началу статьи
Порывшись в Интернете, Вы, наверное, могли обнаружить, что, говоря
“система частиц”, подразумевают три понятия:
эмиттер, аттрактор и собственно сами партиклы. Как правило, эти термины
реализуются в виде трех классов и означают следующее: Аттрактор
– моделирует различные силовые поля, воздействующие на
партиклы (в этой статье не рассматриваются… может в другой
раз).
Эмиттер – управляет разбросом и
“рождением” частиц (о процессе рождения поговорим
позже)
“сами партиклы” – подразумевается
совокупность данных (вертексы, скорости, величины разброса частиц,
время жизни и т.д.) представленная в удобном виде.
Создадим соответствующие классы:
Представление вершины партикла… ну тут вроде никаких тайн
– данные + конструкторы. |
#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.
Исходные коды. |
|
|