Введение в программирование трехмерных игр с DX9

       

Компоненты системы частиц


Системой частиц называется набор частиц и код, отвечающий за управление этими частицами и их отображение. Система частиц отслеживает глобальные свойства, влияющие на все частицы, такие как размер частиц, место из которого появляются частицы, накладываемая на частицы текстура ит.д. С точки зрения функциональности система частиц отвечает за обновление, отображение, уничнтожение и создание частиц.

Хотя различные системы частиц ведут себя по-разному, мы можем выполнить обобщение и выделить некоторые базовые свойства, которые используются всеми системами частиц. Мы поместим эти общие свойства в абстрактный базовый класс PSystem, который будет родителем для классов конкретных систем частиц. Давайте теперь взглянем на класс PSystem:

class PSystem { public: PSystem(); virtual ~PSystem();

virtual bool init(IDirect3DDevice9* device, char* texFileName); virtual void reset(); virtual void resetParticle(Attribute* attribute) = 0; virtual void addParticle(); virtual void update(float timeDelta) = 0;

virtual void preRender(); virtual void render(); virtual void postRender();

bool isEmpty(); bool isDead(); protected: virtual void removeDeadParticles();

protected: IDirect3DDevice9* _device; D3DXVECTOR3 _origin; d3d::BoundingBox _boundingBox; float _emitRate; float _size; IDirect3DTexture9* _tex; IDirect3DVertexBuffer9* _vb; std::list<Attribute> _particles; int _maxParticles;

DWORD _vbSize; DWORD _vbOffset; DWORD _vbBatchSize; };

Начнем с членов данных:

_origin — Базовая точка системы. Это то место, откуда появляются частицы системы.

_boundingBox — Ограничивающий параллелепипед используется в тех системах частиц, где надо ограничить объем пространства в котором могут находиться частицы. Например, мы хотим, чтобы снег падал только в пространстве, окружающем высокую вершину горы; для этого следует создать ограничивающий параллелепипед, охватывающий желаемый объем и частицы, вышедшие за границы этого объема будут уничтожаться.

_emitRate — Частота добавления новых частиц к системе. Обычно измеряется в частицах в секунду.

_size — Размер всех частиц системы.

_particles — Список, содержащий атрибуты частиц системы. Мы работаем с этим списком при создании, уничножении и обновлении частиц. Когда мы готовы к рисованию частиц, мы копируем часть узлов списка в буфер вершин и рисуем частицы. Затем мы копируем следующий блок и рисуем частицы. Эти действия повторяются до тех пор, пока не будут нарисованы все частицы. Это крайне упрощенное описание; подробно процесс рисования будет рассмотрен в разделе 14.2.1.

_maxParticles — Максимальное количество частиц, которое может быть в системе одновременно. Если, к примеру, частицы создаются быстрее чем удаляются, может получиться, что у вас будет огромное количество частиц, что приведет к неработоспособности программы. Данный член позволяет избежать такого развития событий.

_vbSize — Количество частиц, которое может быть помещено в буфер вершин одновременно. Это значение не зависит от количества частиц в конкретной системе частиц.



ПРИМЕЧАНИЕ

Члены данных _vbOffset и _vbBatchSize используются при визуализации системы частиц. Мы отложим их обсуждение до раздела 14.2.1.
Методы класса:

PSystem/~PSystem — Конструктор инициализирует значения по умолчанию, а деструктор освобождает интерфейсы устройства (буфер вершин, текстуры).

init — Этот метод выполняет зависящую от устройства Direct3D инициализацию, такую как создание буфера вершин для хранения точечных спрайтов и создание текстуры. При создании буфера вершин указываются несколько флагов о которых мы говорили, но до настоящего момента еще ни разу не использовали:

hr = device->CreateVertexBuffer( _vbSize * sizeof(Particle), D3DUSAGE_DYNAMIC | D3DUSAGE_POINTS | D3DUSAGE_WRITEONLY, Particle::FVF, D3DPOOL_DEFAULT, &_vb, 0);

Обратите внимание, что мы используем динамический буфер вершин. Это вызвано тем, что мы должны в каждом кадре обновлять данные частиц, что требует доступа к памяти буфера вершин.


Вспомните, что доступ к статическому буферу вершин осуществляется очень медленно; поэтому мы используем динамический буфер вершин.

Взгляните на используемый флаг D3DUSAGE_POINTS, который сообщает, что в буфере вершин будут храниться точечные спрайты.

Обратите внимание, что размер буфера вершин задан переменной _vbSize и не имеет ничего общего с количеством частиц в системе. То есть, значение _vbSize очень редко равно количеству частиц в системе. Это вызвано тем, что мы визуализируем систему частиц по частям, а не всю сразу. Процесс визуализации мы исследуем в разделе 14.2.1.

Мы используем пул памяти по умолчанию, а не обычный управляемый пул памяти потому что динамический буфер вершин не может быть размещен в управляемом пуле памяти.

reset — Этот метод сбрасывает значения атрибутов у каждой частицы системы:

void PSystem::reset() { std::list<Attribute>::iterator i; for(i = _particles.begin(); i != _particles.end(); i++) { resetParticle(&(*i)); } }

resetParticle — Этот метод сбрасывает значения атрибутов одной частицы. То, как именно должен выполняться сброс атрибутов, зависит от параметров конкретной системы частиц. Следовательно, мы делаем этот метод абстрактным и он должен быть реализован в производном классе.

addParticle — Этот метод добавляет частицу к системе. Он использует метод resetParticle для инициализации частицы перед ее добавлением к списку:

void PSystem::addParticle() { Attribute attribute;

resetParticle(&attribute);

_particles.push_back(attribute); }

update — Этот метод обновляет данные всех частиц системы. Поскольку реализация такого метода зависит от спецификаций конкретной системы частиц, мы объявляем этот метод абстрактным и должны реализовать его в производном классе.

render — Данный метод отображает все частицы системы. Его реализация достаточно сложна и мы отложим ее обсуждение до раздела 14.2.1.

preRender — Применяется для установки начальных режимов визуализации, которые должны быть заданы перед визуализацией.


Поскольку они могут меняться в зависимости от конкретной системы частиц, мы делаем этот метод виртуальным. Предлагаемая по умолчанию реализация выглядит так:

void PSystem::preRender() { _device->SetRenderState(D3DRS_LIGHTING, false); _device->SetRenderState(D3DRS_POINTSPRITEENABLE, true); _device->SetRenderState(D3DRS_POINTSCALEENABLE, true); _device->SetRenderState(D3DRS_POINTSIZE, d3d::FtoDw(_size)); _device->SetRenderState(D3DRS_POINTSIZE_MIN, d3d::FtoDw(0.0f));

// Управление изменением размера частицы // в зависимости от расстояния до нее _device->SetRenderState(D3DRS_POINTSCALE_A, d3d::FtoDw(0.0f)); _device->SetRenderState(D3DRS_POINTSCALE_B, d3d::FtoDw(0.0f)); _device->SetRenderState(D3DRS_POINTSCALE_C, d3d::FtoDw(1.0f));

// Для текстуры используется альфа-смешивание _device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); _device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); _device->SetRenderState(D3DRS_ALPHABLENDENABLE, true); _device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); _device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); }

Обратите внимание, что мы разрешили альфа-смешивание, чтобы альфа-канал установленной в данный момент текстуры определял прозрачность ее участков. Прозрачность используется для множества эффектов; один из них — реализация частиц непрямоугольной формы. Предположим, вам нужны круглые, похожие на хлопья снега, частицы. В этом случае мы используем текстуру в виде белого квадрата с альфа-каналом в виде черного квадрата с белым кругом. В результате на экране будет отображен похожий на снежинку белый круг, а не квадратная текстура.

postRender — Используется для восстановления режимов визуализации, которые изменила данная система частиц. Поскольку режимы меняются в зависимости от конкретной системы частиц, этот метод виртуальный. По умолчанию используется следующая реализация:

void PSystem::postRender() { _device->SetRenderState(D3DRS_LIGHTING, true); _device->SetRenderState(D3DRS_POINTSPRITEENABLE, false); _device->SetRenderState(D3DRS_POINTSCALEENABLE, false); _device->SetRenderState(D3DRS_ALPHABLENDENABLE, false); }


isEmpty — Метод возвращает true, если в системе нет ни одной частицы и false в ином случае.

isDead — Метод возвращает true если все частицы в системе мертвы и false, если хотя бы одна частица жива. Обратите внимание что если все частицы мертвы, это не значит, что система частиц пуста. В пустой системе нет ни мертвых ни живых частиц. Если система мертвая, это значит, что в ней есть частицы, но все они помечены как мертвые.

removeDeadParticles — Метод перебирает элементы списка атрибутов _particle и удаляет из него все частицы, которые отмечены как мертвые:

void PSystem::removeDeadParticles() { std::list::iterator i; i = _particles.begin(); while( i != _particles.end() ) { if( i->_isAlive == false ) { // стирание возвращает номер следующего элемента, // поэтому самостоятельно увеличивать счетчик не надо i = _particles.erase(i); } else { i++; // следующий элемент списка } } }

ПРИМЕЧАНИЕ

Этот метод обычно вызывается из метода обновления данных частиц производного класса для удаления тех частиц, которые были уничтожены (помечены как мертвые). Однако для некоторых систем частиц может оказаться предпочтительнее повторно использовать мертвые частицы, а не удалять их. В этом случае вместо добавления в список новых частиц и удаления из него старых в моменты их рождения и смерти, мы просто сбрасываем данные мертвой частицы и воскрешаем ее. Этот подход будет продемонстрирован в реализации снегопада, рассмариваемой в разделе 14.3.



Содержание раздела