实例化数据管理
本文关键字:数据管理 实例化 | 更新日期: 2023-09-27 18:13:02
我试图用实例化来绘制几千个粒子。它可以工作,而且速度很快,但是我有一个瓶颈使整个程序变慢。
我的Particle
类类似于:
public class Particle
{
public Vector2 Position;
//More data not used for drawing
//....
}
现在在我的DrawLoop()
中,我得到了这样的内容:
Vector2[] instanceData = new Vector2[numParticles];
public void Draw()
{
for(int i = 0; i < numParticles; ++i)
instanceData[i] = Particles[i].Position; //THAT'S the slow part
instanceBuffer.SetData(instanceData);
//Now draw VertexBuffer using instancing
//...
}
我试过使用Parallel.For
,但它没有加快速度,因为我有8000个粒子。此外,我还查看了MSDN的particlesystem
示例。但是他们的Particle
结构体只包含绘制粒子的数据,并且位置是在着色器中计算的。然而,我需要一些算法的额外数据。
我想不出一个类的设计,所以我不需要每一帧都给数组分配粒子的位置
由于这个问题最终是由所使用的数据结构引起的,所以让我为您提供一种常见的替代链表的方案,用于此类场景。
链表通常不是存储粒子的好主意,原因有两个:第一,你不能有效地随机访问它们,正如你在这里发现的;第二,链表的引用局部性很差。考虑到粒子系统的性能要求,后一点可能是致命的。
标准列表具有更好的引用局部性,但正如你所发现的,添加和删除项目可能很慢,这是你在粒子引擎中经常做的事情。
我们可以改进吗?
让我们从比列表更基本的东西开始,一个简单的数组。为了简单起见,让我们对引擎中的粒子数量进行硬性限制(我们稍后会纠正这个问题)。
private const Int32 ParticleCount = 8000;
private readonly Particle[] particles = new Particle[ParticleCount];
private Int32 activeParticles = 0;
假设你有空间,你总是可以在常量时间内将一个粒子添加到数组的末尾:
particles[activeParticles++] = newParticleData;
但是移除一个粒子是O(n),因为它之后的所有粒子都需要向下移动:
var indexOfRemovedParticle = 12;
particles.RemoveAt(indexOfRemovedParticle);
activeParticles--;
在常数时间内我们还能做什么?我们可以移动粒子:
particles[n] = particles[m];
我们可以用它来提高我们的性能吗?
是的!将remove操作更改为move操作,则O(n)变为O(1):
var indexOfRemovedParticle = 12;
var temp = particles[indexOfRemovedParticle];
particles[indexOfRemovedParticles] = particles[activeParticles - 1];
particles[activeParticles - 1] = temp;
activeParticles--;
我们划分数组:开始的所有粒子都是活动的,最后的所有粒子都是不活动的。因此,要删除一个粒子,我们所要做的就是将它与最后一个活动粒子交换,然后减少活动粒子的数量。
(注意,您需要在要删除的粒子数组内的索引。如果你要搜索这个,你最终会回到O(n)时间;然而,由于粒子通常的工作流程是"循环遍历整个列表,更新每个粒子,如果它是死的,就从列表中删除它",你通常会得到"free"的死粒子索引。
现在,这都是假设一个固定数量的粒子,但如果你需要更多的灵活性,你可以用同样的方式解决这个问题List<T>
类:每当你用完空间,只需分配一个更大的数组,并复制所有的东西到它。
该数据结构提供快速插入和删除、快速随机访问和良好的引用局部性。后者可以通过将Particle
类制作成一个结构来进一步改进,这样所有的粒子数据将连续存储在内存中。