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

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

Редеринг частиц

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

if (transformChanged)
{
  float4x4 rotMatrix, trnlMatrix;
  gmuQuaternionToMatrix4x4(rotMatrix, rotation);
  gmuBuildTranslation4x4(trnlMatrix, translation.x, translation.y, translation.z);
  mul4x4(worldTransform, trnlMatrix, rotMatrix);
  transformChanged = HD_FALSE;
}

Первым делом, если изменилось положение или ориентация эмиттера нужно пересоздать мировую матрицу трансформации.

float4x4  matView, matViewT, matVtW, matVtWV, matProj, matVtWVP;
lpRenderDevice->GetTransform(GPUTS_PROJECTION, &matProj);
lpRenderDevice->GetTransform(GPUTS_VIEW, &matView);

gmuTranspose4x4(matViewT, matView);
matViewT._14 = matViewT._24 = matViewT._34 = 0.0f;
mul4x4(matVtW, worldTransform, matViewT);
mul4x4(matVtWV, matView, matVtW);
mul4x4(matVtWVP, matProj, matVtWV);

Далее вычисляется матрица, совмещающая трансформацию эмиттера, видовое преобразования для декалей и проекционное преобразование.

lpEffector->SetSuitableTechnique(0);

Устанавливаем первую подходящую технику в материале.

currentTime += Delta;
if (currentTime > respawnPeriod)
{
  currentTime -= respawnPeriod;
  ++bufferShift;
  if (bufferShift >= numParticles)
    bufferShift = 0;
}

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

texCoordsIndex = lpEffector->GetVertexShaderConstantIndex(0, "particleUV");
quadPointsIndex = lpEffector->GetVertexShaderConstantIndex(0,"particleQuad");

Получаем индексы вершинных регистров-массивов, для сохранения текстурных координат квада частицы и локальных координат вершин квада.

lpRenderDevice->SetRenderState(GPUSTATE_SRCBLEND, GPUBLEND_ONE);
lpRenderDevice->SetRenderState(GPUSTATE_DESTBLEND, GPUBLEND_ONE);
lpRenderDevice->SetRenderState(GPUSTATE_ALPHABLENDENABLE, HD_TRUE);
lpRenderDevice->SetRenderState(GPUSTATE_BLENDOP, GPUBLENDOP_ADD);
lpRenderDevice->SetRenderState(GPUSTATE_ALPHATESTENABLE, HD_TRUE);
lpRenderDevice->SetRenderState(GPUSTATE_ALPHAFUNC, GPUCMPFUNC_GREATER);
lpRenderDevice->SetRenderState(GPUSTATE_ALPHAREF, 0x0000001f);
lpRenderDevice->SetRenderState(GPUSTATE_ZWRITEENABLE,   HD_FALSE);

Без комментариев.
 

lpEffector->SetVSConstant(0,"matWorldDecalProj", matVtWVP);
lpEffector->SetVSConstant(0,"timeValues", float2(currentTime, 1.0/particleLifeTime));
lpEffector->SetVSConstant(0,"totalForce", float3(0.0f, -1.8f, 0.0f));
lpEffector->SetVSConstant(0,"particleInvMass", float4(1.0f, 1.0f, 1.0f, 1.0f));
lpEffector->SetVSConstant(0,"particleSize", float4(1.0f, 1.0f, 3.0f, 3.0f));
lpEffector->SetVSConstant(0,"particleStartColor", float4(1.0f, 1.0f, 0.0f, 1.0f));
lpEffector->SetVSConstant(0,"particleEndColor", float4(1.0f, 0.3f, 0.0f, 0.0f));
lpEffector->SetVSConstant(0,texCoordsIndex++, float2(0.0f, 0.0f));
lpEffector->SetVSConstant(0,texCoordsIndex++, float2(0.25f, 0.0f));
lpEffector->SetVSConstant(0,texCoordsIndex++, float2(0.0f, 0.25f));
lpEffector->SetVSConstant(0,texCoordsIndex++, float2(0.25f, 0.25f));
lpEffector->SetVSConstant(0,quadPointsIndex++, float2(-1.0f, 1.0f));
lpEffector->SetVSConstant(0,quadPointsIndex++, float2(1.0f, 1.0f));
lpEffector->SetVSConstant(0,quadPointsIndex++, float2(-1.0f, -1.0f));
lpEffector->SetVSConstant(0,quadPointsIndex++, float2(1.0f, -1.0f));

Устанавливаем константы для вершинного шейдера. Текстурные координаты и другие параметры поставлены от балды, здесь конечно должно быть нечто осмысленное.

for (uint32 i=0; i<lpEffector->GetPassCount(); ++i)
{
  uint32 from, count;

  lpRenderDevice->SetEffector(i, lpEffector);    
  lpRenderDevice->SetIndexSource(lpIB);
  lpRenderDevice->SetVertexDeclaration(lpVertDecl);

  from  = 0;
  count  = numParticles - bufferShift;
  if (count > 0)
  {
    lpRenderDevice->SetVertexSource(0, GPUPRIMITIVE_TRIANGLELIST, 0, lpVB, sizeof(PARTICLEVERTEX));
    lpRenderDevice->SetVertexSource(1, GPUPRIMITIVE_TRIANGLELIST, bufferShift*sizeof(float)*4, lpAIB, sizeof(float));
    lpRenderDevice->DrawIndexedPrimitive(0, 0, count*4, 0, count*2);
  }

Рисуем первую часть частиц от 0-й до самой старой.

  from  = numParticles - bufferShift;
  count  = bufferShift;
  if (count > 0)
  {
    lpRenderDevice->SetVertexSource(0,GPUPRIMITIVE_TRIANGLELIST, from*4*sizeof(PARTICLEVERTEX), lpVB, sizeof(PARTICLEVERTEX));
    lpRenderDevice->SetVertexSource(1, GPUPRIMITIVE_TRIANGLELIST, 0, lpAIB, sizeof(float));
    lpRenderDevice->DrawIndexedPrimitive(0, 0, count*4, 0, count*2);
  }
}

Рисуем оставшееся вершины, от самой молодой до 0-й.

lpRenderDevice->SetRenderState(GPUSTATE_ALPHABLENDENABLE,  HD_FALSE);
lpRenderDevice->SetRenderState(GPUSTATE_ALPHATESTENABLE,  HD_FALSE);
lpRenderDevice->SetRenderState(GPUSTATE_ZWRITEENABLE,    HD_TRUE);

Тут всё понятно.

Вершинный шейдер

Ниже приведён код вершинного шейдера. Его я комментировать не буду, всё и так понятно.

// General
float4x4  matWorldDecalProj;
float2  timeValues; // x - emmitter delta, y - inv particle lifetime
float3  totalForce;

// Physics
float4  particleInvMass;
float4  particleSize; //x,y - start size, z,w - end size

// Visuals
float4  particleStartColor;
float4  particleEndColor;

// Additionals
float2  particleUV[4];
float3  particleQuad[4];

struct VS_INPUT
{
  float3  velocity:  POSITION;
  int4    vertexID:  BLENDINDICES;
  float    lifetime:  BLENDWEIGHT;
};

struct VS_OUTPUT
{
  float4  position:  POSITION;
  float4  diffuse:  COLOR;
  float2  texcoord:  TEXCOORD;
};

VS_OUTPUT vs_main(VS_INPUT In)
{
  VS_OUTPUT  Out;
  float4  pos;
  float    particleTime;
  float3  quadPoint;
  float2  lerpValue;
  
  // Calculate time values
  particleTime= In.lifetime + timeValues.x;
  lerpValue  = particleTime * timeValues.y;
  
  quadPoint  = particleQuad[In.vertexID.x];
  quadPoint.xy= quadPoint.xy*lerp(particleSize.xy, particleSize.zw, lerpValue.xy);
  pos.xyz  = quadPoint + In.velocity*particleTime +
          totalForce*particleInvMass.x*particleTime*particleTime;
  pos.w    = 1.0f;  
  Out.position= mul(matWorldDecalProj, pos);
  Out.diffuse  = lerp(particleStartColor, particleEndColor, lerpValue.xxxx);
  Out.texcoord= particleUV[In.vertexID.x];
  return Out;
}

Заключение

Подведём итоги. Все требования технического задания выполнены, система частиц работает, местами можно даже сделать красивые эффекты. Теперь что стоило бы переделать:
  1) использовать один вершинный (точнее два) и один индексный буфер на все эмиттеры;
  2) сделать несколько вариантов законов изменения параметров частиц, для каждого свой шейдер;
  3) в системе наблюдается некоторая повторяемость движения частиц (ну ещё бы :),
      но это легко исправляется добавлением небольшой случайной колеблющейся силы.
  4) ну и переписать всё это для совместимости с fixed-function и sm2-3.

Наконец, покажу что получилось:

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

Конечно в динамике выглядит гораздо лучше, но думаю и так понятно что всё работает.

Страницы: 1 2 3

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