如何制作一个更平滑的Perlin噪声生成器?

10
使用Perlin噪声生成器来制作地图的瓦片,噪声太过尖锐。它有许多高低起伏的地形,没有平坦的区域。它们看起来不像山脉、岛屿或湖泊;它们是随机的,有很多峰值。
def Noise(self, x):     # I wrote this noise function but it seems too random
    random.seed(x)
    number = random.random()
    if number < 0.5:
        final = 0 - number * 2
    elif number > 0.5:
        final = number * 2
    return final

 def Noise(self, x):     # I found this noise function on the internet
    x = (x<<13) ^ x
    return ( 1.0 - ( (x * (x * x * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0)

2D: 二维:
def Noise(self, x, y):     # I wrote this noise function but it seems too random
    n = x + y
    random.seed(n)
    number = random.random()
    if number < 0.5:
        final = 0 - number * 2
    elif number > 0.5:
        final = number * 2
    return final

def Noise(self, x, y):     # I found this noise function on the internet
    n = x + y * 57
    n = (n<<13) ^ n
    return ( 1.0 - ( (x * (x * x * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0)

你不需要使用Matplotlib或NumPy;我只是用它们来绘制图形以便更直观地展示结果。
import random
import matplotlib.pyplot as plt              # To make graphs
from mpl_toolkits.mplot3d import Axes3D      # To make 3D graphs
import numpy as np                           # To make graphs

class D():     # Base of classes D1 and D2
    def Cubic_Interpolate(self, v0, v1, v2, v3, x):
        P = (v3 - v2) - (v0 - v1)
        Q = (v0 - v1) - P
        R = v2 - v0
        S = v1
        return P * x**3 + Q * x**2 + R * x + S

class D1(D):
    def __init__(self, lenght, octaves):
        self.result = self.Perlin(lenght, octaves)

    def Noise(self, x):     # I wrote this noise function but it seems too random
        random.seed(x)
        number = random.random()
        if number < 0.5:
            final = 0 - number * 2
        elif number > 0.5:
            final = number * 2
        return final

    def Noise(self, x):     # I found this noise function on the internet
        x = (x<<13) ^ x
        return ( 1.0 - ( (x * (x * x * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0)

    def Perlin(self, lenght, octaves):
        result = []
        for x in range(lenght):
            value = 0
            for y in range(octaves):
                frequency = 2 ** y
                amplitude = 0.25 ** y
                value += self.Interpolate_Noise(x * frequency) * amplitude
            result.append(value)
            print(f"{x} / {lenght} ({x/lenght*100:.2f}%): {round(x/lenght*10) * '#'} {(10-round(x/lenght*10)) * ' '}. Remaining {lenght-x}.")     # I don't use `os.system('cls')` because it slow down the code.
        return result

    def Smooth_Noise(self, x):
        return self.Noise(x) / 2 + self.Noise(x-1) / 4 + self.Noise(x+1) / 4

    def Interpolate_Noise(self, x):
        round_x = round(x)
        frac_x  = x - round_x
        v0 = self.Smooth_Noise(round_x - 1)
        v1 = self.Smooth_Noise(round_x)
        v2 = self.Smooth_Noise(round_x + 1)
        v3 = self.Smooth_Noise(round_x + 2)
        return self.Cubic_Interpolate(v0, v1, v2, v3, frac_x)

    def graph(self, *args):
        plt.plot(np.array(self.result), '-', label = "Line")
        for x in args:
            plt.axhline(y=x, color='r', linestyle='-')
        plt.xlabel('X')
        plt.ylabel('Y')
        plt.title("Simple Plot")
        plt.legend()
        plt.show()

class D2(D):
    def __init__(self, lenght, octaves = 1):

        self.lenght_axes = round(lenght ** 0.5)
        self.lenght = self.lenght_axes ** 2

        self.result = self.Perlin(self.lenght, octaves)

    def Noise(self, x, y):     # I wrote this noise function but it seems too random
        n = x + y
        random.seed(n)
        number = random.random()
        if number < 0.5:
            final = 0 - number * 2
        elif number > 0.5:
            final = number * 2
        return final

    def Noise(self, x, y):     # I found this noise function on the internet
        n = x + y * 57
        n = (n<<13) ^ n
        return ( 1.0 - ( (x * (x * x * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0)

    def Smooth_Noise(self, x, y):
        corners = (self.Noise(x - 1, y - 1) + self.Noise(x + 1, y - 1) + self.Noise(x - 1, y + 1) + self.Noise(x + 1, y + 1) ) / 16
        sides   = (self.Noise(x - 1, y) + self.Noise(x + 1, y) + self.Noise(x, y - 1)  + self.Noise(x, y + 1) ) /  8
        center  =  self.Noise(x, y) / 4
        return corners + sides + center

    def Interpolate_Noise(self, x, y):

        round_x = round(x)
        frac_x  = x - round_x

        round_y = round(y)
        frac_y  = y - round_y

        v11 = self.Smooth_Noise(round_x - 1, round_y - 1)
        v12 = self.Smooth_Noise(round_x    , round_y - 1)
        v13 = self.Smooth_Noise(round_x + 1, round_y - 1)
        v14 = self.Smooth_Noise(round_x + 2, round_y - 1)
        i1 = self.Cubic_Interpolate(v11, v12, v13, v14, frac_x)

        v21 = self.Smooth_Noise(round_x - 1, round_y)
        v22 = self.Smooth_Noise(round_x    , round_y)
        v23 = self.Smooth_Noise(round_x + 1, round_y)
        v24 = self.Smooth_Noise(round_x + 2, round_y)
        i2 = self.Cubic_Interpolate(v21, v22, v23, v24, frac_x)

        v31 = self.Smooth_Noise(round_x - 1, round_y + 1)
        v32 = self.Smooth_Noise(round_x    , round_y + 1)
        v33 = self.Smooth_Noise(round_x + 1, round_y + 1)
        v34 = self.Smooth_Noise(round_x + 2, round_y + 1)
        i3 = self.Cubic_Interpolate(v31, v32, v33, v34, frac_x)

        v41 = self.Smooth_Noise(round_x - 1, round_y + 2)
        v42 = self.Smooth_Noise(round_x    , round_y + 2)
        v43 = self.Smooth_Noise(round_x + 1, round_y + 2)
        v44 = self.Smooth_Noise(round_x + 2, round_y + 2)
        i4 = self.Cubic_Interpolate(v41, v42, v43, v44, frac_x)

        return self.Cubic_Interpolate(i1, i2, i3, i4, frac_y)

    def Perlin(self, lenght, octaves):
        result = []
        for x in range(lenght):
            value = 0
            for y in range(octaves):
                frequency = 2 ** y
                amplitude = 0.25 ** y
                value += self.Interpolate_Noise(x * frequency, x * frequency) * amplitude
            result.append(value)
            print(f"{x} / {lenght} ({x/lenght*100:.2f}%): {round(x/lenght*10) * '#'} {(10-round(x/lenght*10)) * ' '}. Remaining {lenght-x}.")     # I don't use `os.system('cls')` because it slow down the code.
        return result

    def graph(self, color = 'viridis'):
        # Other colors: https://matplotlib.org/examples/color/colormaps_reference.html
        fig = plt.figure()
        Z = np.array(self.result).reshape(self.lenght_axes, self.lenght_axes)

        ax = fig.add_subplot(1, 2, 1, projection='3d')
        X = np.arange(self.lenght_axes)
        Y = np.arange(self.lenght_axes)
        X, Y = np.meshgrid(X, Y)
        d3 = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=color, linewidth=0, antialiased=False)
        fig.colorbar(d3)

        ax = fig.add_subplot(1, 2, 2)
        d2 = ax.imshow(Z, cmap=color, interpolation='none')
        fig.colorbar(d2)

        plt.show()

输出不适合用于地图。使用以下方式查看此输出:
test = D2(1000, 3)
test.graph()

enter image description here

也许在二维噪音中很难注意到,但在一维中更容易:
test = D1(1000, 3)
test.graph()

enter image description here

从互联网上获取的噪音函数具有稍小且不太频繁的峰值,但仍然太多。我正在寻找更加平滑的东西,就像这样:

enter image description here

或者这样:

enter image description here

我根据这个伪代码制作了这个。

Pikalek:

enter image description here

即使数值较低,它仍然有峰值,没有曲线或平滑/平坦的线条。

2
尽管标题如此,该教程并不涉及实际的Perlin噪声(使用梯度),而是针对值噪声(可以产生类似的结果,但有一些关键差异)。 无论如何,可能还有其他问题,但我怀疑第一个问题是您的初始值为1000。如果尝试100或10会发生什么? - Pikalek
@Pikalek,我在最后添加了更多照片。所以...我不是在制作Perlin噪声吗?你觉得这个页面怎么样(https://www.lanshor.com/ruido-perlin/)?虽然它是用西班牙语编写的,但它有照片。那不也是Perlin噪声吗? - Ender Look
1
从技术上讲,这两个教程都没有使用Perlin噪声,但如果你得到了所需的输出类型,就不用担心。值噪声要简单得多,足以满足某些应用程序的需求。但是,如果值噪声对您不起作用,您可以阅读Adrian的教程或查找Simplex噪声。 - Pikalek
3个回答

7
我在你的代码中发现了以下错误:
  • 您需要将Interpolate_Noise参数乘以一个系数,以“缩放”地图(例如,将x0.01相乘)。如果在1D情况下这样做,您会发现生成的函数已经好多了。
  • 将八度数从3增加到更大的值(3个八度不会产生太多细节)
  • 使用振幅0.5 ^ octave,而不是0.25 ^ octave(但您可以玩弄这个参数,所以0.25并不本质上是不好的,但它不会提供太多细节)
  • 对于2D情况,您需要有2个外部循环(一个用于水平,一个用于垂直。当然,您仍然需要拥有八度循环)。因此,您需要正确地使用水平和垂直位置来“索引”噪声,而不仅仅是xx
  • 完全删除平滑。Perlin噪声不需要它。
  • 2D噪声函数存在错误:它在返回表达式中使用x而不是n
  • 在立方插值中,您使用round而不是math.floor

这是我的答案,其中包含一个简单的(C ++)类似于Perlin(它不是真正的perlin)噪声的实现:https://dev59.com/taPia4cB1Zd3GeqPy3US#45121786


  1. 好的,但这样只会使所有峰值都降低吗?我的意思是,代替像[0.2,0.7,0.6,0.4,0.5]这样的东西,它将是[0.002,0.007,0.006,0.004,0.005],但每个值之间的相对大小将保持不变。
  2. 我知道,我不确定为什么我选择了一个低值来举例。
  3. 好的。
  4. 谢谢,我没有注意到那个。看看我编辑的最后一部分。
  5. 但如果我去掉平滑处理,它会产生更大的峰值,你为什么这么说?
- Ender Look
  1. 我的意思是 self.Interpolate_Noise(x * frequency * 0.01),而不是 self.Interpolate_Noise(x * frequency) * 0.01
  2. 也许你根本不需要进行重新整形?你只需要显示这个二维数组。
  3. 如果你正确地完成了1),你会发现不需要平滑。当然,你可以对其进行平滑处理,但Perlin噪声的全部意义在于不需要平滑。如果你想要更少细节的噪声,那就使用更少的八度。
- geza
我已经再次编辑了我的问题以展示新的结果。看起来更好,但我不确定为什么结果总是产生垂直线条。请看一下,谢谢。 - Ender Look
谢谢你!你已经修复了代码!我已经编辑了我的答案,最后一张图片展示了图表的样子。现在好多了。谢谢。 - Ender Look

1
你需要实现一个更加激进的平滑算法。最好的方法是使用矩阵卷积。它的工作原理是,你有一个被称为“核”的矩阵,应用于网格中的每个单元格,创建一个新的、转换后的数据集。一个例子核可能是:
0.1 0.1 0.1
0.1 0.2 0.1
0.1 0.1 0.1

假设你有这样一个网格:

2 4 1 3 5
3 5 1 2 3
4 9 2 1 2
3 4 9 5 2
1 1 3 6 7

假设我们想将核应用于中心的2,我们需要按照核的形状切出网格,并将每个单元格与其相应的核单元格相乘:

. . . . .
. 5 1 2 .       0.1 0.1 0.1       0.5 0.1 0.2
. 9 2 1 .   x   0.1 0.2 0.1   =   0.9 0.4 0.1
. 4 9 5 .       0.1 0.1 0.1       0.4 0.9 0.5
. . . . .

然后我们可以将所有这些值相加,得到单元格的新值:0.5+0.1+0.2+0.9+0.4+0.1+0.4+0.9+0.5= 4,并在我们的新数据集中填充该空间:

? ? ? ? ?
? ? ? ? ?
? ? 4 ? ?
? ? ? ? ?
? ? ? ? ?

正如您所想象的那样,我们必须为网格中的每个其他空间重复此操作,以填充我们的新数据集。完成后,我们丢弃旧数据并使用此新网格作为我们的数据集。
优点是您可以使用大型内核执行非常大的平滑操作。例如,您可以使用5x5或9x9大小的内核,这将使您的噪声更加平滑。
还有一点需要注意,内核需要构建,使得所有单元格的总和为1,否则您将无法保持质量守恒(可以这么说;例如,如果总和大于1,则您的峰值会趋向于更高,数据的平均值也会更高)。一个5x5矩阵的例子如下:
0.010 0.024 0.050 0.024 0.010
0.024 0.050 0.062 0.050 0.024
0.050 0.062 0.120 0.062 0.050
0.024 0.050 0.062 0.050 0.024
0.010 0.024 0.050 0.024 0.010

确保矩阵质量的一种方法是将其标准化;将每个单元格除以所有单元格的总和。例如:

1  4  16 4  1                    0.002808989    0.011235955 0.04494382  0.011235955 0.002808989
4  16 32 16 4                    0.011235955    0.04494382  0.08988764  0.04494382  0.011235955
16 32 64 32 16  (sum = 356) -->  0.04494382     0.08988764  0.179775281 0.08988764  0.04494382
4  16 32 16 4                    0.011235955    0.04494382  0.08988764  0.04494382  0.011235955
1  4  16 4  1                    0.002808989    0.011235955 0.04494382  0.011235955 0.002808989

0

你可以使用简单的算法使噪声更加平滑 -> f(n)=(f(n-1) + f(n+1))/2

不知道为什么,但它确实有效。

smoother noise


关于为什么,这是将 f(n) 设置为其旁边点的平均值。 - Puff

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接