XNA - 同时创建大量粒子

6

又到了提出XNA问题的时候了。这次,从技术设计的角度出发。

我的情况是这样的:我创建了一个基于GPU计算的粒子引擎,尽管远未完成,但它可以运行。我的GPU轻松处理了10000个粒子而毫不费力,如果我添加更多粒子,我也不会感到惊讶。

我的问题是:每当有大量粒子同时创建时,我的帧率就会非常低。为什么?因为有很多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创建所有东西,或者可能是因为我不知道你是如何完成这些工作的。 ;)

编辑:如果我不经常创建粒子,仍然可以使效果看起来好吗?

所以我在这里发帖,希望您知道一个好的粒子引擎应该如何设计,如果我某处走错了路,也请告诉我。

1个回答

4

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

如果我要创建一个最大CPU效率的粒子系统,我会像这样在顶点和索引缓冲区中预先创建(仅为举例而选择100个):

  • 制作包含四个顶点的四边形(每个粒子不是六个,而是四个)的顶点缓冲区
  • 使用自定义顶点格式,它可以存储“时间偏移”值以及“初始速度”值(类似于XNA粒子3D示例
  • 设置时间值,使得每个粒子的时间偏移比上一个少1/100(因此通过缓冲区,偏移范围从1.0到0.01)
  • 随机设置初始速度
  • 使用索引缓冲区,使用每个粒子的四个顶点给出所需的两个三角形
很酷的是,您只需要执行一次此操作 - 您可以为所有粒子系统重复使用相同的顶点缓冲区和索引缓冲区(前提是它们足够大以容纳您最大的粒子系统)。
然后,我将拥有一个顶点着色器,它将采用以下输入:
- 每个顶点: - 时间偏移量 - 初始速度 - 着色器参数: - 当前时间 - 粒子寿命(也是粒子时间环绕值和正在使用的缓冲区中粒子的分数) - 粒子系统位置/旋转/缩放(世界矩阵) - 任何其他有趣的输入,例如:粒子大小、重力、风等 - 时间刻度(以获得实时时间,因此速度和其他物理计算有意义)
该顶点着色器(就像XNA Particle 3D Sample一样)可以根据其初始速度和该粒子在模拟中存在的时间确定粒子顶点的位置。
每个粒子的时间将是(伪代码):
time = (currentTime + timeOffset) % particleLifetime;

换句话说,随着时间的推移,由于偏移量,粒子将以恒定速率释放。每当粒子在time = particleLifetime(或者是在1.0?浮点模数很令人困惑)死亡时,时间会循环回到time = 0.0,这样粒子就重新进入动画。
然后,在绘制粒子时,我会设置缓冲区、着色器和着色器参数,并调用DrawIndexedPrimitives。现在聪明的地方来了:我会设置startIndexprimitiveCount,使得没有粒子在中途开始动画。当粒子系统第一次启动时,我会绘制1个粒子(2个基元),当该粒子即将死亡时,我会绘制所有100个粒子,其中第100个粒子刚开始。
然后,一会儿后,第一个粒子的计时器会循环并使其成为第101个粒子。
如果我只想在系统中有50个粒子,我只需将粒子寿命设置为0.5,并且只绘制顶点/索引缓冲区中的前100个粒子中的前50个。
当关闭粒子系统时,只需按相反的方式进行 - 设置 startIndex primitiveCount ,以便在粒子死亡后停止绘制。
现在我必须承认,我已经忽略了涉及的数学和一些关于使用四边形进行粒子处理的细节 - 但这不应该太难理解。 要理解的基本原则是,您将自己的顶点/索引缓冲区视为粒子的循环缓冲区。 循环缓冲区的一个缺点是,当您停止发射粒子时,除非当前时间是粒子寿命的倍数,否则您将得到跨越缓冲区两端并在中间产生间隙的活动粒子集 - 这需要两个绘制调用(速度有点慢)。为避免这种情况,您可以等待适当的时间再停止 - 对于大多数系统来说,这应该没问题,但对某些系统(例如需要立即停止的“缓慢”粒子系统)可能看起来很奇怪。
这种方法的另一个缺点是粒子必须以恒定速率释放 - 虽然这通常对于粒子系统来说是相当典型的(显然,这是每个系统的情况,并且速率是可调节的)。通过一些微调,爆炸效果(所有粒子同时释放)应该是可能的。
总之:如果可能的话,使用现有的粒子库可能是值得的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接