为什么这个一维柏林噪声发生器 Return 值 > 1?

Why does this 1-dimensional Perlin Noise Generator Return Values > 1?

出于教育目的,我想在 Kotlin 中实现一维 Perlin Noise 算法。我熟悉了算法 here and here.

我想我理解了基本概念,但是 我的实现可以 return 大于 1 的值。我希望调用 perlin(x) 的结果在 0 到 1 的范围内。我不知道我错在哪里,所以也许有人可以指出我正确的方向。为简单起见,我现在使用简单的线性插值代替 smoothstep 或其他高级技术。

class PerlinNoiseGenerator(seed: Int, private val boundary: Int = 10) {
    private var random = Random(seed)
    private val noise = DoubleArray(boundary) {
        random.nextDouble()
    }

    fun perlin(x: Double, persistence: Double = 0.5, numberOfOctaves: Int = 8): Double {
        var total = 0.0
        for (i in 0 until numberOfOctaves) {
            val amplitude = persistence.pow(i) // height of the crests
            val frequency = 2.0.pow(i) // number of crests per unit distance
            val octave = amplitude * noise(x * frequency)
            total += octave
        }
        return total
    }

    private fun noise(t: Double): Double {
        val x = t.toInt()
        val x0 = x % boundary
        val x1 = if (x0 == boundary - 1) 0 else x0 + 1
        val between = t - x
        
        val y0 = noise[x0]
        val y1 = noise[x1]
        return lerp(y0, y1, between)
    }

    private fun lerp(a: Double, b: Double, alpha: Double): Double {
        return a + alpha * (b - a)
    }
}

例如,如果您要使用这些随机生成的噪音

private val noise = doubleArrayOf(0.77, 0.02, 0.63, 0.74, 0.49, 0.22, 0.19, 0.76, 0.16, 0.08)

你最终会得到这样的图像:

其中绿线是 8 个八度的计算 Perlin Noise,持久性为 0.5。如您所见,x=0 处所有八度音阶的总和大于 1。(蓝线是第一个八度音阶 noise(x),橙色线是第二个八度音阶 0.5 * noise(2x))。

我做错了什么?

提前致谢。

注意:我知道 Simplex Noise 算法是 Perlin Noise 的继承者,但是出于教育目的我想实现 Perlin Noise 首先。我也知道我的边界应该设置为 256 的大小,但为了简单起见,我现在只使用 10。

我一直在挖掘并发现 this article 引入了一个值来规范化 Perlin(x) 返回的结果。本质上,将振幅相加,然后将总数除以该值。这似乎是有道理的,因为我们可能会“运气不好”并且在第一个八度音阶中的 y 值为 1.0,然后在下一个八度音阶中为 0.5,等等。所以除以振幅之和(在这种情况下为 1.5 2 个八度)将值保持在 0 - 1 范围内似乎是合理的。

但是,我不确定这是否是首选方式,因为其他资源的 none 使用了这种技术。

修改后的代码如下所示:

    fun perlin(x: Double, persistence: Double = 0.5, numberOfOctaves: Int = 8): Double {
        var total = 0.0
        var amplitudeSum = 0.0 //used for normalizing results to 0.0 - 1.0
        for (i in 0 until numberOfOctaves) {
            val amplitude = persistence.pow(i) // height of the crests
            val frequency = 2.0.pow(i) // frequency (number of crests per unit distance) doubles per octave
            val octave = amplitude * noise(x * frequency)
            total += octave
            amplitudeSum += amplitude
        }
        return total / amplitudeSum
    }