netlib.narod.ru< Назад | Оглавление | Далее >

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

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

Хотя различные системы частиц ведут себя по-разному, мы можем выполнить обобщение и выделить некоторые базовые свойства, которые используются всеми системами частиц. Мы поместим эти общие свойства в абстрактный базовый класс 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;
};

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

 

ПРИМЕЧАНИЕ
Члены данных _vbOffset и _vbBatchSize используются при визуализации системы частиц. Мы отложим их обсуждение до раздела 14.2.1.

Методы класса:

14.2.1. Рисование системы частиц

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

  1. Создать буфер вершин, размер которого достаточен для хранения всех находящихся в кадре частиц.

    В каждом кадре:

    1. Обновить данные всех частиц.

    2. Скопировать все живые частицы в буфер вершин.

    3. Нарисовать содержимое буфера вершин.

Этот подход работает, но неэффективен. Во-первых, для того чтобы хранить все частицы системы, буфер вершин должен быть очень большим. Что еще более важно, в момент копирования данных частиц из списка в буфер вершин (этап В) видеокарта будет простаивать. Предположим, что в нашей системе 10 000 частиц; для начала нам понадобится буфер вершин, в котором могут храниться данные 10 000 частиц, а он займет очень много памяти. Помимо этого, видеокарта будет ждать и ничего не делать пока мы не скопируем данные всех 10 000 частиц из списка в буфер вершин и не вызовем метод DrawPrimitive. Этот сценарий хороший пример того, как центральный процессор и видеокарта не работают сообща.

Гораздо лучший подход (который используется в примере Point Sprite из SDK) заключается в следующем:

 

ПРИМЕЧАНИЕ
Это очень упрощенное описание, но оно иллюстрирует идею. Предполагается, что у нас всегда есть 500 частиц, чтобы заполнить целый сегмент, хотя в действительности это условие не выполняется потому что мы постоянно создаем и уничтожаем частицы и их количество меняется от кадра к кадру. Предположим, что у нас осталось только 200 частиц для копирования и визуализации в текущем кадре. Поскольку 200 частиц недостаточно для заполнения буфера вершин, в коде мы обрабатываем такой сценарий как особый случай. Данный сценарий может иметь место только в том случае, когда в текущем кадре заполняется последний сегмент. Если сегмент не последний, это значит что у нас есть по крайней мере 500 частиц для перехода к следующему сегменту.

 

ПРИМЕЧАНИЕ
Вспомните, что наш буфер вершин динамический, и поэтому мы можем пользоваться преимуществами, предоставляемыми флагами динамической блокировки D3DLOCK_NOOVERWRITE и D3DLOCK_DISCARD. Эти флаги позволяют блокировать часть буфера вершин, которая не визуализируется, в то время как остальные части буфера вершин будут продолжать визуализироваться. Например, предположим, что мы визуализируем сегмент 0; используя флаг D3DLOCK_NOOVERWRITE мы можем заблокировать и заполнить сегмент 1 в то время, когда визуализируется сегмент 0. Это позволяет предотвратить простои визуализации, которые возникали бы в ином случае.

Данный подход более эффективен. Во-первых, мы сокращаем размер необходимого нам буфера вершин. Во-вторых, теперь центральный процессор и видеокарта работают в унисон; то есть мы копируем небольшую партию частиц в буфер вершин (работа центрального процессора), а затем мы визуализируем эту партию частиц (работа видеокарты). Затем мы копируем в буфер вершин следующую партию частиц и рисуем ее. Это продолжается до тех пор, пока не будут визуализированы все частицы. Как видите, видеокарта больше не простаивает, ожидая пока не будет заполнен весь буфер вершин.

Теперь мы обратим наше внимание на реализацию этой схемы визуализации. Чтобы облегчить визуализацию системы частиц с помощью данной схемы мы будем использовать следующие члены данных класса PSystem:

Теперь мы представим вам код метода визуализации:

void PSystem::render()
{
     if(!_particles.empty())
     {
          // Установка режимов визуализации
          preRender();
          _device->SetTexture(0, _tex);
          _device->SetFVF(Particle::FVF);
          _device->SetStreamSource(0, _vb, 0, sizeof(Particle));

          // Если мы достигли конца буфера вершин,
          // возвращаемся к его началу
          if(_vbOffset >= _vbSize)
               _vbOffset = 0;

          Particle* v = 0;

          _vb->Lock(
                _vbOffset * sizeof(Particle),
                _vbBatchSize * sizeof(Particle),
                (void**)&v,
                _vbOffset ? D3DLOCK_NOOVERWRITE : D3DLOCK_DISCARD);

          DWORD numParticlesInBatch = 0;

          //
          // Пока все частицы не будут визуализированы
          //
          std::list<Attribute>::iterator i;
          for(i = _particles.begin(); i != _particles.end(); i++)
          {
               if(i->_isAlive)
               {
                    //
                    // Копируем партию живых частиц в
                    // очередной сегмент буфера вершин
                    //
                    v->_position = i->_position;
                    v->_color = (D3DCOLOR)i->_color;
                    v++; // следующий элемент;

                    numParticlesInBatch++; // увеличиваем счетчик партий

                    // партия полная?
                    if(numParticlesInBatch == _vbBatchSize)
                    {
                         //
                         // Рисуем последнюю партию частиц, которая
                         // была скопирована в буфер вершин.
                         //
                         _vb->Unlock();
                         _device->DrawPrimitive(
                                      D3DPT_POINTLIST,
                                      _vbOffset,
                                      _vbBatchSize);
                         //
                         // Пока партия рисуется, начинаем заполнять
                         // следующую партию частиц.
                         //
                         // Увеличиваем смещение к началу следующей партии

                         _vbOffset += _vbBatchSize;

                         // Проверяем не вышли ли мы за пределы буфера вершин.
                         // Если да, то возвращаемся к началу буфера.
                         if(_vbOffset >= _vbSize)
                              _vbOffset = 0;

                         _vb->Lock(
                               _vbOffset * sizeof(Particle),
                               _vbBatchSize * sizeof(Particle),
                               (void**)&v,
                               _vbOffset ? D3DLOCK_NOOVERWRITE :
                                                     D3DLOCK_DISCARD);

                         numParticlesInBatch = 0; // обнуляем количество частиц в партии
                    }//конец инструкции if
               }//конец инструкции if
          }//конец инструкции for

          _vb->Unlock();

          // Возможно, ПОСЛЕДНЯЯ партия частиц начала заполняться,
          // но не была визуализирована, потому что условие
          // (numParticlesInBatch == _vbBatchSize) не было выполнено.
          // Сейчас мы нарисуем эту последнюю частично заполненную партию частиц
          if( numParticlesInBatch )
          {
               _device->DrawPrimitive(
                              D3DPT_POINTLIST,
                              _vbOffset,
                              numParticlesInBatch);
          }

          // Следующий блок
          _vbOffset += _vbBatchSize;

          postRender();
     }//конец инструкции if
}// конец метода render()

14.2.2. Хаотичность

В системах частиц есть своего рода хаотичность. Например, моделируя снегопад мы не хотим, чтобы все снежинки падали абсолютно одинаково. Нам нужно чтобы они падали похожим образом, а не абсолютно одинаково. Чтобы облегчить реализацию хаотичности, необходимую для систем частиц, мы добавляем в файлы d3dUtility.h/cpp две функции.

Первая функция возвращает случайное число с плавающей точкой, находящееся в диапазоне [lowBound, highBound]:

float d3d::GetRandomFloat(float lowBound, float highBound)
{
     if(lowBound >= highBound) // неправильные параметры
          return lowBound;

     // Получаем случайное число в диапазоне [0, 1]
     float f = (rand() % 10000) * 0.0001f;

     // Возвращаем число из диапазона [lowBound, highBound]
     return (f * (highBound - lowBound)) + lowBound;
}

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

void d3d::GetRandomVector(
                 D3DXVECTOR3* out,
                 D3DXVECTOR3* min,
                 D3DXVECTOR3* max)
{
     out->x = GetRandomFloat(min->x, max->x);
     out->y = GetRandomFloat(min->y, max->y);
     out->z = GetRandomFloat(min->z, max->z);
}

 

ПРИМЕЧАНИЕ
Не забывайте об инициализации генератора случайных чисел с помощью функции srand().

netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz