D3D粒子系统设计关键要点

粒子系统设计思想

用D3D8以后用点精灵来指定一个粒子,而不是D3D8以前的需要用公告板平面(四个顶点)来模拟一个粒子。

点精灵粒子的大小,纹理颜色,都可以用渲染状态来改变。粒子系统的渲染信息结构(位置和颜色)和粒子系统的属性(包括了位置和颜色变化、速度加速度和生命期),粒子属性会每帧更新且拷贝到粒子系统信息结构体渲染即可(不拷贝不需要那么多消息,拷贝了又会影响性能)。

1. 使用点精灵(一个顶点)和渲染状态技术

使用点精灵(一个顶点)和渲染状态技术来表现一个粒子位置,大小,纹理和颜色,而不是用老式的公告板平面(4个顶点)

2.粒子的顶点缓存(没有索引缓存)放置在显存中,使用D3DUSAGE_DYNAMIC可以大幅提高读取更新性能

粒子的顶点缓存(没有索引缓存)放置在显存中,使用D3DUSAGE_DYNAMIC可以大幅提高读取更新性,但是不能 用D3DPOOL_MANAGED,D3DLOCK_DISCARD和D3DLOCK_NOOVERWRITE只能在D3DUSAGE_DYNAMIC中 使用,且D3DLOCK_DISCARD可以满天过海避免同步可以有效的提高锁定性能。

3.粒子系统的更新、消亡内存管理和粒子阈值控制数量

粒子系统的更新、消亡内存管理和粒子阈值控制数量,每个子粒子系统都要维护自己的更新和重置函数,粒子消亡后可以通过不删掉内存而是重置的方式提高内存管理效率;且粒子系统需要一个粒子最大量的值,避免粒子的产生率比消亡率大,导致粒子膨胀

4. 粒子系统分段渲染

粒子系统分段渲染,属性不断更新,拷贝到粒子顶点缓存进行渲染的方式,如果整个拷贝完再渲染会阻碍了CPU和GPU的协同工作效率,应该采用分段的思想来处理,也就是拷贝完一部分渲染一部分,粒子顶点缓存采用动态更新且放置到显存中提高效率,且锁定粒子顶点缓存时候用D3DLOCK_DISCARD和D3DLOCK_NOOVERWRITE方式避免同步提高效率


粒子系统关键技术细节

1.点精灵相关的状态设置

void PSystem::preRender()

{

 _device->SetRenderState(D3DRS_LIGHTING, false);

 // 当为true时候点精灵的纹理来自一整个纹理,否则点精灵的纹理来自于顶点指定的uv纹理坐标映射的纹理,默认是false

 _device->SetRenderState(D3DRS_POINTSPRITEENABLE, true);

 //  true表示用视图空间单位来解释点的大小。视图空间单位的3D空间点在照相机中,点精灵将会自动缩放,

 //  这取决到它有多远, 像其他对象一样,离照相机近的粒子比离照相机远的粒子要大。

 // false 表示点的大小将用屏幕空间单位来解释。屏幕空间单位是屏幕上的像素单位。. 因此如果你指定false,

 // 例如, 设置点精灵的尺寸为3, 则点精灵在屏幕区域中的尺寸为3×3像素。

 _device->SetRenderState(D3DRS_POINTSCALEENABLE, true);

 // 表示点精灵的尺寸. 这个值可以任意指定视图空间或屏幕空间的点精灵的尺寸, 取决于D3DRS_POINTSCALEENABLE状态如何设置

 _device->SetRenderState(D3DRS_POINTSIZE, d3d::FtoDw(_size));

 // 表示点精灵的最小尺寸

 _device->SetRenderState(D3DRS_POINTSIZE_MIN, d3d::FtoDw(0.2f));

 // 表示点精灵的最大尺寸

 _device->SetRenderState(D3DRS_POINTSIZE_MAX, d3d::FtoDw(_size + 1));

 // control the size of the particle relative to distance

 // D3DRS_POINTSCALE_A, D3DRS_POINTSCALE_B, D3DRS_POINTSCALE_C

 // 这3个常量表示如何根据距离控制点精灵的尺寸—这个距离是点精灵到照相机的距离。

 // 公式是: FinalSize = PointSize * sqrt( 1 / ( A + B*D + C*D^2);再从MinSize, MaxSize,FinalSize中取得大小

 // D是点精灵到视图空间原点的距离。

 _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));

 

 // use alpha from texture

 // 为了启用遮罩的,也就是方形的粒子通过alpha融合使得粒子变为圆形的

 _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);

}


2. 粒子顶点缓存

bool PSystem::init(IDirect3DDevice9* device, char* texFileName)

{

 // vertex buffer's size does not equal the number of particles in our system.  We

 // use the vertex buffer to draw a portion of our particles at a time.  The arbitrary

 // size we choose for the vertex buffer is specified by the _vbSize variable.

 _device = device; // save a ptr to the device

 HRESULT hr = 0;

 // D3DUSAGE_DYNAMIC可以大幅提高读取更新性能,但是不能用D3DPOOL_MANAGED

 // D3DLOCK_DISCARD和D3DLOCK_NOOVERWRITE只能在D3DUSAGE_DYNAMIC中使用,

 // 且D3DLOCK_DISCARD可以满天过海避免同步可以有效的提高锁定性能。

 hr = device->CreateVertexBuffer(

  _vbSize * sizeof(Particle),

  D3DUSAGE_DYNAMIC | D3DUSAGE_POINTS | D3DUSAGE_WRITEONLY,

  Particle::FVF,

  D3DPOOL_DEFAULT, // D3DPOOL_MANAGED can't be used with D3DUSAGE_DYNAMIC

  &_vb,

  0);

 

 if(FAILED(hr))

 {

  ::MessageBox(0, "CreateVertexBuffer() - FAILED", "PSystem", 0);

  return false;

 }

 hr = D3DXCreateTextureFromFile(

  device,

  texFileName,

  &_tex);

 if(FAILED(hr))

 {

  ::MessageBox(0, "D3DXCreateTextureFromFile() - FAILED", "PSystem", 0);

  return false;

 }

 return true;

}


3.更新粒子和回收粒子

void Snow::resetParticle(Attribute* attribute)

{

 attribute->_isAlive  = true;

 // get random x, z coordinate for the position of the snow flake.

 d3d::GetRandomVector(

  &attribute->_position,

  &_boundingBox._min,

  &_boundingBox._max);

 // no randomness for height (y-coordinate).  Snow flake

 // always starts at the top of bounding box.

 // 回收粒子到顶部

 attribute->_position.y = _boundingBox._max.y;

 // snow flakes fall downwards and slightly to the left

 attribute->_velocity.x = d3d::GetRandomFloat(0.0f, 1.0f) * -3.0f;

 attribute->_velocity.y = d3d::GetRandomFloat(0.0f, 1.0f) * -10.0f;

 attribute->_velocity.z = 0.0f;

 // white snow flake

 attribute->_color = d3d::WHITE;

}

void Snow::update(float timeDelta)

{

 std::list<Attribute>::iterator i;

 for(i = _particles.begin(); i != _particles.end(); i++)

 {

  i->_position += i->_velocity * timeDelta;

  // is the point outside bounds?

  // 回收粒子到顶部

  if( _boundingBox.isPointInside( i->_position ) == false )

  {

   // nope so kill it, but we want to recycle dead

   // particles, so respawn it instead.

   resetParticle( &(*i) );

  }

 }

}

4.分段拷贝和拷贝完成渲染机制,提高CPU和GPU的协同工作效率

void PSystem::render()

{

 //

 // Remarks:  The render method works by filling a section of the vertex buffer with data,

 //           then we render that section.  While that section is rendering we lock a new

 //           section and begin to fill that section.  Once that sections filled we render it.

 //           This process continues until all the particles have been drawn.  The benifit

 //           of this method is that we keep the video card and the CPU busy.  

 if( !_particles.empty() )

 {

  //

  // set render states

  //

  preRender();

 

  _device->SetTexture(0, _tex);

  _device->SetFVF(Particle::FVF);

  _device->SetStreamSource(0, _vb, 0, sizeof(Particle));

  //

  // render batches one by one

  //

  // start at beginning if we're at the end of the vb

  if(_vbOffset >= _vbSize)

   _vbOffset = 0;

  Particle* v = 0;

  // 锁定一个分段进行同时拷贝和分段完成进行渲染,如果是开始那么 D3DLOCK_DISCARD,否则追加数据

  _vb->Lock(

   _vbOffset    * sizeof( Particle ),

   _vbBatchSize * sizeof( Particle ),

   (void**)&v,

   _vbOffset ? D3DLOCK_NOOVERWRITE : D3DLOCK_DISCARD);

  DWORD numParticlesInBatch = 0;

  //

  // Until all particles have been rendered.

  // 分段的填充和分段渲染,提高CPU和GPU的系统工作效率

  // 丢弃只是针对顶点缓存说的

  std::list<Attribute>::iterator i;

  for(i = _particles.begin(); i != _particles.end(); i++)

  {

   if( i->_isAlive )

   {

    //

    // Copy a batch of the living particles to the

    // next vertex buffer segment

    //

    v->_position = i->_position;

    v->_color    = (D3DCOLOR)i->_color;

    v++; // next element;

    numParticlesInBatch++; //increase batch counter

    // if this batch full?

    // 分段满了渲染

    if(numParticlesInBatch == _vbBatchSize)

    {

     //

     // Draw the last batch of particles that was

     // copied to the vertex buffer.

     //

     _vb->Unlock();

     _device->DrawPrimitive(

      D3DPT_POINTLIST,

      _vbOffset,

      _vbBatchSize);

     //

     // While that batch is drawing, start filling the

     // next batch with particles.

     //

     // move the offset to the start of the next batch

     _vbOffset += _vbBatchSize;

     // don't offset into memory thats outside the vb's range.

     // If we're at the end, start at the beginning.

     if(_vbOffset >= _vbSize)

      _vbOffset = 0;      

     // 重新锁定后面或者回到开始的分段,同时进行拷贝和分段完进行渲染

     _vb->Lock(

      _vbOffset    * sizeof( Particle ),

      _vbBatchSize * sizeof( Particle ),

      (void**)&v,

      _vbOffset ? D3DLOCK_NOOVERWRITE : D3DLOCK_DISCARD);

     numParticlesInBatch = 0; // reset for new batch

    }

   }

  }

  _vb->Unlock();

  // its possible that the LAST batch being filled never

  // got rendered because the condition

  // (numParticlesInBatch == _vbBatchSize) would not have

  // been satisfied.  We draw the last partially filled batch now.

 

  // 最后拷贝好的非整分段数量的缓存,直接进行渲染即可

  if( numParticlesInBatch )

  {

   _device->DrawPrimitive(

    D3DPT_POINTLIST,

    _vbOffset,

    numParticlesInBatch);

  }

  // next block

  // 直接下一个分段的意思,用于下一帧到达进行

  /*if(_vbOffset >= _vbSize)

   _vbOffset = 0; */

  // 判断,回到顶点缓存开头进行分段拷贝,和分段拷贝完成马上渲染

  _vbOffset += _vbBatchSize;

  //

  // reset render states

  //

  // 重置渲染状态

  postRender();

 }

}



你可能感兴趣的:(D3D粒子系统设计关键要点)