[译]05.打造灵活高效的粒子系统——更新器

05_1

前面的几篇文章介绍了粒子系统的各个方面,当粒子创建出来之后,我们需要通过一种方法来更新粒子的属性。这次我们将介绍updater(更新器),这个类真正的让粒子动起来。

转载请注明[本文链接][原文链接],谢谢!

引言

Updater同样也遵守单一职责原则,通常updater负责更新粒子的属性和决定粒子的生死。当然我们也可以单独做个类,只负责删除粒子,这样感觉有点过度设计。

Gist地址在这: fenbf / BasicParticleUpdaters

Update接口

class ParticleUpdater

{

public:

ParticleUpdater() { }

virtual ~ParticleUpdater() { }

 

virtual void update(double dt, ParticleData *p) = 0;

};

Updater有增量时间值和粒子的数据,遍历所有激活的粒子做一些事情。这个类很“宽泛”。因为粒子的数据全传进来了,它可以做很多事,也许有人会觉得这是个问题,但是目前为止我觉得还不能限制它的行为。

理想情况下updater应该只更新粒子的一部分属性,比如EulerUpdater(更新位置,速度和加速度)和ColorUpdater(更新颜色)。

粒子Updater的实现

先看下EulerUpdater,这里是BoxPosGen的示例:

class EulerUpdater : public ParticleUpdater

{

public:

glm::vec4 m_globalAcceleration{ 0.0f };

public:

virtual void update(double dt, ParticleData *p) override;

};

 

void EulerUpdater::update(double dt, ParticleData *p)

{

const glm::vec4 globalA{ dt * m_globalAcceleration.x,

dt * m_globalAcceleration.y,

dt * m_globalAcceleration.z,

0.0 };

const float localDT = (float)dt;

 

const unsigned int endId = p->m_countAlive;

for (size_t i = 0; i < endId; ++i)

p->m_acc[i] += globalA;

 

for (size_t i = 0; i < endId; ++i)

p->m_vel[i] += localDT * p->m_acc[i];

 

for (size_t i = 0; i < endId; ++i)

p->m_pos[i] += localDT * p->m_vel[i];

}

很简单哈!跟 generator类似,我们可以组合各种不同的updater来达到需要的效果。我之前做的粒子系统不是这种做法,而是有巨复杂的“updater”,本来只需要稍微修改一下的效果,我不得不把相同的代码拷一遍,那么做肯定不好,你甚至可以认为这是反模式:)

其他updaters:

  • FloorUpdater- 虚拟一个地面,落在地面的粒子将反弹。
  • AttractorUpdater- 引力模拟,比如重力。
  • BasicColorUpdater- 基于时间将粒子从一种颜色变成另一种。
  • PosColorUpdater- 根据位置计算颜色。
  • VelColorUpdater- 根据速度计算颜色。
  • BasicTimeUpdater- 计算粒子的存活时间,当时间结束时删除粒子。

Updater结构示例

用以下代码实现“地面效果”:

auto timeUpdater = std::make_shared<particles::updaters::BasicTimeUpdater>();

m_system->addUpdater(timeUpdater);

 

auto colorUpdater = std::make_shared<particles::updaters::BasicColorUpdater>();

m_system->addUpdater(colorUpdater);

 

m_eulerUpdater = std::make_shared<particles::updaters::EulerUpdater>();

m_eulerUpdater->m_globalAcceleration = glm::vec4{ 0.0, -15.0, 0.0, 0.0 };

m_system->addUpdater(m_eulerUpdater);

 

m_floorUpdater = std::make_shared<particles::updaters::FloorUpdater>();

m_system->addUpdater(m_floorUpdater);

可以在这里看一个视频,从第39秒开始。

缓存使用

混合各种updater是很好的事,同时运行起来也是很高效的。使用SoA容器可以让updater使用缓存非常智能。

比如ColorUpdater只使用了3个数组,currentColor(当前颜色),startColor(开始颜色)和endColor(结束颜色),计算的时候,CPU缓存里只被填充了这三个数组。请记住CPU从内存读取数据的时候并不是一个字节一个字节的读,而是一块一块的读,一般一块是64字节。

如果我们使用AoS容器的话,因为一个粒子保存了所有的属性,在ColorUpdater执行的时候,大量无关的属性被加载到CPU缓存,导致缓存命中率降低,相比SoA的方式,CPU需要多次从内存加载数据,所以降低了性能。

05_2

05_3

第二张图表示CPU缓存中保存了其他的属性,而这些属性是本次updater过程根本不需要的。

问题:当然目前这个解决方案也是不完美的。有时你做一个复杂效果使用了所有的参数(假设所有的参数都用来计算最终的颜色),这种情况下CPU缓存会会尝试从AoS加载所有的属性,缓存里又放不下,所以会导致频繁切换,因此效率降低。这一点我们在优化部分里会详细讨论。

关于这个设计,如果有疑问请跟帖。

下一步

我们目前的粒子系统已经有了容器、创建和更新模块,但是渲染还没有。下一步我们将建立一个基础的渲染系统。

原文地址:http://www.bfilipek.com/2014/06/flexible-particle-system-updaters.html

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

You must enable javascript to see captcha here!