基于粒子的表面侵蚀模拟


注意:该项目的完整源代码以及有关其使用和阅读的说明,可以在Github [ here ] 上找到

我脱离了硕士论文,开始从事我长期以来一直坚持的工作:为我的Territory项目改进了地形生成。一种简单的实现方法是水力侵蚀,这就是我创建它的原因!

对于一天的软件难题,它工作得很好,并且事实证明它并不像我预期的那么复杂。结果快速生成,具有物理意义,并且看起来很棒。

在本文中,我将讨论基于粒子的方形网格水力侵蚀系统的简单C ++实现。我将解释实现基础的所有物理依据,并讨论数学。该代码非常简单(关于腐蚀的数学仅约20行)并且可以快速实施,因此,我建议所有想要提高地形逼真度的人使用它。

使用简化版本的Homebrew OpenGl Engine渲染结果,我对其进行了修改以将2D点数组渲染为高度图。如果您有兴趣学习C ++中的OpenGL,则简化版本的引擎更容易理解。

实作


受许多水力侵蚀源的启发,我决定使用颗粒是最合逻辑的。

基于粒子的腐蚀非常简单:

  • 我们在表面上的任意点创建一个粒子。
  • 它使用标准的经典力学在表面上移动/滑动(我们将在下面讨论它们)
  • 我们进行物质/沉积物在表面和颗粒之间的转移(这也将在下面解释)
  • 我们蒸发了一部分颗粒
  • 如果粒子在地图之外或太小,则将其销毁
  • 用所需数量的粒子重复该过程。

注意:系统参数在相应部分中说明。最重要的是时间步长因子dt,它按比例缩放所有参数。这使我们能够在不改变参数相对范围的情况下增加仿真频率(以增加噪声为代价)。可以在下面的代码中看到。

粒子


为此,我创建了一个简单的粒子结构,其中包含我需要的所有属性:

struct Particle{
  //Construct particle at position _pos
  Particle(glm::vec2 _pos){ pos = _pos; }

  glm::vec2 pos;
  glm::vec2 speed = glm::vec2(0.0); //Initialize to 0

  float volume = 1.0;   //Total particle volume
  float sediment = 0.0; //Fraction of volume that is sediment!
};

注意:如果您不熟悉GLM库:我用它来执行向量操作。

粒子具有确定其移动方式的位置和速度。此外,它还有一个体积和一个分数,该体积决定了沉积岩有多少体积。

机芯:经典机械


使用经典力学模拟表面上的粒子运动简而言之,粒子的位置x由速度v改变,由加速度a改变



注意:粗体字母表示该值是向量。

我们还知道力等于质量乘以加速度:


粒子由于重力而经历向下的加速度,但是它在表面上,因此无法进行向下的加速度。因此,替代于此,粒子受到沿表面指向并与表面法线成比例的力F。

因此,可以说加速度a与表面n的法线向量除以粒子的质量成正比


其中k是比例常数,m是粒子质量。如果质量等于体积乘以密度,则可以使用经典力学获得完整的粒子运动系统:

//... particle "drop" was spawned above at random position

glm::ivec2 ipos = drop.pos; //Floored Droplet Initial Position
glm::vec3 n = surfaceNormal(ipos.x, ipos.y);  //Surface Normal

//Accelerate particle using classical mechanics
drop.speed += dt*glm::vec2(n.x, n.z)/(drop.volume*density);
drop.pos   += dt*drop.speed;
drop.speed *= (1.0-dt*friction);  //Friction Factor

//...

注意:粒子运动后的速度会因摩擦矢量而降低。请注意,此处包括了时间步长因子。粒子具有自己的惯性,与它们的密度成比例,这是因为我们使用加速度模拟了它们的运动。

沉积岩形成过程:传质


沉积岩的形成过程实际上是通过沉积岩从地球到颗粒的转移而发生的,反之亦然,这是在颗粒所在的位置(“ 质量转移 ”)。

在化学技术中,通常使用传质系数来描述两相(在这种情况下是地球表面和液滴)之间的传质(即,质量/沉积物随时间的变化)

传质与浓度c和平衡浓度c_eq之间的差成正比:


其中k是比例常数(传质系数)。平衡浓度与实际浓度之间的这种差异通常称为“驱动力”。

如果平衡浓度高于当前浓度,则颗粒会吸收沉积岩。如果更低,则将其丢失。如果它们相等,则不会发生任何变化。

传质系数可以通过多种方式解释:

  • 作为相之间转换的频率(此处为“沉积速率”)
  • 液滴浓度趋于平衡浓度的速率

该系统完全取决于确定平衡浓度。根据定义,该系统将展示沉积岩沉积的不同动力学。在我的实现中,如果我们向下移动并且我们移动得更快,则平衡浓度会更高,并且它与粒子的体积成比例:

//...

//Compute Equilibrium Sediment Content
float c_eq = drop.volume*glm::length(drop.speed)*(heightmap[ipos.x][ipos.y]-heightmap[(int)drop.pos.x][(int)drop.pos.y]);

if(c_eq < 0.0) c_eq = 0.0;

//Compute Capacity Difference ("Driving Force")
float cdiff = c_eq - drop.sediment;

//Perform the Mass Transfer!
drop.sediment += dt*depositionRate*cdiff;
heightmap[ipos.x][ipos.y] -= dt*drop.volume*depositionRate*cdiff;

//...

注意:颗粒内部浓度的变化完全由传质方程描述。高度图的变化还乘以粒子的体积,因为我们不是按比例改变它的浓度,而是改变质量(浓度乘以体积)。

其他方面


高度图由带有随机种子的多层Perlin噪声初始化。

在每个时间步结束时,根据蒸发速率,粒子损失的质量很小:

//...

drop.volume *= (1.0-dt*evapRate);

//...

对于在随机位置创建并单独模拟的数千个粒子重复此过程(在我的情况下,计算在CPU中顺序执行)。

我的默认实现包括一组很好的参数。经过20万个粒子后,侵蚀看起来非常好。

结果


腐蚀过程的完成代码约为20行,没有注释。

这是对10个样本的“前后”比较。模拟在高程上生成了非常漂亮的山脊,沉积岩石沉积在一些山脊的侧面,从而形成了美丽的高原。


十个前后比较的选择。我编写的着色器在输入处接收两种颜色。我还实现了阴影贴图,与距离有关的雾和Phong阴影。

这里还有十个(不同的)样本,它们的结果如下:


还有十个样本结果。即使某些形式看起来相似,它们也与之前的有所不同。

通过在地图初始化期间选择不同的种子,可以创建受控的不同结果。

模拟时间


模拟时间与颗粒的寿命和模拟颗粒的数量直接相关。

有几个因素影响粒子的寿命,并且每个粒子的寿命可能会发生很大变化,因为它们是在随机的位置产生的:

  • 摩擦和惯性:粒子速度
  • 网格大小:粒子从地图上掉落的概率。
  • 蒸发速率:颗粒消光速率

使用默认参数时,单个粒子的仿真需要10-100毫秒,而对于200,000个粒子的仿真则需要10-20秒钟。

您可以通过增加时间步长来增加侵蚀程度并减少粒子的寿命,而无需更改模拟粒子的数量。这会使模拟更加“嘈杂”,并且如果时间步长太大,则模拟可能会失败。

引擎中的模拟本身会导致重新创建用于渲染的曲面网格而产生额外费用。

您可以通过减少重新创建网格的成本或提高单个粒子一步步的速度来优化代码。

为未来而努力


不久,我会将这段代码插入我的Territory项目中,作为在高程生成器中生成高度图的基础。

我已经编写了一个框架,用于简化气候系统中流体动力学的仿真,但是尚未发布。该系统从高度图获取气候模式。这将是以后发布有关物理精确过程气候系统的主题!

使用模拟的气候系统,将来可能会从分布中(例如,在下雨的地方)采样颗粒,而不是将它们均匀分布在地图上。理想情况下,它们将与浮雕的地质和气候相结合。

此外,在添加不同类型的地质构造之后,石头的溶解度也可能不同。这将直接影响传质系数(沉积速率),从而产生不同的腐蚀速率。

该系统无法模拟真实的流体和河流流量,但可以潜在地适应此类任务。我会考虑一下,也许将来我会发布该职位的续篇。

All Articles