Случайные / шумовые функции для GLSL


179

Поскольку производители драйверов графических процессоров обычно не пытаются реализовать их noiseXв GLSL, я ищу набор служебных функций «швейцарский армейский нож для графической рандомизации» , предпочтительно оптимизированный для использования в шейдерах графических процессоров. Я предпочитаю GLSL, но код, который мне подойдет для любого языка, вполне подходит для его самостоятельного перевода в GLSL.

В частности, я бы ожидал:

a) Псевдослучайные функции - N-мерное, равномерное распределение по [-1,1] или более [0,1], рассчитанное из M-мерного начального числа (в идеале - любое значение, но я в порядке с ограниченным начальным числом) скажем, 0..1 для равномерного распределения результатов). Что-то вроде:

float random  (T seed);
vec2  random2 (T seed);
vec3  random3 (T seed);
vec4  random4 (T seed);
// T being either float, vec2, vec3, vec4 - ideally.

б) Непрерывный шум, такой как шум Perlin - опять же, N-мерное, + - равномерное распределение, с ограниченным набором значений и, ну, хорошо выглядящий (некоторые параметры для настройки внешнего вида, такие как уровни Perlin, также могут быть полезны). Я ожидал бы подписи как:

float noise  (T coord, TT seed);
vec2  noise2 (T coord, TT seed);
// ...

Я не очень разбираюсь в теории генерации случайных чисел, так что я бы с большим удовольствием принялся за готовое решение , но я также был бы признателен за ответы типа «вот очень хороший, эффективный 1D rand (), и позвольте мне объяснить Вы, как сделать хороший N-мерный rand () поверх него ... " .

Ответы:


263

Для очень простых псевдослучайных вещей я использую этот oneliner, который я где-то нашел в Интернете:

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

Вы также можете сгенерировать шумовую текстуру, используя любой PRNG, который вам нравится, затем загрузить его обычным способом и сэмплировать значения в своем шейдере; Я могу выкопать пример кода позже, если хотите.

Также проверьте этот файл для GLSL-реализаций шума Perlin и Simplex, автор Stefan Gustavson.


14
Как вы используете vec2 co? это диапазон? семена?
Росс

12
Остерегайтесь фрагментных шейдеров с плавающей точкой низкой точности с помощью этого алгоритма (например, ARM Mali S3): stackoverflow.com/questions/11293628/… . Проект github.com/ashima/webgl-noise , похоже, не имеет проблем с низким уровнем.
PT

4
FWIW, функция, описанная здесь, обсуждается более подробно здесь .
Loomchild

3
К вашему сведению: распределение этой функции ужасно.
Тара

3
Я новичок в GLSL, кто-нибудь может объяснить, почему co.xyиспользуется, а не co?
Келин

83

Мне приходит в голову, что вы можете использовать простую целочисленную хеш-функцию и вставить результат в мантиссу с плавающей точкой. IIRC спецификация GLSL гарантирует 32-разрядные целые числа без знака и двоичное представление IEEE32, поэтому оно должно быть идеально переносимым.

Я дал это попробовать только сейчас. Результат очень хороший: он выглядит точно как статический при каждом входе, который я пробовал, никаких видимых шаблонов вообще. Напротив, популярный фрагмент кода sin / fract имеет довольно четко выраженные диагональные линии на моем графическом процессоре при тех же входных данных.

Недостатком является то, что требуется GLSL v3.30. И хотя это кажется достаточно быстрым, я не эмпирически оценил его производительность. AMD Shader Analyzer требует 13,33 пикселей на такт для версии vec2 на HD5870. Контраст с 16 пикселями за такт для фрагмента sin / fract. Так что, конечно, немного медленнее.

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

/*
    static.frag
    by Spatial
    05 July 2013
*/

#version 330 core

uniform float time;
out vec4 fragment;



// 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;
}



// Compound versions of the hashing algorithm I whipped together.
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 random( float x ) { return floatConstruct(hash(floatBitsToUint(x))); }
float random( vec2  v ) { return floatConstruct(hash(floatBitsToUint(v))); }
float random( vec3  v ) { return floatConstruct(hash(floatBitsToUint(v))); }
float random( vec4  v ) { return floatConstruct(hash(floatBitsToUint(v))); }





void main()
{
    vec3  inputs = vec3( gl_FragCoord.xy, time ); // Spatial and temporal inputs
    float rand   = random( inputs );              // Random per-pixel value
    vec3  luma   = vec3( rand );                  // Expand to RGB

    fragment = vec4( luma, 1.0 );
}

Скриншот:

Вывод случайного (vec3) в static.frag

Я проверил скриншот в программе для редактирования изображений. Имеется 256 цветов, а среднее значение составляет 127, что означает, что распределение является равномерным и охватывает ожидаемый диапазон.


17
+1 за хорошую идею и реализацию. Я бы поставил под сомнение утверждение, что, поскольку существует 256 цветов, а среднее значение составляет 127, распределение должно быть равномерным (в строгом смысле). Это может быть единообразно, но я не думаю, что мы это еще знаем. Например, распределение кривой колокола может иметь одинаковое среднее значение и количество цветов, но не будет равномерным.
LarsH

Отклонил это по причине, указанной @LarsH.
Autumnsault

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

5
По моему восприятию гистограммы, оно кажется очень равномерным ... Я бы сказал, что оно достаточно хорошо для большинства приложений, которым также необходима однородность. (Единственные значения, которые, как кажется, генерируются меньше, чем остальные, это 0 и 255)
leviathanbadger,

Спасибо. Моя вероятность ржавая. Посмотрев на набор команд GCN, это должно быть очень быстрым на более новых аппаратных средствах, поскольку они напрямую поддерживают операции битового поля в своих наборах инструкций. Тесты, которые я делал, выполнялись на старом оборудовании.
Пространственный

73

Реализация Густавсона использует 1D текстуру

Нет, не с 2005 года. Просто люди настаивают на скачивании старой версии. Версия, указанная в указанной вами ссылке, использует только 8-битные 2D-текстуры.

Новая версия Ian McEwan из Ashima и меня не использует текстуру, но работает на половине скорости на типичных настольных платформах с большой пропускной способностью текстур. На мобильных платформах безтекстурная версия может быть быстрее, потому что текстурирование часто является существенным узким местом.

Наш активно поддерживаемый исходный репозиторий:

https://github.com/ashima/webgl-noise

Коллекция как текстурных, так и текстурных версий шума здесь (с использованием только 2D текстур):

http://www.itn.liu.se/~stegu/simplexnoise/GLSL-noise-vs-noise.zip

Если у вас есть какие-либо конкретные вопросы, напишите мне напрямую (мой адрес электронной почты можно найти в classicnoise*.glslисточниках).


4
Да, реализация, о которой я говорю, ваш код на davidcornette.com, на который ссылается @dep, использует 1D-текстуру glBindTexture(GL_TEXTURE_1D, *texID);и т. Д. Неясно, что вы подразумеваете под «ссылкой, которую вы указали», поскольку цитируете мой ответ но этот ответ не связан с вашей реализацией. Я обновлю свой ответ, чтобы уточнить, о чем я говорю, и отразить новую информацию, которую вы предоставили. Характеризация людей как «настойчивых» при загрузке старой версии - это искажение, которое не заслуживает вашего внимания.
LarsH

1
PS Возможно, вы захотите написать Дэвиду Корнетту (у него есть контактная информация на davidcornette.com ) и попросить его изменить свою ссылку на davidcornette.com/glsl/links.html для ссылки на ваш репо-источник. Я тоже напишу ему по электронной почте.
LarsH

1
PPS Можете ли вы уточнить, какая версия использует только 8-битные 2D текстуры? Похоже, это может быть хорошим вариантом для определенных платформ ...
LarsH

31

Золотой шум

// Gold Noise ©2015 dcerisano@standard3d.com
// - based on the Golden Ratio
// - uniform normalized distribution
// - fastest static noise generator function (also runs at low precision)

float PHI = 1.61803398874989484820459;  // Φ = Golden Ratio   

float gold_noise(in vec2 xy, in float seed){
       return fract(tan(distance(xy*PHI, xy)*seed)*xy.x);
}

Смотрите Золотой шум в вашем браузере прямо сейчас!

введите описание изображения здесь

Эта функция улучшила случайное распределение по текущей функции в ответе @appas от 9 сентября 2017 года:

введите описание изображения здесь

Функция @appas также неполная, учитывая, что начальное число не поставляется (уф не является начальным значением - одинаковое для каждого кадра) и не работает с чипсетами низкой точности. Gold Noise по умолчанию работает с низкой точностью (намного быстрее).


Спасибо за публикацию этого. Не могли бы вы опубликовать работающую версию, например, на shadertoy.com, чтобы люди могли попробовать ее в браузере?
LarsH

@snb Shadertoy.com проходит техническое обслуживание в этом месяце, наберитесь терпения. Также я ясно задокументировал требование к иррациональным начальным значениям в коде. Поскольку золотой шум возвращает скаляр, построение векторов с его помощью тривиально, а также задокументировано в коде.
Доминик Черизано

7
Я не думаю, что это отличается от других шумовых функций. что вы доказываете, что это имеет особые свойства. просто потому, что вы используете кучу иррациональных чисел, это не делает его особенным.
М.Казем Ахгари

2
@Dominic: «У него превосходное распределение по аналогичным функциям»: это должно быть доказано. tan () действительно плохо обусловлен. и tan () вблизи pi / 2, и sqrt () вблизи нуля с большой вероятностью будут давать разные результаты на разных аппаратных средствах, поскольку все фракты (нелинейные * большие) основаны на менее значимых битах. Маленькие или высокие входные значения также будут влиять на это. Кроме того, динамика битов, вероятно, также сильно варьируется в зависимости от местоположения.
Fabrice NEYRET

2
NB. В настоящее время GLSL имеет целые числа, поэтому больше нет причин не использовать «серьезные» основанные на int генераторы хешей, когда требуется качественное распределение (и динамика) с аналогичными характеристиками. (за исключением устройств очень низкого уровня).
Fabrice NEYRET

12

Существует также хорошая реализация, описанная здесь McEwan и @StefanGustavson, которая выглядит как шум Перлина, но «не требует какой-либо настройки, то есть не текстуры или унифицированных массивов. Просто добавьте ее в исходный код вашего шейдера и вызывайте ее где угодно».

Это очень удобно, особенно если учесть, что более ранняя реализация Густавсона, на которую ссылается @dep, использует 1D текстуру, которая не поддерживается в GLSL ES (шейдерный язык WebGL).


1
Это лучший ответ на б) запрос типа шума ОП! Вот прямая ссылка github.com/ashima/webgl-noise . Есть 2d, 3d и 4d версии, готовые как код GLSL 120.
user362515

3

Используйте это:

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);
}

Не используйте это:

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

Вы можете найти объяснение в разделе Улучшения канонического однострочного GLSL rand () для OpenGL ES 2.0


Я пролистал статью, но все еще не уверен, есть ли 3.14 в modприближении числа пи?
Каан Э.

2

Только что нашел эту версию 3D шума для GPU, якобы она является самой быстрой из доступных:

#ifndef __noise_hlsl_
#define __noise_hlsl_

// hash based 3d value noise
// function taken from https://www.shadertoy.com/view/XslGRr
// Created by inigo quilez - iq/2013
// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.

// ported from GLSL to HLSL

float hash( float n )
{
    return frac(sin(n)*43758.5453);
}

float noise( float3 x )
{
    // The noise function returns a value in the range -1.0f -> 1.0f

    float3 p = floor(x);
    float3 f = frac(x);

    f       = f*f*(3.0-2.0*f);
    float n = p.x + p.y*57.0 + 113.0*p.z;

    return lerp(lerp(lerp( hash(n+0.0), hash(n+1.0),f.x),
                   lerp( hash(n+57.0), hash(n+58.0),f.x),f.y),
               lerp(lerp( hash(n+113.0), hash(n+114.0),f.x),
                   lerp( hash(n+170.0), hash(n+171.0),f.x),f.y),f.z);
}

#endif

1
Gold Noise (выше), очевидно, является самым быстрым, поскольку он выполняет гораздо меньше операций и выполняет только один хэш - этот вызов вызывает функцию хэширования 8 раз, выполняя вложенные линейные интерполяции (lerps). Также этот имеет низкое распределение, особенно при низкой точности.
Доминик Черизано,

1
О, хорошо, это график типа перлин-шума от Shadertoh от Inigo Quilez. Хороший код Доминик плохо проверил его Lrr
com.prehensible

@Fabrice Похоже, вы не понимаете вопрос OP, мой ответ, мой код или мой комментарий. Gold Noise является непрерывным по определению OP - он принимает ультрафиолетовое и начальное число и доказывает это, предоставляя шейдер. Все в вашем комментарии неверно. Вы продолжаете путать хеш-функции с псевдослучайными шумовыми функциями. Они не одинаковы. Шумовые функции не требуют генерации уникальных идентификаторов, таких как хеш-функции (фактическая точка хеширования).
Доминик Черизано,

Пожалуйста, пожалуйста, Доминик, прочитайте больше и узнайте больше, прежде чем заявлять о терминах, которые, как вы думаете, вы понимаете, хотя это не так. Не только эти термины абсолютно точны и хорошо определены в литературе, плюс я работаю на местах, но и ОП доказывает, что он понял термины на примерах, которые он привел после. Подсказка: «непрерывно» + «шум» + «как у Perlin». ru.wikipedia.org/wiki/Perlin_noise
Fabrice NEYRET

Непрерывный - это только случай добавления предложения цикла, многие функции шума зацикливаются и ухудшаются после определенного пути из-за округления битов, особенно для графики. Ребята, это просто коммуникация от вас, используйте свое время для важных исследований.
com.prehensible

1

Прямая, зубчатая версия 1d Perlin, по сути случайный зигзаг.

half  rn(float xx){         
    half x0=floor(xx);
    half x1=x0+1;
    half v0 = frac(sin (x0*.014686)*31718.927+x0);
    half v1 = frac(sin (x1*.014686)*31718.927+x1);          

    return (v0*(1-frac(xx))+v1*(frac(xx)))*2-1*sin(xx);
}

Я также нашел 1-2-3-4d перлин-шум на владельце шейдерного сайта Inigo Quilez Perlin, а также вороной и т. Д., У него есть полные быстрые реализации и коды для них.


1

hash: В настоящее время существует webGL2.0, поэтому целые числа доступны в (w) GLSL. -> для качественного переносимого хеша (при той же цене, что и уродливые хеш-плавающие) мы теперь можем использовать «серьезные» методы хеширования. IQ реализовал некоторые в https://www.shadertoy.com/view/XlXcW4 (и больше)

Например:

  const uint k = 1103515245U;  // GLIB C
//const uint k = 134775813U;   // Delphi and Turbo Pascal
//const uint k = 20170906U;    // Today's date (use three days ago's dateif you want a prime)
//const uint k = 1664525U;     // Numerical Recipes

vec3 hash( uvec3 x )
{
    x = ((x>>8U)^x.yzx)*k;
    x = ((x>>8U)^x.yzx)*k;
    x = ((x>>8U)^x.yzx)*k;

    return vec3(x)*(1.0/float(0xffffffffU));
}

0

Ниже приведен пример добавления белого шума к отображаемой текстуре. Решение состоит в том, чтобы использовать две текстуры: оригинальный и чистый белый шум, как этот: вики белый шум

private static final String VERTEX_SHADER =
    "uniform mat4 uMVPMatrix;\n" +
    "uniform mat4 uMVMatrix;\n" +
    "uniform mat4 uSTMatrix;\n" +
    "attribute vec4 aPosition;\n" +
    "attribute vec4 aTextureCoord;\n" +
    "varying vec2 vTextureCoord;\n" +
    "varying vec4 vInCamPosition;\n" +
    "void main() {\n" +
    "    vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
    "    gl_Position = uMVPMatrix * aPosition;\n" +
    "}\n";

private static final String FRAGMENT_SHADER =
        "precision mediump float;\n" +
        "uniform sampler2D sTextureUnit;\n" +
        "uniform sampler2D sNoiseTextureUnit;\n" +
        "uniform float uNoseFactor;\n" +
        "varying vec2 vTextureCoord;\n" +
        "varying vec4 vInCamPosition;\n" +
        "void main() {\n" +
                "    gl_FragColor = texture2D(sTextureUnit, vTextureCoord);\n" +
                "    vec4 vRandChosenColor = texture2D(sNoiseTextureUnit, fract(vTextureCoord + uNoseFactor));\n" +
                "    gl_FragColor.r += (0.05 * vRandChosenColor.r);\n" +
                "    gl_FragColor.g += (0.05 * vRandChosenColor.g);\n" +
                "    gl_FragColor.b += (0.05 * vRandChosenColor.b);\n" +
        "}\n";

Общий фрагмент содержит параметр uNoiseFactor, который обновляется при каждом рендеринге основным приложением:

float noiseValue = (float)(mRand.nextInt() % 1000)/1000;
int noiseFactorUniformHandle = GLES20.glGetUniformLocation( mProgram, "sNoiseTextureUnit");
GLES20.glUniform1f(noiseFactorUniformHandle, noiseFactor);

0

Я перевел одну из Java-реализаций Кена Перлина в GLSL и использовал ее в нескольких проектах на ShaderToy.

Ниже приведена интерпретация GLSL, которую я сделал:

int b(int N, int B) { return N>>B & 1; }
int T[] = int[](0x15,0x38,0x32,0x2c,0x0d,0x13,0x07,0x2a);
int A[] = int[](0,0,0);

int b(int i, int j, int k, int B) { return T[b(i,B)<<2 | b(j,B)<<1 | b(k,B)]; }

int shuffle(int i, int j, int k) {
    return b(i,j,k,0) + b(j,k,i,1) + b(k,i,j,2) + b(i,j,k,3) +
        b(j,k,i,4) + b(k,i,j,5) + b(i,j,k,6) + b(j,k,i,7) ;
}

float K(int a, vec3 uvw, vec3 ijk)
{
    float s = float(A[0]+A[1]+A[2])/6.0;
    float x = uvw.x - float(A[0]) + s,
        y = uvw.y - float(A[1]) + s,
        z = uvw.z - float(A[2]) + s,
        t = 0.6 - x * x - y * y - z * z;
    int h = shuffle(int(ijk.x) + A[0], int(ijk.y) + A[1], int(ijk.z) + A[2]);
    A[a]++;
    if (t < 0.0)
        return 0.0;
    int b5 = h>>5 & 1, b4 = h>>4 & 1, b3 = h>>3 & 1, b2= h>>2 & 1, b = h & 3;
    float p = b==1?x:b==2?y:z, q = b==1?y:b==2?z:x, r = b==1?z:b==2?x:y;
    p = (b5==b3 ? -p : p); q = (b5==b4 ? -q : q); r = (b5!=(b4^b3) ? -r : r);
    t *= t;
    return 8.0 * t * t * (p + (b==0 ? q+r : b2==0 ? q : r));
}

float noise(float x, float y, float z)
{
    float s = (x + y + z) / 3.0;  
    vec3 ijk = vec3(int(floor(x+s)), int(floor(y+s)), int(floor(z+s)));
    s = float(ijk.x + ijk.y + ijk.z) / 6.0;
    vec3 uvw = vec3(x - float(ijk.x) + s, y - float(ijk.y) + s, z - float(ijk.z) + s);
    A[0] = A[1] = A[2] = 0;
    int hi = uvw.x >= uvw.z ? uvw.x >= uvw.y ? 0 : 1 : uvw.y >= uvw.z ? 1 : 2;
    int lo = uvw.x <  uvw.z ? uvw.x <  uvw.y ? 0 : 1 : uvw.y <  uvw.z ? 1 : 2;
    return K(hi, uvw, ijk) + K(3 - hi - lo, uvw, ijk) + K(lo, uvw, ijk) + K(0, uvw, ijk);
}

Я перевел это из Приложения B из Главы 2 Аппаратуры Шума Кена Перлина в этом источнике:

https://www.csee.umbc.edu/~olano/s2002c36/ch02.pdf

Вот публичный оттенок, который я сделал для Shader Toy, который использует опубликованную функцию шума:

https://www.shadertoy.com/view/3slXzM

Некоторые другие хорошие источники, которые я нашел по теме шума во время моего исследования, включают:

https://thebookofshaders.com/11/

https://mzucker.github.io/html/perlin-noise-math-faq.html

https://rmarcus.info/blog/2018/03/04/perlin-noise.html

http://flafla2.github.io/2014/08/09/perlinnoise.html

https://mrl.nyu.edu/~perlin/noise/

https://rmarcus.info/blog/assets/perlin/perlin_paper.pdf

https://developer.nvidia.com/gpugems/GPUGems/gpugems_ch05.html

Я настоятельно рекомендую книгу шейдеров, поскольку она не только дает отличное интерактивное объяснение шума, но и другие концепции шейдеров.

РЕДАКТИРОВАТЬ:

Может быть в состоянии оптимизировать переведенный код с помощью некоторых аппаратно-ускоренных функций, доступных в GLSL. Обновлю этот пост, если я в конечном итоге сделаю это.


Кроме того, я почти уверен, что Perlin / Simplex Noise все еще псевдослучайный. Насколько я помню, интересно то, что вы можете наложить и «увеличить» шум на разных уровнях, чтобы он казался очень плавным. Не цитируйте меня об этом, но подумайте над чем.
Андрей Месервый

@Zibri К сожалению, я не очень знаком с прямыми командами C или .sh. Но похоже, что функция - это просто генератор псевдослучайных чисел, а не функция шума. Также имейте в виду, что пиксельные шейдеры glsl работают непосредственно на графическом процессоре. У вас не будет доступа ни к одной из этих дополнительных библиотек или возможностей ЦП, которые могут быть доступны в C.
Эндрю Месерви

В книге шейдеров есть отличное объяснение того, как Simplex Noise является более эффективной версией Perlin Noise из-за перекоса сетки и менее необходимых вычислений на точку. Определенно стоит прочитать.
Андрей Месервый

также см. главы о фрактальном броуновском движении и воронуазе
Эндрю Месерви

Эндрю Месерви: библиотеки не нужны ... моя функция шума очень проста: 2 64-битных целых числа - это состояния x (n) и x (n-1). Простая и быстрая формула: x (n + 1) = ROTR ( х (п) + х (п-1), 8). если вы клонируете мой мерзавец и запускаете его, вы увидите его в действии.
Зибри
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.