Случайное число hlsl


13

Как вы генерируете случайное число в HLSL?

Я спрашиваю, потому что я хочу попробовать трассировку лучей GPU . Вам нужно генерировать случайные направления в пиксельном шейдере. Так что я хочу randFloat(), где результатом является случайное число от -1 до +1.

Кроме того, как обстоят дела с инструкцией по шуму hlsl ? Документы говорят, что он был удален из HLSL 2.0 и выше. Почему ?

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

С подробностями, как вы можете дешево генерировать случайные числа на GPU?


1
Что вы подразумеваете под каждой вершиной, что координата текс интерполируется в пиксельном шейдере и его поиск на пиксель для этой случайной текстуры.
Кикаймару

Я имею в виду, что если значения двух текстур окажутся слишком близко друг к другу, эта зависимость будет существовать (вы получите «смешанное» значение). Скажем, одна вершина имеет (0.5,0.5) для u, v, а соседняя вершина имеет (0.51,0.52), и на этом отрезке примерно 500 фрагментов ... тогда на выходе будет 2 или 3 фактических случайных значения (от текстуры), но остальные будут интерполированы линейно вдоль лица и не будут случайными. Я хочу исключить эту возможность и иметь функцию rand (), которую я могу вызвать в пиксельном шейдере
bobobobo

Почему вы говорите, что одна вершина имеет 0,5, а другая 0,51, что не звучит случайным образом, вам также нужно «рандомизировать» эти координаты текс, вы можете сделать это в пиксельном шейдере, если хотите, но это намного больше операций , Эти текстовые координаты не нужно генерировать из их положения, поэтому не имеет значения, насколько они близки друг к другу. Вы можете отобрать случайную текстуру один раз в вершинном шейдере (используя position, normal, texcoords * некоторый параметр как texcoords), и это даст вам texcoords, которые вы передадите пиксельному
шейдеру

@ Kikaimaru Я имею в виду под плохой случайностью, это возможно ..
Бобобо

Да в случайной последовательности возможно иметь два одинаковых значения друг за другом
Кикаймару

Ответы:


14

Псевдослучайные числа в пиксельном шейдере получить нелегко. Генератор псевдослучайных чисел в ЦП будет иметь некоторое состояние, из которого он одновременно читает и записывает при каждом вызове функции. Вы не можете сделать это в пиксельном шейдере.

Вот несколько вариантов:

  1. Используйте вычислительный шейдер вместо пиксельного шейдера - они поддерживают доступ для чтения и записи в буфер, так что вы можете реализовать любой стандартный PRNG.

  2. Выборка из одной или нескольких текстур, содержащих случайные данные, на основе такого параметра, как положение на экране. Вы также можете выполнить некоторые математические операции с позицией, прежде чем использовать ее для поиска текстуры, что должно позволить вам повторно использовать текстуру, если математические операции включают в себя шейдерную константу random-per-draw-call.

  3. Найдите некоторую математическую функцию положения на экране, которая дает «достаточно случайные» результаты.

Быстрый поиск в Google нашел эту страницу с этими функциями:

float rand_1_05(in float2 uv)
{
    float2 noise = (frac(sin(dot(uv ,float2(12.9898,78.233)*2.0)) * 43758.5453));
    return abs(noise.x + noise.y) * 0.5;
}

float2 rand_2_10(in float2 uv) {
    float noiseX = (frac(sin(dot(uv, float2(12.9898,78.233) * 2.0)) * 43758.5453));
    float noiseY = sqrt(1 - noiseX * noiseX);
    return float2(noiseX, noiseY);
}

float2 rand_2_0004(in float2 uv)
{
    float noiseX = (frac(sin(dot(uv, float2(12.9898,78.233)      )) * 43758.5453));
    float noiseY = (frac(sin(dot(uv, float2(12.9898,78.233) * 2.0)) * 43758.5453));
    return float2(noiseX, noiseY) * 0.004;
}

Re: вычислить шейдеры, вы также можете хранить состояние RNG в разделяемой памяти группы потоков. Для эффективности вам не нужно только одно состояние ГСЧ (конфликт будет огромным узким местом, поскольку каждый поток пытался его обновить), но многие - возможно, по одному на каждый поток, или, возможно, один на 4 или 8 потоков или что-то подобное - пока государства достаточно малы, чтобы сделать это возможным (то есть, вероятно, не Мерсенн Твистер). Существуют ли схемы ГСЧ, в которых несколько потоков SIMD могут совместно генерировать несколько случайных чисел одновременно? Это было бы очень полезно здесь.
Натан Рид

1
внешняя ссылка не работает
Джордж Бирбилис

2

Вот как я генерирую псевдослучайные числа для эффектов частиц с помощью вычислительного шейдера. Не уверен, насколько случайным является этот код, но он достаточно хорош для моих целей.

Ключом к тому, чтобы дать каждому ядру графического процессора свою собственную случайную последовательность, является подача на вычислительный шейдер начального случайного начального числа из ЦП. Затем каждое ядро ​​использует это начальное число для циклического запуска генератора чисел, используя свой идентификатор потока в качестве счетчика. Оттуда каждый поток должен иметь свое собственное уникальное начальное число для генерации уникальных значений.

// Source
// http://www.gamedev.net/topic/592001-random-number-generation-based-on-time-in-hlsl/
// Supposebly from the NVidia Direct3D10 SDK
// Slightly modified for my purposes
#define RANDOM_IA 16807
#define RANDOM_IM 2147483647
#define RANDOM_AM (1.0f/float(RANDOM_IM))
#define RANDOM_IQ 127773u
#define RANDOM_IR 2836
#define RANDOM_MASK 123459876

struct NumberGenerator {
    int seed; // Used to generate values.

    // Returns the current random float.
    float GetCurrentFloat() {
        Cycle();
        return RANDOM_AM * seed;
    }

    // Returns the current random int.
    int GetCurrentInt() {
        Cycle();
        return seed;
    }

    // Generates the next number in the sequence.
    void Cycle() {  
        seed ^= RANDOM_MASK;
        int k = seed / RANDOM_IQ;
        seed = RANDOM_IA * (seed - k * RANDOM_IQ ) - RANDOM_IR * k;

        if (seed < 0 ) 
            seed += RANDOM_IM;

        seed ^= RANDOM_MASK;
    }

    // Cycles the generator based on the input count. Useful for generating a thread unique seed.
    // PERFORMANCE - O(N)
    void Cycle(const uint _count) {
        for (uint i = 0; i < _count; ++i)
            Cycle();
    }

    // Returns a random float within the input range.
    float GetRandomFloat(const float low, const float high) {
        float v = GetCurrentFloat();
        return low * ( 1.0f - v ) + high * v;
    }

    // Sets the seed
    void SetSeed(const uint value) {
        seed = int(value);
        Cycle();
    }
};
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.