具有脊状分形噪声的程序地形

Procedural Terrain with ridged fractal noise

我实现了双线性插值白噪声,以便按程序生成地形。

我可以得到这样的结果:

我想实现脊状分形噪声以获得更逼真的地形,例如:

但是我找不到关于脊形分形噪声的好教程。你能告诉我怎么做吗?

脊状柏林噪声实际上很容易做到 - 您只需 ABS() 最终高度图或噪声层的某些子集(然后反转生成的高度图值,以确保脊出现在高值)。

示例:(具有三线性插值的基本柏林噪声,然后是整个高度场的 ABS 和 INVERT)。 (INVERT 表示 "multiply by -1"。)

我强烈建议尝试各种分形噪声层配置和基本 mathematical/logical 操作。

另一个例子:(两个不同的低频 perlin 噪声层使用逻辑 INTERSECTION / math MINIMUM 函数合并)

但是,对分形噪声算法的简单修改不会为您提供看起来 "flow downhill" 的那些细节(并使地形更加逼真和吸引人)。要实现这些,您需要添加某种侵蚀模拟,这是一个复杂得多的野兽(算法方面和 CPU 方面)。

关于这方面的一些信息,我推荐这两篇论文(你可以忽略 GPU 部分,算法在 CPU 上运行良好,尽管根据我的经验,模拟 1000x1000 需要一分钟左右px 图片):

  1. Mei, Xing, Decaudin, Philippe and Hu, Bao-Gang. Fast Hydraulic Erosion Simulation and Visualisation on GPU. 2007.
  2. Fast Hydraulic and Thermal Erosion on the GPU. Jákó, Balász. 2011. The 15th Central European Seminar on Computer Graphics.

编辑:

让我们澄清一下 "apply ABS on the height field" 的意思。

您只需在地图的每个像素中获取高度的数值并对其应用 ABS () 函数,丢弃其符号。

假定 perlin 噪声发生器生成 <-1,1> 范围内的值(或以 0 为中心的另一个范围)。预计约 50% 的像素值大于 0,约 50% 的像素值预计小于 0。

ABS 会导致在 0 所在的位置创建尖锐的脊线,因为所有 bilinear/trilinear 插值都会确保曾经有一个平滑的斜坡通过 0 值。

考虑这张图片:

它显示了两个 COS(x) 函数,其中一个是用 ABS 函数调制的(我添加了一个小偏移量以确保两条线分别可见)。现在想象紫色线垂直翻转 - 你最终会看到两座山峰之间有一个尖锐的山脊和一个山谷:)

为了产生物理上逼真的脊线,如前面的回答所述,修改噪声函数可能不是最好的方法。相反,如果您从分形表面开始,并对其应用合适的侵蚀函数,则可以相当轻松地创建具有物理逼真形状的山脊。

我最近在我的网站上按照这些思路写下了我的努力:https://fractal-landscapes.co.uk/maths.html

本质上,如果您的目标只是山脊,您可以修改热侵蚀方程以去除所有沉积(即进行 subtractive-only 侵蚀)。我包括了我用来侵蚀单个点的 C# 代码——它相当快,并且会在我的 PC 上用 250 次迭代在大约 10 秒内侵蚀我的 4096x4096 测试环境(尽管在 12 个内核上有一些并行化)。

摆弄位只是获取被侵蚀点的 8 个相邻点的有效方法 - 我的表示使用线性数组,所以 y-coordinates 是 pre-multiplied 的宽度景观(yMul 等...)。为简单起见,您可以将步幅视为 1。 r2demon 和位移只是角点的 1/sqrt(2) 的快速乘法。

var x0 = (x1 - stride) & mask;
var x2 = (x1 + stride) & mask;
indices[0] = x0 + yMul0;
indices[1] = x1 + yMul0;
indices[2] = x2 + yMul0;
indices[3] = x0 + yMul1;
indices[4] = x2 + yMul1;
indices[5] = x0 + yMul2;
indices[6] = x1 + yMul2;
indices[7] = x2 + yMul2;

for (int i = 0; i < actualLength; i++)
{
    elements[i] = thisSurface[indices[i]];
}

var height = thisSurface[x1 + yMul1];

//Differences in height.
//Corner differences multipled by 1/sqrt(2)
diffs[0] = ((height - elements[0]) * r2denom) >> 8;
diffs[1] = height - elements[1];
diffs[2] = ((height - elements[2]) * r2denom) >> 8;
diffs[3] = height - elements[3];
diffs[4] = height - elements[4];
diffs[5] = ((height - elements[5]) * r2denom) >> 8;
diffs[6] = height - elements[6];
diffs[7] = ((height - elements[7]) * r2denom) >> 8;

//Compare the differences to the talus threshold.
var max = 0;
var talusSum = 0;
var slopeSum = 0;
for (var i = 0; i < actualLength; i++)
{
    var diff = diffs[i];
    if (diff > 0)
    {
        if (diff > max)
        {
            max = diff;
        }

        talusSum += diff;
        if (diff > talusThreshold)
        {
            slopeSum += (diff - talusThreshold);
        }
    }
}

talusSum = talusSum / 2 - talusThreshold;

if (talusSum > 0)
{
    //Work out how much height to redistribute.
    var toMove = talusSum / 4;
    if (altitudeProportional)
    {
        toMove *= (height - minAltitude);
        toMove /= (maxAltitude - minAltitude);
    }

    newSurface[x1 + yMul1] -= toMove;
}