14.2.1 繪制粒子系統(tǒng)
因?yàn)榱W酉到y(tǒng)是動(dòng)態(tài)的,在每一個(gè)幀中我們需要更新系統(tǒng)中的粒子,對(duì)于渲染粒子系統(tǒng)的一種直觀但效率低下的方法如下:
創(chuàng)建一個(gè)足夠大的頂點(diǎn)緩存保存最大數(shù)量的粒子。
每一幀里執(zhí)行:
A. 更新所有粒子。
B. COPY所有活著的粒子到頂點(diǎn)緩存。
C. 繪制頂點(diǎn)緩存。
這個(gè)方法正確,不過(guò)不是最有效率的。第一,頂點(diǎn)緩沖必須足夠大以保存系統(tǒng)中所有粒子。但是非常重要的是,當(dāng)我們從列表拷貝所有粒子到頂點(diǎn)緩沖(步驟B)時(shí),顯卡卻什么也不做。舉個(gè)例子,假設(shè)我們系統(tǒng)有10,000個(gè)粒子,首先我們需要一個(gè)能容納10,000個(gè)粒子的頂點(diǎn)緩沖,這是一個(gè)很大的內(nèi)存。另外顯卡將停著什么也不做直到列表中的10,000個(gè)粒子拷到頂點(diǎn)緩沖,直到我們調(diào)用DrawPrimitive。這個(gè)特定情況是CPU與顯卡不同時(shí)工作的一個(gè)很好的例子。
更好的辦法(SDK中點(diǎn)精靈例程中用到的方法)就象這樣:
提示:這是一個(gè)簡(jiǎn)單的描述,但它說(shuō)明了這一思想。它假定我們總是有500個(gè)粒子以填充一個(gè)緩存片段,但是這是不可能發(fā)生的,因?yàn)槲覀兘?jīng)常殺死并創(chuàng)建粒子,所以從一幀到另一幀粒子數(shù)量是變化的。舉個(gè)例子,假設(shè)我們只剩下200個(gè)粒子要在當(dāng)前幀拷貝并渲染。因?yàn)?span lang="EN-US">200個(gè)粒子不能填充整個(gè)緩存片段,我們用代碼處理這個(gè)特定情形。這個(gè)特定情形只有在最后的緩存片段中才會(huì)出現(xiàn),因?yàn)槿绻皇亲詈蟮钠瑪啵鸵馕吨厝挥?span lang="EN-US">500個(gè)粒子將被移到下一緩存片段。
創(chuàng)建一個(gè)合適尺寸的頂點(diǎn)緩存(能夠保存2000個(gè)粒子),然后我們劃分頂點(diǎn)緩存為幾個(gè)小的塊,就像這個(gè)例子,我們?cè)O(shè)置每個(gè)緩存片斷的尺寸為500個(gè)粒子。

l然后創(chuàng)建一個(gè)全局變量 i = 0 ,用來(lái)記錄片段。
每一幀里執(zhí)行:
A. 更新所有粒子。
B. 直到所有粒子渲染完畢。
1. 如果頂點(diǎn)緩存沒(méi)有滿:
a 用D3DLOCK_NOOVERWRITE標(biāo)記鎖定緩存片段i
b COPY 500個(gè)粒子到片段i
2. 如果頂點(diǎn)緩存滿了:
a 從起始的地方開(kāi)始頂點(diǎn)緩沖: i=0
b 用D3DLOCK_NOOVERWRITE標(biāo)記鎖定緩存段i
c COPY 500個(gè)粒子到片段i
3. 渲染片段i.
4. 下一片段: i+ +
備注:頂點(diǎn)緩存是動(dòng)態(tài)的, 因此我們能利用動(dòng)態(tài)鎖定標(biāo)記D3DLOCK_NOOVERWRITE和 D3DLOCK_DISCARD。這兩個(gè)標(biāo)記允許我們鎖定頂點(diǎn)緩存的某一部分。當(dāng)頂點(diǎn)緩存中的其他部分被渲染時(shí),它是不能渲染的。例如,假如我們正在使用D3DLOCK_NOOVERWRITE標(biāo)記渲染片段0時(shí), 當(dāng)渲染片段0的時(shí)候我們能鎖定并填充片段1。這樣可以防止渲染的延遲。
這個(gè)方法更有效率。首先,我們減少頂點(diǎn)緩存的尺寸;然后, CPU與顯卡在協(xié)調(diào)的工作。也就是說(shuō),當(dāng)我們繪制一小批粒子時(shí)(graphics card work),同時(shí)拷貝另一小批粒子到頂點(diǎn)緩存 (CPU work)。這個(gè)動(dòng)作是連續(xù)執(zhí)行的,直到所有的粒子都被渲染完畢,就像你了解的一樣, 顯卡在全部頂點(diǎn)緩存被填充的時(shí)候是不用處于空閑狀態(tài)的。
我們現(xiàn)在將注意力轉(zhuǎn)向這一個(gè)渲染方案的實(shí)現(xiàn),為了方便使用這個(gè)粒子系統(tǒng)的渲染方案, 我們使用 cParticleSystem 類中的下列數(shù)據(jù)成員:
m_vb_num—在給定時(shí)間內(nèi)我們的頂點(diǎn)緩存能夠保存的粒子數(shù)量。這個(gè)值與實(shí)際的粒子系統(tǒng)中的粒子數(shù)無(wú)關(guān)。
m_vb_offset—這個(gè)變量是頂點(diǎn)緩存中的偏移,在頂點(diǎn)緩存里我們將用它開(kāi)始COPY下一批粒子,例如,如果第一批在緩存中是0到499,偏移到第二批COPY的開(kāi)始處將是500。
m_vb_batch_size—定義一批緩存中的粒子數(shù)量。
我們現(xiàn)在介紹渲染方法的代碼:
void cParticleSystem::render()
{
// 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(m_particles.empty())
return;
// set render states
pre_render();
m_device->SetTexture(0, m_texture);
m_device->SetFVF(PARTICLE_FVF);
m_device->SetStreamSource(0, m_vertex_buffer, 0, sizeof(sParticle));
//
// render batches one by one
//
// start at beginning if we're at the end of the vertex buffer
if(m_vb_offset >= m_vb_num)
m_vb_offset = 0;
sParticle* v;
m_vertex_buffer->Lock(
m_vb_offset * sizeof(sParticle),
m_vb_batch_num * sizeof(sParticle),
(void**)&v,
m_vb_offset ? D3DLOCK_NOOVERWRITE : D3DLOCK_DISCARD);
DWORD num_particles_in_batch = 0;
// until all particles have been rendered
for(list<sParticleAttribute>::iterator iter = m_particles.begin(); iter != m_particles.end(); iter++)
{
if(! iter->is_alive)
continue;
// copy a batch of the living particles to the next vertex buffer segment
v->position = iter->position;
v->color = (D3DCOLOR) iter->color;
v++; // next element
num_particles_in_batch++;
// if this batch full?
if(num_particles_in_batch == m_vb_batch_num)
{
// draw the last batch of particles that was copied to the vertex buffer
m_vertex_buffer->Unlock();
m_device->DrawPrimitive(D3DPT_POINTLIST, m_vb_offset, m_vb_batch_num);
//
// While that batch is drawing, start filling the next batch with particles.
//
// move the offset to the start of the next batch
m_vb_offset += m_vb_batch_num;
// Don't offset into memory that is outside the vb's range.
// If we're at the end, start at the beginning.
if(m_vb_offset >= m_vb_num)
m_vb_offset = 0;
m_vertex_buffer->Lock(
m_vb_offset * sizeof(sParticle),
m_vb_batch_num * sizeof(sParticle),
(void**)&v,
m_vb_offset ? D3DLOCK_NOOVERWRITE : D3DLOCK_DISCARD);
num_particles_in_batch = 0; // reset for new batch
}
}
m_vertex_buffer->Unlock();
// Its possible that the LAST batch being filled never got rendered because the condition
// (num_particles_in_batch == m_vb_batch_num) would not have been satisfied.
// We draw the last partially filled batch now.
if(num_particles_in_batch)
m_device->DrawPrimitive(D3DPT_POINTLIST, m_vb_offset, num_particles_in_batch);
m_vb_offset += m_vb_batch_num; // next block
post_render(); // reset render states
}
14.2.2 隨機(jī)
如果我們模擬雪花,不能讓所有雪花以完全相同的方式落下。我們要讓它們按相似的方式落下而不是完全相同的方式。為了使粒子系統(tǒng)的隨機(jī)功能更簡(jiǎn)單,我們?cè)黾恿讼铝袃蓚€(gè)函數(shù)到d3dUtility.h/cpp文件。
第一個(gè)函數(shù)在[low_bound, high_bound]區(qū)間內(nèi)隨機(jī)的返回一個(gè)float類型值:
float get_random_float(float low_bound, float high_bound)
{
if(low_bound >= high_bound) // bad input
return low_bound;
// get random float in [0, 1] interval
float f = (rand() % 10000) * 0.0001f;
// return float in [low_bound, high_bound] interval
return f * (high_bound - low_bound) + low_bound;
}
第二個(gè)函數(shù)在邊界盒的范圍內(nèi),輸出一個(gè)隨機(jī)的向量。
void get_random_vector(D3DXVECTOR3* out, D3DXVECTOR3* min, D3DXVECTOR3* max)
{
out->x = get_random_float(min->x, max->x);
out->y = get_random_float(min->y, max->y);
out->z = get_random_float(min->z, max->z);
}