#kotlin #noise #perlin-noise
Вопрос:
В образовательных целях я хочу реализовать 1-мерный Perlin Noise
алгоритм в Котлине. Я ознакомился с алгоритмом здесь и здесь.
Я думаю, что понял основную концепцию, однако моя реализация может возвращать значения, превышающие 1. Я ожидаю, что результат вызова perlin(x)
будет в диапазоне от 0 до 1. Я не могу понять, где я ошибаюсь, так что, может быть, кто-нибудь сможет указать мне правильное направление. Для простоты я пока использую простую линейную интерполяцию вместо плавного шага или других передовых методов.
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)
Вы бы в конечном итоге получили такой образ:
где зеленая линия-это число Perlin Noise
8
октав с постоянством 0.5
. Как вы можете видеть, сумма всех октав при x=0, например, больше 1. (Синяя линия-первая октава noise(x)
, а оранжевая-вторая октава 0.5 * noise(2x)
).
Что я делаю не так?
Заранее спасибо.
Примечание: Я знаю , что Simplex Noise
алгоритм является преемником Perlin Noise
, однако в образовательных целях я хочу реализовать Perlin Noise
его в первую очередь. Я также знаю, что моя граница должна быть установлена на величину 256, но для простоты я пока использовал 10.
Ответ №1:
Я покопался и нашел эту статью, в которой вводится значение для нормализации результатов, возвращаемых Perlin(x)
. По существу, амплитуды суммируются, и общая сумма делится на это значение. Это, по-видимому, имеет смысл, поскольку нам может «не повезти» и значение y будет равно 1,0 в первой октаве, а затем 0,5 в следующей и т. Д. Таким образом, деление на сумму амплитуд (1,5 в данном случае с 2 октавами) кажется разумным, чтобы сохранить значения в диапазоне 0 — 1.
Однако я не уверен, что это предпочтительный способ, поскольку ни один другой ресурс не использует эту технику.
Измененный код будет выглядеть следующим образом:
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
}