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:
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.
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
}