粒子系统在上一篇博客中简单介绍了一下:DirectX中的粒子系统
现在我们知道,粒子系统是动态的,我们必须每一帧都去更新这个粒子系统,有一种直观但是效率不高的方法如下:
1、更新所有粒子的状态
2、创建一个足够容纳粒子系统中最大粒子数目的顶点缓存
3、将所有处于活动状态的粒子赋值到这个巨大的顶点缓存中去
4、将顶点缓存中的粒子全部绘制出来
该方法的效率不高的原因,其一是因为我们必须创建一个足够大的顶点缓存,里面需要存放粒子系统中最大的粒子数目;其二是因为我们在创建顶点缓存之后,需要将所有的顶点都复制到顶点缓存里面,数目非常的大,此时GPU的没有任何的事情在做的,相当于空闲状态,此时因为CPU和GPU没有协同工作,所以效率是不高的。
一种更好的方法就是我们现在创建一个合理大小的顶点缓存,然后我们将这个顶点缓存划分为几个片段,比如,我们创建一个可以容纳2000粒子的顶点缓存,然后我们把它分为4个片段,每一个片段可以容纳500个粒子,然后创建一个全局变量i来跟踪这个片段。
上面这个是一种理想化的情况,事实上我们总是在不断的创建和销毁粒子,所以粒子的数目很有可能不足500,填不满一个片段,比如现在我需要绘制200个粒子,我们可以将这种情况作为一个特殊的情况来处理,这种情况只会在当前帧时填充最后一个片段时发生,也就是我们将不够一个片段的粒子放在最后一个片段去填充。
该方案的优点在于,大大减少了顶点缓存的大小,而且使得CPU和GPU可以协同工作,因为我们先去赋值一部分的顶点在顶点缓存里,然后让GPU去绘制这些顶点,绘制的时候,再去复制下一批顶点到顶点缓存里面,就这样直到所有的粒子被绘制完毕,这个过程中,GPU就不会出现空闲的状态。
下面为这种绘制方案的具体代码实现:
void PSystem:: render()
{
if(l_particles. empty())
{
//设置渲染状态
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;
//对顶点缓存进行操作的时候先去上锁
//_vbBatchSize每一批粒子的数目
_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->_isAlive)
{
//
//copy a batch of the living particles to the
//next vertex buffer segment
//将仍然存活的粒子复制到顶点缓存里面
//i现在指的是粒子系统链表上的节点
//把粒子系统上的节点的坐标和颜色复制到缓存v里面
v->_position=i->_position;
v->_color = (D3DCOLOR)i->_color;.
v++; //下一个节点;
numParticlesInBatch++; //每一个片段里面的计数器
//判断这个片段计数器是不是等于每一个片段的最大值
//也就是判断当前片段有没有满
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.
//上一个函数已经开始绘制了,现在继续填充下一批粒子
//将偏移量移动到下一个片段的开头
_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
//将偏移量移动到下一个片段的开头,
//暂时还没有发现这个代码的具体用处,因为最后一次已经绘制完成,下一次绘制应该是从顶点缓存的起始开始的
//此时继续往下偏移感觉没什么用,
_vboffset += _vbBatchSize;
//
// reset render states
// 重置绘制状态
postRender();
}//end if
}//end render