Випадковий шум на основі насіння


16

Зараз я працюю над програмою, яка повинна генерувати випадковий шум на екрані на основі "координат" пікселя. Координати повинні мати один і той же колір при кожному перезапуску програми. Однак, використовуючи util.Random Java, результати, які я отримую, не такі випадкові, як хотілося б:

роздрукувати зображене на екрані

Я думав, що якби я використовував комбіновані координати (як в одному цілому цілому, утвореному від обох координат поруч з іншим), кожна координата мала б інше число. Використовуючи це число в якості насіння, я розраховував отримати різні випадкові числа для кожної координати, щоб використовувати для значення rgb цієї координати.

Це код, який я використав:

public class Generate {

static Random Random;

    public static int TileColor(int x, int y){          
        Random = new Random(Integer.valueOf(Integer.toString(x)+Integer.toString(y)));
        int b = 1 + Random.nextInt(50);
        int g = 1 + Random.nextInt(50);
        int r = 1 + Random.nextInt(50);
        int color = -Color.rgb888(r, g, b);
        return color;
    }
}

Чи є модель, яку створює програма, завдяки тому, як працює функція Random Java, чи я щось роблю неправильно, і чи варто спробувати інший підхід?

Оновлення. Зараз я намагався позбутися проблем, пов'язаних з конкатенацією, використовуючи наступний код:

public static int TileColor(int x, int y){  
            Randomy = new Random(y);
            Randomx = new Random(x);
            Random = new Random(Integer.valueOf(Integer.toString(Randomx.nextInt(1234))+Integer.toString(Randomy.nextInt(1234))));
            int b = 1 + Random.nextInt(100);
            int g = 1 + Random.nextInt(100);
            int r = 1 + Random.nextInt(100);
            int color = -Color.rgb888(r, g, b);
            return color;
}

Так чи інакше це дало (на мій погляд) достатньо випадковий образ:

образ mew

Однак цей код перезавантажується три рази на піксель. Незважаючи на те, що зараз це не є проблемою для мене, я думаю, що я міняю цей код у випадку, якщо згодом мені буде потрібна краща підготовка.


3
Не впевнений у випадковості Java, але я впевнений, що це не справжній випадковий ... Читайте en.wikipedia.org/wiki/Pseudorandom_number_generator Ви зрозумієте, чому ви бачите ці шаблони.
Салькетер

23
Щось важливого, чого не вистачає в інших відповідях: не перезавантажуйте RNG для кожного пікселя. Помістіть його один раз і генеруйте послідовні значення для всіх пікселів вашого зображення, виходячи з цього.
Конрад Рудольф

4
Примітка: генератор псевдовипадкових чисел може бути рівномірно розподілений в одному вимірі , але виходить з ладу при використанні більш ніж одного виміру ... ви ефективно генеруєте точки в 3D (r, g і b і 3 різні координати), тому вам потрібен випадковий генератор, який гарантує не тільки те, що величини, які він генерує, розподілено однорідно, але й трійні, які він генерує, однорідно розподілені у 3D-просторі.
Бакуріу

6
@Bakuriu Якщо X, Y і Z є незалежними рівномірними випадковими змінними, то я майже впевнений (X, Y, Z) є рівномірним у 3d просторі.
Jack M

2
Ви можете експериментувати з використанням різних RNG, як, наприклад, Mersenne Twister .
Кевін

Відповіді:


21

java.util.RandomКлас Java зазвичай дає послідовності псевдовипадкових чисел, які досить хороші для використання в іграх 1 . Однак ця характеристика стосується лише послідовності декількох зразків на основі насіння. Якщо ви повторно ініціалізуєте RNG із збільшенням значень насіння і перегляньте лише перше значення кожної послідовності, характеристики випадковості будуть не настільки ж хорошими.

Що ви можете зробити замість цього:

  • Використовуйте те ж насіння, щоб генерувати цілі шматки пікселів одночасно. Наприклад, коли вам потрібно значення кольору пікселя 425: 487, подайте координати 400: 400 в RNG, генеруйте 10000 випадкових кольорів і використовуйте колір за індексом 2587 (25 * 100 + 87). Згенеровані таким чином шматки повинні кешуватися, щоб уникнути повторного генерування цих 10000 випадкових кольорів для кожного пікселя цього фрагмента.
  • Замість генератора випадкових чисел використовуйте функцію перебору повідомлень щоб перетворити пару координат у значення кольору. Вихід більшості МДФ є непередбачуваним, щоб виконати більшість тестів на випадковість. Вихід, як правило, перевищує 24 біт, необхідний для значення RGB, але обрізка їх зазвичай не становить жодних проблем.

    Для підвищення продуктивності можна поєднувати генерацію дайджесту повідомлень із шматками. Створіть невеликі шматки пікселів, які достатньо великі, щоб використовувати повну довжину одного виводу дайджест-функції.

1, коли абсолютно важливо, щоб наступне число ніхто не міг передбачити, використовуйте повільніше, але менш передбачуванеjava.security.SecureRandom


13

Координати повинні мати однаковий колір у всіх, хто перезапускає програму

У такому випадку ви хочете використовувати детерміновану функцію шуму, таку як шум Перліна або симплексний шум .

( Дивіться це запитання для отримання додаткової інформації про шум Перліна з гарними малюнками. )

Здебільшого використання вбудованої random()або подібної функції дасть вам різні значення при кожному запуску програми, оскільки вони можуть використовувати годинник як вхідне чи якесь інше псевдовипадкове значення.

Інший варіант - створити "карту шуму" один раз, в режимі офлайн, а потім використовувати це як джерело випадкових чисел згодом.

У своїй реалізації ви об'єднуєте рядкові представлення x і y. Це погано, оскільки він не є унікальним для домену. Наприклад,

x    y   concatenated
40   20  4020
402   0  4020
10   10  1010
101   0  1010
12   34  1234
123   4  1234
1   234  1234

Удачі!


1
Хороший момент щодо з’єднаних чисел. Однак програма завжди дає точно такий же результат, якщо я запускаю програму кілька разів. Я також розглядав перлін / симплексний шум, можливо, я загляну його і побачу, чи працює він краще. Однак я ще не впевнений, чому Java створює шаблони, тому що проблема конкатенації, здається, не вирішує її повністю
стрекоза

1
Чи недостатньо просто насіннєве насіннєве з постійним значенням насіння перед створенням пікселів?
Jack M

1
@JackM Це повністю залежить від алгоритму PRNG у грі.
3Dave

4
"Я колись бачив реалізацію rand (), яка використовувала кожне значення як джерело для наступного значення." Чи не так працює більшість генераторів некриптографічних псевдовипадкових чисел? Вони використовують попереднє випадкове число (або стан) як вхід для генерування наступного випадкового числа / стану.
JAB

2
@DavidLively Практично всі PRNG роблять це чи щось еквівалентне, якщо тільки їх внутрішній стан не перевищує діапазон чисел, які вони генерують (наприклад, твістери Мерсена), і навіть тоді послідовність випадкових чисел, звичайно, повністю визначається насінням.
Конрад Рудольф

9

Давайте подивимося, що саме ви робите:

  • Ви перебираєте всі пікселі по одному
  • Для кожного пікселя ви використовуєте конкатенацію його координат як початкову
  • Потім ви починаєте новий випадковий від даного насіння і виймаєте 3 числа

Все це звучить добре, але ви отримуєте шаблон, оскільки:

Піксель у 1,11 та піксель у 11,1 є насінням числа 111, тому вони, безумовно, мають однаковий колір.

Крім того, якщо ви завжди рухаєтесь однаково, ви можете використовувати лише один генератор, не потрібно використовувати один для кожного пікселя. Один на весь образ зробить! Ще є якісь закономірності через псевдовипадковість. @David_Lively має рацію в тому, щоб використовувати якийсь алгоритм шуму, це зробить це виглядати більш випадковим.


Вся справа в тому, що погляд на зображення повинен бути здатний зміщуватися (далі до позитивних координат). Тож цей підхід не спрацює повністю
стрікоза

4
Насправді "все це" не звучить нормально - повторна повторна детермінованість RNG для кожного пікселя є жахливою стратегією, якщо саме насіння не є криптографічним RNG (і навіть тоді це хитра стратегія з причин, не пов'язаних з розповсюдженням).
Конрад Рудольф

ви можете включити правильний спосіб конденсації чисел у цьому контексті. Тобто (x + y * ширина)
Taemyr

1

Зробіть генератор кольорів, а потім виробіть свої кольори для вашої плитки. Висійте лише один раз! Вам не потрібно сіяти більше, принаймні, на плитку.

public class RandomColorGenerator {
  private final int minValue;
  private final int range;
  private final Random random;
  public RandomColorGenerator(int minValue, int maxValue, Random random) {
    if (minValue > maxValue || (long)maxValue - (long)minValue > (long)Integer.MAX_VALUE) {
      throw new IllegalArgumentException();
    }
    this.minValue = minValue;
    this.range = maxValue - minValue + 1;
    this.random = Objects.requireNonNull(random);
  }

  public int nextColor() {
    int r = minValue + random.nextInt(range);
    int g = minValue + random.nextInt(range);
    int b = minValue + random.nextInt(range);
    return -Color.rgb888(r, g, b);
  }
}

public class Tile {
  private final int[][] colors;
  public Tile(int width, int height, RandomColorGenerator colorGenerator) {
    this.colors = new int[width][height];
    for (int x = 0; x < width; x++) {
      for (int y = 0; y < height; y++) {
        this.colors[x][y] = colorGenerator.nextColor();
      }
    }
  }

  public int getColor(int x, int y) {
    return colors[x][y];
  }
}

І використання буде таким:

RandomColorGenerator generator = new RandomColorGenerator(1, 100, new Random(0xcafebabe));
Tile tile = new Tile(300, 200, generator);
...
// getting the color for x, y:
tile.getColor(x, y);

З цим, якщо ви не задоволені результатом, просто поміняйте Randomнасіння. Крім того, вам потрібно зберігати / передавати насіння та розміри, щоб усі клієнти мали однакове зображення.


1

Замість використання Random, розглянути можливість використання дайджесту хешу, як MD5. Це дає важко передбачити "випадкове" значення на основі певного введення, але завжди те саме значення для одного і того ж вводу.

Приклад:

public static int TileColor(int x, int y){
        final MessageDigest md = MessageDigest.getInstance("MD5");
        final ByteBuffer b = ByteBuffer.allocate(8);
        b.putInt(x).putInt(y);
        final byte[] digest = md.digest(b.array());
        return -Color.rgb888(digest[0], digest[1], digest[2]);
}

ПРИМІТКА. Я не знаю, звідки походить Color.rgb888 (..), тому я не знаю, що таке допустимий діапазон. 0-255, хоча це нормально.

Вдосконалення для розгляду:

  • Складіть змінні MessageDigest та ByteBuffer за межами класу, щоб підвищити продуктивність. Для цього вам потрібно буде скинути ByteBuffer, і метод більше не буде безпечним для теми.
  • Масив дайджесту буде містити значення байтів 0-255, якщо ви хочете інші діапазони, вам доведеться зробити з ними певну математику.
  • Якщо ви хочете отримати різні "випадкові" результати, можете додати якесь "насіння". Наприклад, змініть на ByteBuffer.allocate (12) та додайте .putInt (насіння).

1

Інші вказали, що один із способів отримати поведінку, яку ви хочете, - це використовувати хеш-функцію, також "функцію перебору повідомлень". Проблема полягає в тому, що вони часто базуються на таких алгоритмах, як MD5, який криптографічно захищений (тобто справді, справді, дуже випадковий), але дуже повільний. Якщо ви використовуєте криптографічну хеш-функцію щоразу, коли вам потрібен випадковий піксель, у вас виникнуть досить серйозні проблеми з продуктивністю.

Однак є некриптографічні хеш-функції, які можуть створювати значення, які є досить випадковими для вашої мети, а також швидко. Той, до якого я зазвичай досягаю, - це бурмотіння . Я не користувач Java, але, здається, є принаймні одна реалізація Java . Якщо ви виявите, що вам дійсно потрібно, щоб кожен піксель генерувався за його координатами, а не генерував їх усі відразу і зберігав їх у текстурі, то це був би хороший спосіб зробити це.


1

Я б використав простір понад 2000 (максимум типової роздільної здатності).
Це дозволить мінімізувати (або усунути дублікати насіння)

public class Generate {

    static Random Random;

    public static int TileColor(int x, int y){          
        Random = new Random(x + 2213 * y);
        int b = 1 + Random.nextInt(50);
        int g = 1 + Random.nextInt(50);
        int r = 1 + Random.nextInt(50);
        int color = -Color.rgb888(r, g, b);
        return color;
    }
}

0

Randomдосить випадковий. Ви використовуєте його неправильно з двох основних причин.

  • Він не був розроблений для повторного посіву. Випадкові властивості зберігаються лише для однієї послідовності випадкових чисел.
  • Існує величезна кореляція Integer.valueOf(Integer.toString(x)+Integer.toString(y))між пікселями, які ви засіваєте.

Я б просто застосував деякий варіант наступного коду, де ви можете вибрати хеш-функцію (не використовувати Integer.getHashCode) з відповідей за адресою /programming/9624963/java-simplest-integer- хеш

public static int TileColor(int x, int y) {
    return hash(x ^ hash(y));
}

де може бути хеш-функція


0

Ви можете спробувати використати системи поточний годинний годинник як насіння, як це:

Random random = new Random(System.currentTimeMillis())

Сподіваємось, це виробляє більш випадкове значення.


Однак це не створює значення дам для координати дам кожен раз.
стрікоза

0

Ось однолінійна функція статичного шейдера, яку я придумав - полтергейст (Noisy Ghost).

Він займає 2D координату і насіннєвий елемент, а також надає монотонно, як вимагається. Вона працює в реальному часі в секунду, незалежно від роздільної здатності екрана. Це те, для чого призначені графічні процесори.

// poltergeist (noisy ghost) pseudo-random noise generator function
// dominic.cerisano@standard3d.com 03/24/2015

precision highp float;

float poltergeist(in vec2 coordinate, in float seed) 
{
    return fract(sin(dot(coordinate*seed, vec2(12.9898, 78.233)))*43758.5453); 
}

void mainImage(out vec4 fragColor, in vec2 fragCoord) 
{   
    fragColor = vec4(poltergeist(fragCoord, iGlobalTime)); 
}

Будь-яка роздільна здатність, будь-яка текстура, на будь-якому пристрої (теж мобільний), що підтримує GL (що майже з будь-яким екраном).

Побачте, як він працює тут прямо зараз!

https://www.shadertoy.com/view/ltB3zD

Ви можете легко включити цей шейдер у свою програму java, використовуючи стандартний opengl або будь-який браузер, використовуючи стандартний webgl.

Просто заради задоволення, я кидаю рукавичку для того, щоб хтось переміг Полтергейста за якістю та продуктивністю на всіх пристроях. Шумний привид правила! Непереможений!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.