|
|||||||
Функция получения случайного числа на языке шейдеров 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) где:
Эта функция возвращает значение в диапазоне [-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(), в качестве инициализирующего значения можно задавать детерминированные числа, не подмешивая время, так как время подмешивается в самой реализации функции. Случайные значения, выдаваемые этой функцией, имеют хорошее распределение, и тест на заполнение случайными точками такая функция проходит хорошо - никаких закономерностей не видно. |
|||||||
Так же в этом разделе:
|
|||||||
|
|||||||
|