MyTetra Share
Делитесь знаниями!
Функция получения случайного числа на языке шейдеров GLSL
Время создания: 29.06.2021 13:13
Автор: Xintrea
Текстовые метки: шейдер, GLSL, random, рандом, хаотичность, случайное число, функция
Раздел: Компьютер - Программирование - Шейдеры - Шейдеры GLSL
Запись: xintrea/mytetra_syncro/master/base/1624961624hl4vze8xr5/text.html на raw.github.com

Удивительно, но в GLSL есть проблемы в получении случайных чисел и генерации шума. Согласно источнику Gamedev.ru, и согласно официальной документации OpenGL, в GLSL есть встроенная функция шума (причем она существует начиная с версии OpenGL 1.1):


Синтаксис:



noise[1234](genType x)



где:



  • genType - float, vec2, vec3, vec4;
  • x выступает в роли seed'а.



Эта функция возвращает значение в диапазоне [-1,1] со средним значением в 0, однако, GLSL не гарантирует полной идентичности результатов этой функции у разных вендоров.


На практике у этой функции есть несколько проблем.


В стандарте GLSL ES (который используется в ShaderToy.com через WebGL) функции генерации шума просто исключены из стандарта.


В полном стандарте GLSL (который используется в Bonzomatic) на десктопных видеокарточках функции шума дожны быть, и они есть. Но вендоры не делают реализации этих функций, и они в большинстве случаев возвращают просто 0 во всех своих компонентах.


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



Самописная функция генерации случайного числа через синус


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



float rand(vec2 co)

{

return fract(sin(dot(co.xy,vec2(12.9898,78.233))) * 43758.5453);

}



Вызов этой функции может выглядеть так:



float randValue=rand(vec2(fGlobalTime, fGlobalTime));



Однако у этой функции есть небольшая проблема: в зависимости от реализации в графическом процессоре функции sin(), функция может выдавать очень неслучайную последовательность из повторяющихся чисел. Происходит это из-за того, что при вычислении функции sin() от больших чисел может происходить численное переполнение. На некоторых графических процессорах sin() корректно работает с большими числами, а на некоторых - нет, и в спецификациях на GPU даже могут написать, что для вычисления sin() нужно нормализовывать значения углов до значений, сопостовимых по своему порядку с 2*PI, иначе на больших значениях углов функция sin() будет вычисляться грубо.


А если sin() вычисляется грубо, то для соседних значений больших углов результат функции sin() будет одним и тем же. Исчезают "биения" дробной части, и исчезает хаотичность.


Поэтому рекомендуется использовать следующую функцию, в которой эта проблема решена:



highp float rand(vec2 co)

{

highp float a = 12.9898;

highp float b = 78.233;

highp float c = 43758.5453;

highp float dt= dot(co.xy ,vec2(a,b));

highp float sn= mod(dt,3.14);

return fract(sin(sn) * c);

}



Более подробно можно почитать в блоге Andy Gryc: http://byteblacksmith.com/improvements-to-the-canonical-one-liner-glsl-rand-for-opengl-es-2-0


Однако, у этой функции есть еще одна проблема: ее значения все-таки не совсем случайны. И поэтому при ее использовании, даже если инициализировать ее значениями не только временем (в пределах одного кадра время почти одинаково), но добавлять или умножать время, например, на x- или y-координату текущей точки, то генерируемые функцией значения в статистике будут иметь закономерность, которая будет выражаться в характерном муаре. То есть, если просто с помощью этой функции заполнить экран случайными точками, то эти точки сложатся в хорошо заметные линии.



Функция генерации случайного числа на основе бинарной арифметики


Следующий вариант функции напрямую использует стандарт представления числа с плавающей запятой IEEE binary32 (формат хранения float). Значения, выдаваемые этой функцией, гораздо более хаотичны чем у sin()-варианта. Вот ее реализация:



// A single iteration of Bob Jenkins' One-At-A-Time hashing algorithm.

uint hash( uint x ) {

x += ( x << 10u );

x ^= ( x >> 6u );

x += ( x << 3u );

x ^= ( x >> 11u );

x += ( x << 15u );

return x;

}

uint hash( uvec2 v ) { return hash( v.x ^ hash(v.y) ); }

uint hash( uvec3 v ) { return hash( v.x ^ hash(v.y) ^ hash(v.z) ); }

uint hash( uvec4 v ) { return hash( v.x ^ hash(v.y) ^ hash(v.z) ^ hash(v.w) ); }


// Construct a float with half-open range [0:1] using low 23 bits.

// All zeroes yields 0.0, all ones yields the next smallest representable value below 1.0.

float floatConstruct( uint m ) {

const uint ieeeMantissa = 0x007FFFFFu; // binary32 mantissa bitmask

const uint ieeeOne = 0x3F800000u; // 1.0 in IEEE binary32


m &= ieeeMantissa; // Keep only mantissa bits (fractional part)

m |= ieeeOne; // Add fractional part to 1.0


float f = uintBitsToFloat( m ); // Range [1:2]

return f - 1.0; // Range [0:1]

}


// Pseudo-random value in half-open range [0:1].

float rand( float x ) { return floatConstruct(hash(floatBitsToUint(x+fGlobalTime))); }

float rand( vec2 v ) { return floatConstruct(hash(floatBitsToUint(v+fGlobalTime))); }

float rand( vec3 v ) { return floatConstruct(hash(floatBitsToUint(v+fGlobalTime))); }

float rand( vec4 v ) { return floatConstruct(hash(floatBitsToUint(v+fGlobalTime))); }



Вызов этой функции может выглядеть так:



float value=rand(gl_FragCoord.x);



В отличие от функции генерации через sin(), в качестве инициализирующего значения можно задавать детерминированные числа, не подмешивая время, так как время подмешивается в самой реализации функции. Случайные значения, выдаваемые этой функцией, имеют хорошее распределение, и тест на заполнение случайными точками такая функция проходит хорошо - никаких закономерностей не видно.


Так же в этом разделе:
 
MyTetra Share v.0.59
Яндекс индекс цитирования