6

是时候讨论另一个 XNA 问题了。不过,这一次纯粹是从技术设计的角度来看。

我的情况是这样的:我创建了一个基于 GPU 计算的粒子引擎,虽然远未完成,但它确实有效。我的 GPU 可以轻松处理 10k 个粒子而不会出汗,如果我可以添加更多粒子,我不会感到惊讶。

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

粒子的创建仍由 CPU 调用完成,例如:

  • 方法想要创建粒子并进行调用。
  • 四边形以顶点的形式创建并存储在缓冲区中
  • 缓冲区插入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);

我的想法是,也许我不应该经常创建粒子,也许有办法让 GPU 创造一切,或者我只是不知道你是怎么做这些东西的。;)

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

所以我在这里发帖希望你知道应该如何设计一个好的粒子引擎,如果我在某个地方走错了路。

4

1 回答 1

4

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

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

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

很酷的是你只需要这样做一次——你可以为你的所有粒子系统重用相同的顶点缓冲区和索引缓冲区(假设它们对于你最大的粒子系统来说足够大)。

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

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

然后,该顶点着色器(再次类似于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 个。)

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

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

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

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

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

于 2010-06-29T05:27:42.980 回答