Генерация псевдослучайных чисел на графическом процессоре
Используя сценарий C# в игровом движке Unity3D для управления вычислительным шейдером HLSL, я пытаюсь сгенерировать псевдослучайные числа на GPU и сохранить их в Texture2D. Следуя методу Geus 3 Hybrid Tausworthe на GPU и другому потоку генерации псевдослучайных чисел на GPU, я столкнулся с проблемой.
Эта проблема:
Полученная текстура выглядит как один сплошной цвет. Если я запускаю шейдер несколько раз, каждый раз получаю разные текстуры сплошного цвета, но вся текстура одного цвета.
Вычислить код шейдера
#pragma kernel CSMain
RWTexture2D<float4> result; // 256 resolution texture to write to
uint4 seed; //four uniform random numbers generated on the CPU in a C# script
struct RandomResult
{
uint4 state;
float value;
};
uint TausStep(uint z, int S1, int S2, int S3, uint M)
{
uint b = (((z << S1) ^ z) >> S2);
return ((z & M) << S3) ^ b;
}
uint LCGStep(uint z, uint A, uint C)
{
return A * z + C;
}
RandomResult HybridTaus(uint4 state)
{
state.x = TausStep(state.x, 13, 19, 12, 4294967294);
state.y = TausStep(state.y, 2, 25, 4, 4294967288);
state.z = TausStep(state.z, 3, 11, 17, 4294967280);
state.w = LCGStep(state.w, 1664525, 1013904223);
RandomResult rand;
rand.state = state;
rand.value = 2.3283064365387e-10 * (state.x ^ state.y ^ state.z ^ state.w);
return rand;
}
[numthreads(8, 8, 1)]
void CSMain(uint3 id)
{
result[id.xy] = HybridTaus(seed).value;
}
Нужно ли сохранять состояние на графическом процессоре? Если так, как бы я это сделал? Нужно ли освобождать память потом?
Я попытался присвоить результат функции HybridTaus() seed
в надежде на то, что он будет использовать новое значение в следующем вызове HybridTaus(seed), чтобы увидеть, будет ли это иметь значение. Я также попытался добавить уникальные произвольные числа на основе идентификатора потока, который является id
параметр. Это дало некоторые улучшенные результаты, но я подозреваю, что случайность настолько хороша, насколько я могу это сделать, исходя из математических операций, выполненных на идентификаторах потоков, а не эффективно из генератора случайных чисел.
[numthreads(8, 8, 1)]
void CSMain(uint3 id)
{
//first thing I tried
//RandomResult rand = HybridTaus(seed);
//seed = rand.state; // re-assign seed with the new state
//result[id.xy] = rand.value;
//second thing I tried
RandResult rand = HybridTaus(seed * uint4(id.x*id.y*id.x*id.y,
id.x*id.y/id.x*id.y,
id.x*id.y+id.x*id.y,
id.x*id.y-id.x*id.y));
result[id.xy] = rand.value;
}
1 ответ
Во-первых, я не знаю, какой алгоритм вы опубликовали, но я нашел этот простой алгоритм онлайн для генерации случайных чисел в графическом процессоре. Вот seed
это 32 бит uint
,
uint wang_hash(uint seed)
{
seed = (seed ^ 61) ^ (seed >> 16);
seed *= 9;
seed = seed ^ (seed >> 4);
seed *= 0x27d4eb2d;
seed = seed ^ (seed >> 15);
return seed;
}
Теперь в большинстве случаев этого достаточно, вы можете передать идентификатор локального вызова вашего вычислительного шейдера как уникальный и получить случайное число для потока или для вызова. Однако если вам нужно несколько случайных чисел на вызов (например, у вас есть цикл или вложенный цикл), это не сработает как seed
остается такой же. Так что я немного испортил функцию и придумал
uint wang_hash(uint seed)
{
seed = seed + 76.897898 * 48.789789 * cos(x) * sin(y) * 20.79797
seed = (seed ^ 61) ^ (seed >> 16);
seed *= 9;
seed = seed ^ (seed >> 4);
seed *= 0x27d4eb2d;
seed = seed ^ (seed >> 15);
return seed;
}
Вот x
а также y
мои вложенные в циклы переменные. И это работает для меня. Теперь вы можете получить несколько случайных чисел за вызов.
В вашем случае, однако, я не думаю, что вам нужен последний. Если я правильно понял, вам просто нужно сохранить случайное число для каждого текселя, чтобы вы могли попробовать первый и использовать уникальный идентификатор локального вызова, чтобы получить случайные числа для каждого значения текселя.