Почему этот 1-мерный Генератор Шума Перлина Возвращает Значения > 1?

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

Вы бы в конечном итоге получили такой образ:

Генерируемый 1D Перлиновый шум

где зеленая линия-это число 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
    }