另一个XNA问题的时间。这一次,纯粹是从技术设计的角度来看。
我的情况是这样的:我已经创建了一个基于GPU计算的粒子引擎,远非完整但可行。我的GPU可以轻松处理10k颗粒而不会出汗,如果我能添加更多的话,我也不会感到惊讶。
我的问题:每当我同时创建大量粒子时,我的帧速率就讨厌我。为什么?大量的CPU使用,即使我已将其最小化以包含几乎只有内存操作。
粒子的创建仍然由CPU调用完成,例如:
当我有大约4个发射器每帧创建一个粒子时,我的FPS降低(当然,每秒只有4帧但是15个发射器将我的FPS降低到25)。
创建粒子:
//### As you can see, not a lot of action here. ###
ParticleVertex []tmpVertices = ParticleQuad.Vertices(Position,Velocity,this.TimeAlive);
particleVertices[i] = tmpVertices[0];
particleVertices[i + 1] = tmpVertices[1];
particleVertices[i + 2] = tmpVertices[2];
particleVertices[i + 3] = tmpVertices[3];
particleVertices[i + 4] = tmpVertices[4];
particleVertices[i + 5] = tmpVertices[5];
particleVertexBuffer.SetData(particleVertices);
我的想法是,也许我不应该经常创建粒子,也许有办法让GPU创造一切,或者我可能只是不知道你是如何做这些东西的。 ;)
编辑:如果我不经常创建粒子,那么仍然可以使它看起来很好的解决方法是什么?
所以我在这里发帖,希望你知道如何设计一个好的粒子引擎,如果我在某个地方采取了错误的路线。
答案 0 :(得分:4)
没有办法让GPU创建所有东西(没有使用需要SM4.0的Geometry Shaders)。
如果我正在创建一个粒子系统以获得最大的CPU效率,我会预创建(仅为了举例来选择一个数字)在顶点和索引缓冲区中有100个粒子,如下所示: / p>
很酷的是你只需要这样做一次 - 你可以为你的所有粒子系统重复使用相同的顶点缓冲区和索引缓冲区(假设它们足够大,可用于你最大的粒子系统)。
然后我会有一个顶点着色器,它将采用以下输入:
然后,该顶点着色器(再次类似于XNA Particle 3D Sample)可以根据粒子的初始速度和粒子在模拟中的时间来确定粒子顶点的位置。
每个粒子的时间是(伪代码):
time = (currentTime + timeOffset) % particleLifetime;
换句话说,随着时间的推移,粒子将以恒定的速率释放(由于偏移)。每当一个粒子在time = particleLifetime
处死亡(或者它是1.0?浮点模数令人困惑)时,时间循环回到time = 0.0
,以便粒子重新进入动画。
然后,当我需要绘制粒子时,我会设置缓冲区,着色器和着色器参数,然后调用DrawIndexedPrimitives
。现在这里有一个聪明的一点:我会设置startIndex
和primitiveCount
,这样就不会在动画中期开始出现粒子。当粒子系统首次开始时,我将绘制1个粒子(2个基元),当粒子即将死亡时,我将绘制所有100个粒子,其中第100个粒子将刚刚开始。
然后,片刻之后,第一个粒子的计时器将循环并使其成为第101个粒子。
(如果我只想在我的系统中使用50个粒子,我只需将粒子寿命设置为0.5,并且只能在顶点/索引缓冲区中绘制100个粒子中的前50个粒子。)
当关闭粒子系统的时候 - 只需反过来做同样的事情 - 设置startIndex
和primitiveCount
,这样粒子就会在它们死后停止绘制。
现在我必须承认,我已经掩盖了所涉及的数学和关于使用粒子的四边形的一些细节 - 但它不应该太难以弄清楚。 理解的基本原则是您将顶点/索引缓冲区视为粒子的循环缓冲区。
循环缓冲区的一个缺点是,当您停止发射粒子时,除非您在当前时间是粒子生命周期的倍数时停止,否则您将最终得到跨越缓冲区末端的活动粒子集中间的间隙 - 因此需要两次绘制调用(稍微慢一点)。为了避免这种情况,你可以等到停止之前的时间 - 对于大多数系统来说这应该没问题,但对某些人来说可能看起来很奇怪(例如:需要立即停止的“慢速”粒子系统)。
这种方法的另一个缺点是颗粒必须以恒定的速率释放 - 尽管这对于粒子系统来说通常是非常典型的(显然这是每个系统并且速率是可调节的)。通过稍微调整,爆炸效果(一次释放所有粒子)应该是可能的。
所有这一切:如果可能的话,使用现有的粒子库可能是值得的。