遊戲開發基礎(十四)

第十四章
顯示粒子時,用點圖元(由D3DPRIMITIVETYPE類型的D3DPT_POINTLIST枚舉常量表示)是一個很好的選擇,但是,光柵化時,點圖元將被映射爲一個單個像素,這就無法提供很大的靈活性,因爲實際應用中可能需要各種尺寸的粒子甚至希望能夠對這些粒子進行紋理映射,在Direct3D 8.0之前,要想擺脫點圖元的這個限制,只能是不去使用它,那時,程序員都願意用廣告牌技術來顯示一個粒子,廣告牌就是一個四邊形,通過對其自身世界變換矩陣的控制,使其總是面向攝像機

Direct3D 8.0引入一種特別的點圖元--點精靈(Point Sprite),該圖元極適合用於粒子系統中,與普通的點圖元不同,點精靈可進行紋理映射且其尺寸可變,點精靈不同於廣告牌,描述點精靈時,僅需要一個單點(single point)即可。由於只需要存儲和處理一個頂點而非4個(廣告牌要用4個頂點描述),這就節省了內存和寶貴的運算時間

該結構描述粒子的位置和顏色
struct Particle
{
 D3DXVECTOR3 _position;
 D3DCOLOR _color;
 static const DWORD FVF;
};

const DWORD Particle::FVF = D3DFVF_XYZ | D3DFVF_DIFFUSE|D3DFVF_PSIZE;

請注意,即使硬件不支持D3DFVFCAPS_PSIZE,藉助像素着色器(vertex shader)也有可能控制每個粒子的尺寸

點精靈的行爲很大程度上是由繪製狀態來控制的


#D3DRS_POINTSPRITEENABLE 一個布爾值,默認爲false
 *若指定爲true,則規定整個當前紋理被映射到點精靈上
 *若指定爲false,則規定點精靈(如果其頂點結構中含紋理座標的話)的紋理座標所指定的紋理元應被映射到點精靈上
#D3DRS_POINTSCALEENABLE一個布爾值,默認爲false
 *若至指定爲true,則規定點的尺寸將用觀察座標系的單位來度量,觀察座標系的單位是僅用來描述攝像機座標系中的3D點。點精靈的尺寸將依據近大遠小的原則進行相應的比例變換
 *若爲false,則規定的尺寸將用屏幕座標系的單位(即像素)來度量,如果你將該繪製狀態指定爲false,而且你想將點精靈的尺寸設爲3,則點精靈將變爲屏幕上一個3*3的像素區域.
 _device->SetRenderState(D3DRS_POINTSCALLENABLE,true);
#D3DRS_POINTSIZE 指定點精靈的尺寸,該值可被解釋爲觀察座標系中的點精靈尺寸,也可被解釋爲屏幕座標系中點精靈尺寸,這主要取決於繪製狀態D3DRS_POINTSCALEENABLE的設置下面的代碼將點尺寸設爲2.5個單位
 _device->SetRenderState(D3DRS_POINTSIZE,d3d::FtoDw(2.5f));
d3d::FtoDW函數,其功能是將float型變量強制轉爲DWORD類型而且必須這樣做,因爲IDirect3DDevice9::SetRenderState函數要求傳入DWORD類型值而非float型
 DWORD d3d::FtoDw(float f)
 {
  return *((DWORD*)&f);
 }
#D3DRS_POINTSIZE_MIN指定點精靈可取的最小尺寸
 例:將點的尺寸的最小值設爲0.2
 _device->SetRenderState(D3DRS_POINTSIZE_MIN,d3d::FtoDw(0.2f));
#D3DRS_POINTSIZE_MAX指定點精靈可取的最大尺寸
 例:將點的尺寸的最大值設爲5.0
 _device->SetRenderState(D3DRS_POINTSIZE_MAX,d3d::FtoDw(5.0f));
#D3DRS_POINTSCALE_A,D3DRS_POINTSCALE_B,D3DRS_POINTSCALE_C這3個常量控制了點精靈的尺寸如何隨距離發生變化,這裏的距離是指點精靈到攝像機的距離
 
給定距離和這些常量時,Direct3D使用如下公式計算點精靈的最終尺寸:
 FinalSize = ViewportHeight * size* squrt(1/A + B(D)+ C(D*D))
#FinalSize計算出距離後,點精靈的最終尺寸
#ViewportHeight視口(viewport)高度
#Size對應於繪製狀態D3DRS_POINT_SIZE所指定的值
#A,B,C分別對應於繪製狀態D3DRS_POINTSCALE_A,D3DRS_POINTSCALE_B和D3DRS_POINTSCALE_C所指定的值
#D在觀察座標系中點精靈到攝像機的距離.由於在觀察座標系中,攝像機位於座標原點,所以D=squrt(x*x + y * y + z * z),其中(x,y,z)是點精靈在觀察座標系中的位置

 例:對點精靈的距離常量進行了設置,這樣點精靈就隨着距離的增大而變小
 _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));


粒子除了位置和顏色外往往還具有許多其他的屬性,例如:粒子可具有一定的速度,但是繪製粒子時並不需要這些附加屬性,所以,將用於繪製粒子的數據與粒子的屬性分別存儲在兩個不同的結構中,當要創建,銷燬或更新粒子時,需要涉及粒子的屬性,當準備繪製粒子時,可將粒子的位置和顏色信息複製到Particle結構中

粒子的屬性與所要模擬的粒子系統的特定類型息息相關。通過指定一些常用的屬性可以使這些屬性結構變得通用一些
下面是一個較通用的粒子屬性結構
struct Attribute
{
 D3DXVECTOR3 _position;#粒子在世界座標系中的位置
 D3DXVECTOR3 _velocity;#粒子速度,度量單位常用單位/秒
 D3DXVECTOR3 _acceleration;#粒子加速度 度量單位常用單位/秒*秒
 float _lifeTime;#粒子自誕生到消亡所需的時間,例如,可能在一段時間後,殺死一段光束粒子
 float _age;#粒子當前年齡
 D3DXCOLOR _color;#粒子顏色
 D3DXCOLOR _colorFade; #粒子顏色隨時間漸弱
 bool _isAlive;#爲true,表面粒子處於活動狀態,否則粒子處於死亡狀態
};

粒子系統(Particle System)是衆多粒子的集合,並負責對這些粒子進行維護和顯示。粒子系統跟蹤系統中影響所有粒子狀態的全局屬性,例如粒子的尺寸,粒子源,將要映射到粒子的紋理等,按照功能來說,粒子系統主要負責更新(updating),顯示(displaying),殺死(kill)以及創建(creating)粒子

可歸納並找到所有粒子系統都需要的基本屬性,我們將這些通用的屬性封裝在一個抽象基類--PSystem類中,該類將作爲所要實現的全部具體的粒子系統的父類

class PSystem
{
public:
 PSystem();
 virtual ~PSystem();
 //該方法完成一些Direct3D設備相關的初始化工作,如創建頂點緩存以存儲點精靈,創建紋理等
 //頂點緩存的創建過程包含了一些標記
 virtual bool init(IDirect3DDevice9* device,char* texFileName);
 virtual void reset();

 // sometimes we don't want to free the momory of a dead particle
 // but rather respawn it instead
 virtual void resetParticle(Attribute* attribute) = 0 ;//該方法重新設定粒子的屬性值,粒子的屬性應如何重新設置依賴於特定粒子系統的細節,所以該方法聲明爲抽象函數,並強制子類必須實現該函數
 virtual void addParticle();

 virtual void updata(flat timeDelta) = 0 ;//該方法用於對系統中的所有粒子進行更新,由於該方法的實現依賴於特定粒子系統的細節,我們將該方法聲明爲抽象方法,並強制子類必須實現該方法
 
 virtual void preRender();
 virtual void render();//該方法用於顯示系統中的所有粒子,該方法的實現相當複雜
 virtual void postRender();

 bool isEmpty();//當前系統中沒有粒子,該函數返回true,反之返回false
 bool isDead();//系統中所有粒子均已死亡,該函數返回true,反之返回false
protected:
 virtual void removeDeadParticles();

protected:
 IDirect3DDevice9* _deivce;
 D3DXVECTOR3   _origin;// 系統的粒子源,所有的粒子都將從系統粒子源產生
 d3d::BoundingBox  _boundingBox;//限制粒子的活動範圍的外接體
 float     _emitRate;// rate new particles are added to system 新增粒子的增加率,該值用粒子數/秒
 float             _size;// size of particles系統所有粒子的尺寸
 
 IDirect3DTexture9*  _tex;
 IDirect3DVertexBuffer9* _vb;
 std::list<Attribute>    _particles;//系統中粒子的屬性列表,可用該列表對粒子進行創建,銷燬和更新
 int    _maxParticles;//max allowed partices system can have 在某個時間,系統所允許的最大粒子數

 // Following three data elements used for rendering the p-system efficiently

 DWORD _vbSize; // size of vb 在給定時間頂點緩存中所存儲的頂點個數,該值不依賴於粒子系統中的實際粒子個數
 DWORD _vbOffset;// offset in vb to lock 該變量子頂點緩存首地址算起的偏移量,標識了下一批粒子將從頂點緩存的何處開始複製,例,第一批粒子存儲在頂點緩存0-499項,則下一批粒子在頂點緩存中開始複製的偏移量將爲500
 DWORD _vbBatchSize ;// number of vertices to lock starting at _vbOffset 每批粒子的數目
};

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);
注意,這裏使用了動態緩存,這是因爲需要在每幀中對粒子進行更新,這就意味着需要訪問頂點緩存的存儲區

注意,使用了標記D3DUSAGE_POINTS,該標記指定了頂點緩存將用於存儲點精靈

注意,頂點緩存的尺寸已有變量_vbSize預先定義好,該值與系統中的粒子個數無關,即,_vbSize基本不可能與系統的粒子數相等,這是因爲繪製粒子系統時往往是分批繪製的,而非一蹴而就

使用的是默認的內存池而非常用的託管內存池,這是因爲動態頂點緩存不允許被放置在託管內存池中


//該方法重新設定系統中每個粒子的屬性
void PSystem::reset()
{
 std::list<Attribute>::iterator i;
 for(i = _particles.begin(); i != _particles.end();i++)
 {
  resetParticle(&(*i));
 }
}

 

addParticle該方法爲系統增加一個粒子,該方法在將粒子加入列表之前,首先調用 resetParticle方法對粒子進行初始化
void PSystem::addParticle()
{
 Attribute attribute;
 resetParticle(&attribute);
 _particles.push_back(attribute);
}
 

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

 // control the size of the particle relative to distance
 _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
 _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);
}

注意,啓用了Alpha融合,這樣當前紋理的Alpha通道就指定了紋理像素的透明度,以此來產生各種效果,一種常用的場合就是獲取像紋理一樣的非矩形的粒子,例:要獲取一個圓形的像‘雪球’一樣的粒子,可使用一個具有Alpha通道(黑色背景中有白色圓)的純白色紋理,這樣只有白色的圓形會顯示出來,矩形的白色紋理就得不到顯示
postRender該方法存儲一個特定粒子系統可能已設置好的任何繪製狀態,由於這也依賴於具體的系統,將該函數聲明爲虛函數
void PSystem::postRender()
{
 _device->SetRenderState(D3DRS_LIGHTING,true);
 _device->SetRenderState(D3DRS_POINTSPRITEENABLE,false);
 _device->SetRenderState(D3DRS_POINTSCALEENABLE,false);
 _device->SetRenderState(D3DRS_ALPHABLENDENABLE,false);
}

removeDeadParticles 對屬性列表_Particle進行搜索,並從列表中移除任何已死亡的粒子

void PSystem::removeDeadParticles()
{
 std::list<Attribute>::interator i;
 
 i = _particles.begin();

 while(i != _particles.end())
 {
  if(i->isAlive == false)
  {
   // erase returns the next iterator ,so no need to
   // increment to the next one ourslves
   i = _particles.erase(i);
  }
  else
  {
   i++;// next in list
  }
 }
}
 
注意,該方法通常在子類的update方法中被調用,已移除任何已被殺死的粒子,但是,對於某些粒子系統,回收那些處於死亡狀態的粒子比銷燬它們有更多的優勢,在粒子誕生和消亡時,不是從列表中爲其重新分配內存或回收內存,只是對那些處於死亡狀態的粒子重新設置,使其變爲新粒子


繪製粒子系統:創建一個容量合理的頂點緩存,然後將該頂點緩存劃分爲若干片段,然後創建全局變量i= 0 在跟蹤當前片段


對每幀的操作如下:
1 更新所有粒子
2 將全部活動粒子被繪製
#若頂點緩存未滿,則
 (1)用標記 D3DLOCK_NOOVERWRITE鎖定片段i;
 (2)將500個粒子複製到鎖定片段i中
#若頂點緩存已存滿,則
 (1)用自頂點緩存的起始位置開始i = 0
 (2)將用標記D3DLOCK_DISCARD鎖定片段i
 (3)將500個粒子複製到鎖定片段i中
#繪製片段i中的粒子
#下一片斷:i++

注意: 前面頂點緩存指定爲動態的,所以在鎖定頂點緩存時,使用了動態鎖定標記D3DLOCK_NOOVERWRITE和D3DLOCK_DISCARD,這些標記允許對頂點緩存中尚未被繪製的部分進行鎖定,這樣錯絲毫不影響頂點緩存中其餘部分的繪製,例如,假定要繪製片段0中的粒子,通過使用標記D3DLOCK_NOOVERWRITE,當繪製片段0中的粒子時,可對片段1鎖定並填充,這就避免了可能出現繪製中斷的情況

該方法效率更高,首先,減少了頂點緩存的容量,其次,cpu和圖形卡現在可以協同工作,向頂點緩存複製(CPU來做)一小批粒子,然後再對該批粒子進行繪製(圖形卡來做),接下來再爲頂點緩存複製下一批粒子,然後對其進行繪製,該過程一直持續到全部粒子都被繪製完畢,從中可看出,在填充頂點緩存過程中,圖形卡不在處於空閒狀態

//列出繪製方法的實現代碼
void PSystem::render()
{
 if (!_particles.empty())
 {
  // set render states
  preRender();

  _device -> SetTexture(0,_tex);
  _device -> SetFVF(Particle::FVF);
  _device -> SetStreamSource(0,_vb,0,sizeof(Particle));

  // start at beginning if we're at the end of the vb
  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 ;
   // Until all particles have been rendered
   std::list<Attribute>::iterator i;
   for(i = _particles.begin(); i != _particles.end();i++)
   {
    if(i->isVlive)
    {
     // copy a batch of the living particles to the
     // next vertex buffer segment

     v->_position = i -> _position;
     v->_color    = (D3DCOLOR)i->_color;
     v++; // 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 momory thats outside the vb's range
      // if we're at the end ,start at the begining
      if(_vbOffset >= _vbSize) 
       _vbOffset = 0 ;

      _vb->Lock(_vbOffset * sizeof(Particle),
       _vbBatchSize * sizeof(Particle),
       (void**)&v,
       _vbOffset ? D3DLOCK_NOOVEWRITE : 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. wo draw the last partially filled batch now
   
   if (numParticlesInBatch)
   {
    _device->DrawPrimitive(
       D3DPT_POINTLIST,
       _vbOffset,
       numParticlesInBatch);
   }
   // next block
   _vbOffset += _vbBatchSize;
   
   // reset render states
   postRender();
  }
  
 }
}

粒子系統隨機功能:
返回區間[lowBound,highBound]內隨機浮點數
float d3d::GetRandomFloat(float lowBound,float highBound)
{
 if (lowBound >= highBound)// bad input
  return lowBound;
 
 // get random float in [0,1] interval
 float f = (rand()%10000) * 0.0001f;
 
 // return float in [lowBound,highBound] interval
 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);
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章