Войти
Станислав КычановСтатьи

Реализация системы частиц для shader model 1 (2 стр)

Подготовка данных

Перейдём к реализации. Как было замечено ранее, нам известно, что в любой момент частиц испускаемых эмиттером будет ровно numParticles, поэтому мы создаём буферы с геометрией только для данного количества (здесь и далее будет приводится engine-specific code):

// Create vertex buffer
memset(&bdesc, 0, sizeof(GPUBUFFER_DESC));
bdesc.Type    = GPURESOURCE_VERTEXBUFFER;
bdesc.Length  = 4*sizeof(PARTICLEVERTEX)*numParticles;
bdesc.Pool    = GPUPOOL_MANAGED;
bdesc.Usage   = 0;
bdesc.Format  = GPUFORMAT_VERTEXDATA;
result = lpRenderDevice->CreateBuffer(&bdesc, (LPBASEBUFFER*) &lpVB);
if (FAILED(result))
{
  _logerror_("Create vertex buffer failed", __FILE__, __LINE__);
  return HDERR_INTERNAL;
}

Здесь мы создали буфер вершин, вмещающий по 4 вершины для каждой частицы (рисовать будем квадами). Рассмотрим содержание структуры PARTICLEVERTEX:

typedef struct PARTICLEVERTEX
{
  PARTICLEVERTEX(float3 vel, dword ID)
    : velocity(vel),
      vertexID(ID)
  {
  }
public:
  float3  velocity;
  dword    vertexID;
} PARTICLEVERTEX;

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

Заполняем буфер данными:

// Fill vertex data
lpVB->Lock(0, 0, (byte**)&pVData, 0);
if (pVData)
{
  for (i=0; i<numParticles; ++i)
  {
    float3 velocity;
    velocity.x = float(rand() % 500) / 25.0f - 10.0f;
    velocity.y = float(rand() % 500) / 25.0f - 10.0f;
    velocity.z = float(rand() % 500) / 25.0f - 10.0f;
    velocity *= 3.0f;

    *pVData++ = PARTICLEVERTEX(velocity, 0);
    *pVData++ = PARTICLEVERTEX(velocity, 1);
    *pVData++ = PARTICLEVERTEX(velocity, 2);
    *pVData++ = PARTICLEVERTEX(velocity, 3);
  }
}
lpVB->Unlock();

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

// Create index buffer
bdesc.Length  = 6*sizeof(word)*numParticles;
bdesc.Type    = GPURESOURCE_INDEXBUFFER;
bdesc.Pool    = GPUPOOL_MANAGED;
bdesc.Usage   = 0;
bdesc.Format  = GPUFORMAT_INDEX16;
result = lpRenderDevice->CreateBuffer(&bdesc, (LPBASEBUFFER*) &lpIB);
if (FAILED(result))
{
  _logerror_("Create index buffer failed", __FILE__, __LINE__);
  return HDERR_INTERNAL;
}

Без комментариев, заполняем данными:

// Fill index data
lpIB->Lock(0, 0, (byte**)&pIData, 0);
if (pIData)
{
  for (i=0; i<numParticles; ++i)
  {
    *pIData++ = i*4 + 2;
    *pIData++ = i*4 + 1;
    *pIData++ = i*4 + 0;

    *pIData++ = i*4 + 2;
    *pIData++ = i*4 + 3;
    *pIData++ = i*4 + 1;
  }
}
lpIB->Unlock();

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

Изображение удалено

Здесь pN – данные N-ой частицы в вершинном буфере (4 вершины), а tN – время, которое частица отжила. Это время мы будем хранить в отдельном вершинном буфере и подключать ко второму потоку (stream). Соответствующая декларация вершин выглядит следующим образом:

// Create vertex declaration
result = lpRenderDevice->CreateVertexDeclaration(&lpVertDecl);
if (FAILED(result))
{
  _logerror_("Create vertex declaration failed", __FILE__, __LINE__);
  return HDERR_INTERNAL;
}

lpVertDecl->StartDeclaration();
lpVertDecl->AddElement(0, 0, GPUDECLTYPE_FLOAT3, GPUDECLMETHOD_DEFAULT, GPUDECLSEM_POSITION, 0);
lpVertDecl->AddElement(0, 12, GPUDECLTYPE_UBYTE4, GPUDECLMETHOD_DEFAULT, GPUDECLSEM_BLENDINDICES, 0);
lpVertDecl->AddElement(1, 0, GPUDECLTYPE_FLOAT1, GPUDECLMETHOD_DEFAULT, GPUDECLSEM_BLENDWEIGHT, 0);
lpVertDecl->EndDeclaration();

Как видно из кода, основные данные (скорость и индекс вершины) будут приходить из 0-го потока, а время из 1-го. Как это анимировать? Посмотрите на второй рисунок и всё станет понятно:

Изображение удалено

Задать динамику можно просто смещая буферы относительно друг друга. Время каждой частицы изменилось на dt = t(i+1) – t(i). Это конечно хорошо, но размер буфера ограничен и мы не можем записать туда всю последовательность lifeTime с некоторым малым шагом, к тому же хотелось бы непрерывного движения, а не дискретного. Решение состоит в том, чтобы сделать интервал dt равным respawnPeriod, т.е. времени, по прошествии которого появляется новая частица, а время внутри интервала задавать шейдерной константой, общей для всех вершин. Например, пусть respawnPeriod будет равен 2, тогда частицам будут соответствовать следующие значения:
p0 – 0
p1 – 2
p2 – 4
p3 – 6

pN – 2*N
Время жизни каждой частицы будет состоять из суммы значения времени, полученного из буфера, и времени внутри интервала респауна.
Как можно заметить, достоинство метода – не нужно отслеживать мёртвые частицы, время жизни которых превысило лимит. Как только наступит новый респаун, время последней частицы (самой старой) станет равной t0, т.е. частица переродится.
Вот код для создания и заполнения буфера времени:

// Create particles time buffer
bdesc.Type    = GPURESOURCE_VERTEXBUFFER;
bdesc.Length  = 4*sizeof(float)*numParticles;
bdesc.Pool    = GPUPOOL_MANAGED;
bdesc.Usage   = 0;
bdesc.Format  = GPUFORMAT_VERTEXDATA;
result = lpRenderDevice->CreateBuffer(&bdesc, (LPBASEBUFFER*) &lpAIB);
if (FAILED(result))
{
  _logerror_("Create attribute-index buffer failed", __FILE__, __LINE__);
  return HDERR_INTERNAL;
}

// Fill data
lpAIB->Lock(0, 0, (byte**)&pAIData, 0);
float time = 0.0f;
for (i=0; i<numParticles; ++i)
{
  *pAIData++ = time;
  *pAIData++ = time;
  *pAIData++ = time;
  *pAIData++ = time;
  time += respawnPeriod;
}
lpAIB->Unlock();
Страницы: 1 2 3 Следующая »

28 сентября 2006 (Обновление: 17 ноя. 2006)