Диапазон выхода шума Перлина

Я исследую несколько различных реализаций для когерентного шума (я знаю, что есть библиотеки, но это в основном для моего собственного назидания и любопытства) и как вы можете его использовать, и есть одна проблема, с которой я столкнулся с оригинальным перлинским шумом,

Согласно часто задаваемым часто задаваемым вопросам по математике, выходной диапазон будет -1 а также 1, но я не понимаю, как значение попадает в этот диапазон.

Насколько я понимаю, алгоритм в основном такой: каждая точка сетки имеет связанный вектор случайного градиента длины 1, Затем для каждой точки для всех четырех окружающих точек сетки вы вычисляете произведение случайного градиента и вектора, идущего от этой точки сетки. Затем вы используете необычную кривую легкости и линейную интерполяцию, чтобы уменьшить это значение до одного значения.

Но вот моя проблема: эти точечные продукты иногда оказываются за пределами диапазона [-1, 1]и поскольку вы в конечном итоге выполняете линейную интерполяцию между точечными произведениями, не означает ли это, что конечное значение иногда будет выходить за пределы диапазона [-1, 1]?

Скажем, например, что один из случайных векторов (sqrt(2)/2, sqrt(2)/2) (который имеет длину 1) и (0.8, 0.8) (который находится в квадрате единицы), вы получите результат примерно 1.131, Если это значение используется в линейной интерполяции, вполне возможно, что сгенерированное значение будет больше 1, И действительно, с моей прямой реализацией, это происходит довольно часто.

Я что-то здесь упускаю?

Для справки, вот мой код на Java. Vec простой класс для выполнения простой 2d векторной арифметики, fade() кривая легкости, lerp() линейная интерполяция, и gradient(x, y) дает вам градиент для этой точки сетки как Vec, gridSize Переменная дает вам размер сетки в пикселях (имеет тип double):

public double getPoint(int x, int y) {
    Vec p = new Vec(x / gridSize, y / gridSize);
    Vec d = new Vec(Math.floor(p.x), Math.floor(p.y));


    int x0 = (int)d.x,
        y0 = (int)d.x;


    double d00 = gradient(x0    , y0    ).dot(p.sub(x0    , y0    )),
           d01 = gradient(x0    , y0 + 1).dot(p.sub(x0    , y0 + 1)),
           d10 = gradient(x0 + 1, y0    ).dot(p.sub(x0 + 1, y0    )),
           d11 = gradient(x0 + 1, y0 + 1).dot(p.sub(x0 + 1, y0 + 1));

    double fadeX = fade(p.x - d.x),
           fadeY = fade(p.y - d.y);

    double i1 = lerp(fadeX, d00, d10),
           i2 = lerp(fadeX, d01, d11);

    return lerp(fadeY, i1, i2);
}

Редактировать: вот код для генерации случайных градиентов:

double theta = gen.nextDouble() * 2 * Math.PI; 
gradients[i] = new Vec(Math.cos(theta), Math.sin(theta));

куда gen это java.util.Random,

2 ответа

У тебя есть y0 = (int)d.x;, но вы имеете в виду d.y, Это наверняка повлияет на ваш выходной диапазон и является причиной, по которой вы видите такие значения, выходящие за пределы допустимого диапазона.


Тем не менее, выходной диапазон шума Перлина на самом деле не [-1, 1]. Хотя я сам не совсем уверен в математике (я, должно быть, старею), это довольно продолжительное обсуждение показывает, что фактический диапазон равен [-sqrt (n) / 2, sqrt (n) / 2], где n - размерность (2 в вашем случае). Таким образом, выходной диапазон вашей двухмерной шумовой функции Перлина должен быть [-0,707, 0,707]. Это как-то связано с тем, что оба d и параметры интерполяции являются функцией p, Если вы прочитаете эту дискуссию, вы можете найти точное объяснение, которое вы ищете (в частности, пост № 7).

Я тестирую вашу реализацию, используя следующую программу (которую я взломал из вашего примера, так что простите за странное использование gridCells а также gridSize):

import java.util.Random;


public class Perlin {

    static final int gridSize = 200;
    static final int gridCells = 20;
    static final Vec[][] gradients = new Vec[gridCells + 1][gridCells + 1];

    static void initializeGradient () {
        Random rand = new Random();
        for (int r = 0; r < gridCells + 1; ++ r) {
            for (int c = 0; c < gridCells + 1; ++ c) {
                double theta = rand.nextFloat() * Math.PI;
                gradients[c][r] = new Vec(Math.cos(theta), Math.sin(theta));                
            }
        }
    }

    static class Vec {
        double x;
        double y;
        Vec (double x, double y) { this.x = x; this.y = y; }
        double dot (Vec v) { return x * v.x + y * v.y; }
        Vec sub (double x, double y) { return new Vec(this.x - x, this.y - y); }
    }

    static double fade (double v) {
        // easing doesn't matter for range sample test.
        // v = 3 * v * v - 2 * v * v * v;
        return v;
    }

    static double lerp (double p, double a, double b) {
        return (b - a) * p + a;
    }

    static Vec gradient (int c, int r) {
        return gradients[c][r];
    }

    // your function, with y0 fixed. note my gridSize is not a double like yours.     
    public static double getPoint(int x, int y) {

        Vec p = new Vec(x / (double)gridSize, y / (double)gridSize);
        Vec d = new Vec(Math.floor(p.x), Math.floor(p.y));

        int x0 = (int)d.x,
            y0 = (int)d.y;

        double d00 = gradient(x0    , y0    ).dot(p.sub(x0    , y0    )),
               d01 = gradient(x0    , y0 + 1).dot(p.sub(x0    , y0 + 1)),
               d10 = gradient(x0 + 1, y0    ).dot(p.sub(x0 + 1, y0    )),
               d11 = gradient(x0 + 1, y0 + 1).dot(p.sub(x0 + 1, y0 + 1));

        double fadeX = fade(p.x - d.x),
               fadeY = fade(p.y - d.y);

        double i1 = lerp(fadeX, d00, d10),
               i2 = lerp(fadeX, d01, d11);

        return lerp(fadeY, i1, i2);

    }

    public static void main (String[] args) {

        // loop forever, regenerating gradients and resampling for range. 
        while (true) {

            initializeGradient();

            double minz = 0, maxz = 0;

            for (int x = 0; x < gridSize * gridCells; ++ x) {
                for (int y = 0; y < gridSize * gridCells; ++ y) {
                    double z = getPoint(x, y);
                    if (z < minz)
                        minz = z;
                    else if (z > maxz)
                        maxz = z;
                }
            }

            System.out.println(minz + " " + maxz);

        }

    }

}

Я вижу значения в пределах теоретического диапазона [-0,707, 0,707], хотя обычно я вижу значения в диапазоне от -0,6 до 0,6; что может быть просто следствием распределения значений и низкой частоты дискретизации.

Когда вы вычисляете точечное произведение, вы можете получить значения за пределами диапазона -1 +1, однако на этапе интерполяции конечное значение попадает в диапазон -1 +1. Это связано с тем, что векторы расстояния продуктов точек, которые интерполируются, указывают на противоположные направления интерполированной оси. Во время последней интерполяции выходной сигнал не будет превышать -1 +1 диапазона.

Конечный выходной диапазон для шума Перлина определяется длиной градиентных векторов. Если мы говорим о 2D-шуме и нашей цели иметь выходной диапазон -1 +1, длина векторов градиента должна быть sqrt(2) (~1,4142). Распространенной ошибкой является смешивание этих векторов (1, 1) (-1, 1) (1, -1) (-1, -1) и (1, 0) (0, 1) (-1, 0) (0, -1). В этом случае конечный выходной диапазон по-прежнему будет равен -1 +1, однако значения в диапазоне -0,707 +0,707 будут более частыми. Чтобы избежать этой проблемы, векторы (1, 0) (0, 1) (-1, 0) (0, -1) следует заменить на (sqrt(2), 0) (0, sqrt(2)) (-sqrt(2), 0) (0, -кврт (2)).

Другие вопросы по тегам