Search code examples
kotlinnoiseperlin-noise

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


For educational purposes I want to implement the 1-dimensional Perlin Noise algorithm in Kotlin. I familiarized myself with the algorithm here and here.

I think I understood the basic concept, however my implementation can return values greater than 1. I expect the result of the call perlin(x) to be in the range 0 to 1. I can't figure out where I'm mistaken, so maybe someone can point me in the right direction. For simplicity I use simple linear interpolation instead of smoothstep or other advanced techniques for now.

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)
    }
}

For example if you would use these random generated noises

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

You would end up with an image like this:

Generated 1D Perlin Noise

where the green line is the calculated Perlin Noise of 8 octaves with a persistence of 0.5. As you can see the sum of all octaves at x=0 for example is greater than 1. (The blue line being the first octave noise(x) and the orange one being the second octave 0.5 * noise(2x)).

What am I doing wrong?

Thanks in advance.

Note: I'm aware that the Simplex Noise algorithm is the successor of Perlin Noise, however for educational purposes I want to implement Perlin Noise first. I'm also aware that my boundary should be set to something in the magnitude of 256 but for simplicity I just used 10 for now.


Solution

  • I've been digging around and found this article which introduces a value to normalize the results returned by Perlin(x). Essentially the amplitudes are summed up and the total is divided by this value. This seems to make sense since we could have "bad luck" and have a y-value of 1.0 in the first octave, followed by a 0.5 in the next, etc. So dividing by the sum of the amplitudes (1.5 in this case with 2 octaves) seems reasonable to keep the values in the range 0 - 1.

    However, I'm unsure if this is the preferred way since none of the other resource uses this technique.

    The modified code would look like this:

        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
        }