65.9K
CodeProject 正在变化。 阅读更多。
Home

灵活粒子系统 - 更新器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (2投票s)

2014 年 6 月 8 日

CPOL

3分钟阅读

viewsIcon

13222

灵活粒子系统 - 更新器

在上一个粒子文章中,介绍了粒子生成系统。 但是,在创建新粒子之后,我们需要一种更新其参数的方法。 这次,我们将看看更新器- 它们是实际使事物移动和存在的类。

 

系列文章

引言

更新器也遵循 SRP 原则。 它们仅用于更新粒子的参数,并最终确定粒子是否仍然存在。 我们还可以进一步创建“杀手” - 它们会杀死粒子,但这可能是一个过于夸张的设计。

要点位于此处: fenbf / BasicParticleUpdaters

更新器接口

class ParticleUpdater
{
public:
    ParticleUpdater() { }
    virtual ~ParticleUpdater() { }

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

更新器获取增量时间和所有粒子数据。 它迭代遍历存活的粒子并执行一些操作。 该类非常“宽泛”,并提供了许多可能性。 有人甚至可能会指出,它提供了太多选项。 但是,目前我认为我们不应该限制此行为。

理想情况下,更新器应仅关注一组参数。 例如,EulerUpdaterColorUpdater

粒子更新器的实现

让我们看看 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];
}

很简单! 与生成器一样,我们可以混合使用不同的更新器来创建所需的效果。 在我旧的粒子系统中,我通常会有一个巨大的“更新器”(尽管整个系统完全不同)。 然后,当我想稍微修改一下效果时,我需要一遍又一遍地复制和粘贴通用代码。 这绝对不是最佳模式! 你可以把它当成一个 反模式 :)

其他 更新器

  • FloorUpdater - 可以使粒子从地板上弹起。
  • AttractorUpdater - 重力系统中的吸引子。
  • BasicColorUpdater - 根据时间和最小和最大颜色生成当前粒子颜色。
  • PosColorUpdater - 当前颜色来自位置。
  • VelColorUpdater - 当前颜色来自速度。
  • BasicTimeUpdater - 测量粒子的寿命。 如果时间到了,它会杀死粒子。

更新器组合示例

对于“地板效果”,我使用以下代码

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 秒开始

<iframe width="560" height="315" src="//www.youtube.com/embed/Zl7FWFsIJqA" frameborder="0" allowfullscreen></iframe>

缓存使用

混合使用不同的更新器当然是一件很棒的事情。 但是请注意,它也非常高效。 由于我们使用 SOA 容器,因此每个更新器都以一种智能的方式使用缓存。

例如,ColorUpdater 仅使用三个数组:currentColorstartColorendColor。 在计算期间,处理器缓存将仅填充这三个数组。 请记住,CPU 不会从内存中读取单个字节 - 它会读取整个缓存行 - 通常为 64 字节。

另一方面,如果我们有 AOS 容器,那么每个粒子都会变得“巨大” - 一个对象包含所有参数。 颜色更新器将仅使用三个字段。 因此,总而言之,缓存的使用效率将非常低,因为它必须存储未参与更新过程的字段。

看看这里

Three arrays of params

和这里

Single particle array

在第二个选项中,缓存还存储了更新过程中未使用的成员。

问题:当然,我们的解决方案并不理想! 有时,你可能有一些使用粒子所有参数的高级效果。 例如,所有参数都用于计算最终颜色。 在这种情况下,缓存将尝试加载所有参数(来自 AOS),并且性能可能会下降... 但是稍后当我们进入优化部分时,我将对此进行描述。

请分享对这种设计的任何疑问!

下一步

我们拥有用于粒子创建,更新和存储的所有系统……但是渲染呢? 下次,我将描述当前但实际上很简单,用于粒子的渲染系统。

© . All rights reserved.