XNA - 同时创建大量粒子

Vec*_*vox 6 xna particle-engine

另一个XNA问题的时间.这一次纯粹是从技术设计的角度来看.

我的情况是这样的:我已经创建了一个基于GPU计算的粒子引擎,远非完整但它的工作原理.我的GPU可以轻松处理10k颗粒而不会出汗,如果我能添加更多的话,我也不会感到惊讶.

我的问题:每当我同时创建大量粒子时,我的帧率就讨厌我了.为什么?大量的CPU使用,即使我已将其最小化以包含几乎只有内存操作.

仍然通过CPU调用来完成粒子的创建,例如:

  • 方法想要创建粒子并进行调用.
  • Quad以顶点的形式创建并存储在缓冲区中
  • 缓冲区插入GPU,我的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);
Run Code Online (Sandbox Code Playgroud)

我的想法是,也许我不应该经常创建粒子,也许有一种方法让GPU创建所有东西,或者我可能只是不知道你是如何做这些东西的.;)

编辑:如果我不经常创建粒子,那么仍然使它看起来很好的解决方法是什么?

所以我发帖在这里,希望你知道如何设计一个好的粒子引擎,如果我在某个地方采取了错误的路线.

And*_*ell 4

没有办法让 GPU 创建所有内容(除非使用需要 SM4.0 的几何着色器)。

如果我要创建一个粒子系统以获得最大的 CPU 效率,我会在顶点和索引缓冲区中预先创建(仅为了举例而选择一个数字)100 个粒子,如下所示:

  • 制作一个包含四边形的顶点缓冲区(每个粒子四个顶点,而不是像您拥有的六个顶点)
  • 使用自定义顶点格式,可以存储“时间偏移”值以及“初始速度”值(类似于XNA 粒子 3D 示例
  • 设置时间值,使每个粒子的时间偏移量比上一个粒子小 1/100(因此通过缓冲区的偏移量范围为 1.0 到 0.01)。
  • 随机设置初始速度。
  • 使用索引缓冲区,通过每个粒子的四个顶点为您提供所需的两个三角形。

最酷的是,您只需要执行一次 - 您可以为所有粒子系统重复使用相同的顶点缓冲区和索引缓冲区(前提是它们对于最大的粒子系统来说足够大)。

然后我会有一个顶点着色器,它将接受以下输入:

  • 每个顶点:
    • 时间偏移
    • 初始速度
  • 着色器参数:
    • 当前时间
    • 粒子寿命(也是粒子时间环绕值,以及正在使用的缓冲区中的粒子分数)
    • 粒子系统位置/旋转/缩放(世界矩阵)
    • 您喜欢的任何其他有趣的输入,例如:颗粒大小、重力、风等
    • 时间尺度(为了获得实时时间,因此速度和其他物理计算才有意义)

然后,该顶点着色器(同样类似于XNA Particle 3D Sample)可以根据粒子的初始速度和该粒子处于模拟中的时间来确定粒子顶点的位置。

每个粒子的时间为(伪代码):

time = (currentTime + timeOffset) % particleLifetime;
Run Code Online (Sandbox Code Playgroud)

换句话说,随着时间的推移,粒子将以恒定的速率释放(由于偏移)。每当粒子在 处死亡time = particleLifetime (或者是在 1.0?浮点模数令人困惑)时,时间就会循环回到 处time = 0.0,以便粒子重新进入动画。

然后,当需要绘制粒子时,我将设置缓冲区、着色器和着色器参数,并调用DrawIndexedPrimitives. 现在这里有一个聪明的地方:我将设置startIndexprimitiveCount以便没有粒子在动画中开始。当粒子系统第一次启动时,我会绘制 1 个粒子(2 个基元),当该粒子即将消失时,我会绘制所有 100 个粒子,其中第 100 个粒子刚刚开始。

然后,片刻之后,第一个粒子的计时器将循环并使其成为第 101 个粒子。

(如果我的系统中只需要 50 个粒子,我只需将粒子寿命设置为 0.5,并且只在顶点/索引缓冲区中绘制 100 个粒子中的前 50 个。)

当需要关闭粒子系统时 - 只需反向执行相同的操作 - 设置startIndexprimitiveCount以便粒子在死亡后停止被绘制。

现在我必须承认我已经掩盖了所涉及的数学以及有关使用四边形粒子的一些细节 - 但它应该不难理解。要理解的基本原理是,将顶点/索引缓冲区视为粒子的循环缓冲区。

循环缓冲区的一个缺点是,当您停止发射粒子时,除非您在当前时间是粒子生命周期的倍数时停止,否则您最终将得到一组活跃的粒子横跨缓冲区的两端,其中有一个间隙中间 - 因此需要两次绘制调用(有点慢)。为了避免这种情况,您可以等到时间到了再停止 - 对于大多数系统来说这应该没问题,但对于某些系统来说可能看起来很奇怪(例如:需要立即停止的“慢”粒子系统)。

这种方法的另一个缺点是粒子必须以恒定的速率释放——尽管这对于粒子系统来说通常是非常典型的(显然这是每个系统的并且速率是可调的)。通过稍微调整,爆炸效果(所有粒子立即释放)应该是可能的。

话虽如此:如果可能的话,使用现有的粒子库可能是值得的。