用D3D8以后用点精灵来指定一个粒子,而不是D3D8以前的需要用公告板平面(四个顶点)来模拟一个粒子。
点精灵粒子的大小,纹理颜色,都可以用渲染状态来改变。粒子系统的渲染信息结构(位置和颜色)和粒子系统的属性(包括了位置和颜色变化、速度加速度和生命期),粒子属性会每帧更新且拷贝到粒子系统信息结构体渲染即可(不拷贝不需要那么多消息,拷贝了又会影响性能)。
使用点精灵(一个顶点)和渲染状态技术来表现一个粒子位置,大小,纹理和颜色,而不是用老式的公告板平面(4个顶点)。
粒子的顶点缓存(没有索引缓存)放置在显存中,使用D3DUSAGE_DYNAMIC可以大幅提高读取更新性能,但是不能 用D3DPOOL_MANAGED,D3DLOCK_DISCARD和D3DLOCK_NOOVERWRITE只能在D3DUSAGE_DYNAMIC中 使用,且D3DLOCK_DISCARD可以满天过海避免同步可以有效的提高锁定性能。
粒子系统的更新、消亡内存管理和粒子阈值控制数量,每个子粒子系统都要维护自己的更新和重置函数,粒子消亡后可以通过不删掉内存而是重置的方式提高内存管理效率;且粒子系统需要一个粒子最大量的值,避免粒子的产生率比消亡率大,导致粒子膨胀。
粒子系统分段渲染,属性不断更新,拷贝到粒子顶点缓存进行渲染的方式,如果整个拷贝完再渲染会阻碍了CPU和GPU的协同工作效率,应该采用分段的思想来处理,也就是拷贝完一部分渲染一部分,粒子顶点缓存采用动态更新且放置到显存中提高效率,且锁定粒子顶点缓存时候用D3DLOCK_DISCARD和D3DLOCK_NOOVERWRITE方式避免同步提高效率。
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);
}
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;
}
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) );
}
}
}
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();
}
}