Випадкові / шумові функції для GLSL


179

Оскільки постачальники драйверів GPU зазвичай не noiseXнамагаються реалізовувати в GLSL, я шукаю набір функціональних функцій "графічної рандомізації swiss Army ніж" , бажано оптимізованих для використання в шейдерах GPU. Я віддаю перевагу GLSL, але код для мене зробить будь-яка мова, я нормально перекладаю його самостійно на GLSL.

Зокрема, я б очікував:

а) Псевдовипадкові функції - 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 Noise - знову ж, N-мірний, + - рівномірний розподіл, з обмеженим набором значень і, добре, добре виглядає (деякі варіанти налаштування зовнішності, наприклад, рівні Перліна, також можуть бути корисними). Я очікую, що підписи:

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 від Стефана Густавсона.


14
Як ви користуєтесь vec2 co? це діапазон? насіння?
Росс

12
Остерігайтеся низькоточних шейдерів з плаваючою комою з цим алгоритмом (наприклад, AR3 Малі S3): stackoverflow.com/questions/11293628/… . Проект github.com/ashima/webgl-noise , схоже, не має проблем.
PT

4
FWIW, функція , описана тут обговорюється більш детально тут .
Loomchild

3
FYI: Розподіл цієї функції жахливий.
Тара

3
Я новачок в GLSL, чи може хтось пояснити, чому co.xyвикористовується, а не co?
келін

83

Мені здається, ви могли б використовувати просту цілу хеш-функцію і вставити результат у мантісу поплавця. IIRC специфікація GLSL гарантує 32-бітні цілі числа, які не підписуються, і IEEE binary32 float представлення, тому воно повинно бути ідеально портативним.

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

Одним з недоліків є те, що для нього потрібен GLSL v3.30. І хоча це здається досить швидким, я не емпірично оцінив його ефективність. Shader Analyzer AMD вимагає 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.
Восени,

Ну, це досить добре для більшості програм, які не потребують рівномірності. :-)
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 також незавершена, враховуючи, що не постачається насіння (uv не є насінням - те саме для кожного кадру), і не працює з чіпсетами з низькою точністю. Золотий шум працює за низькою точністю за замовчуванням (набагато швидше).


Дякуємо, що опублікували це. Чи розглядаєте ви розміщення запущеної версії, наприклад, на 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.
користувач362515

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

Ви можете знайти пояснення у вдосконаленні канонічного однолінійного rand GLSL () для OpenGL ES 2.0


Я переглянув статтю, але я все ще не впевнений, чи 3,14 modнаближений до pi?
Каан Е.

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
Золотий шум (вище) тоді, очевидно, найшвидший, оскільки він має набагато менше операцій і виконує лише один хеш - цей викликає хеш-фукцію 8 разів, виконуючи вкладені лінійні інтерполяції (lerps). Також цей має нижчий розподіл, особливо при низькій точності.
Домінік Серісано

1
Ну добре, це графік типу перлін-шуму від шадерто Ініго Квілез. Хороший код Домінік погано перевірив його l8r
зрозумілий

@Fabrice Ви, здається, не розумієте питання ОП, моєї відповіді, мого коду чи мого коментаря. Золотий шум безперервний за визначенням ОП - він приймає uv та насіння та доводить це, надаючи шейдер. Все про ваш коментар невірно. Ви продовжуєте плутати хеш-функції з псевдослучайними шумовими функціями. Вони не однакові. Функції шуму не вимагають генерування унікальних ідентифікаторів, таких як хеш-функції (фактична вся точка хешування).
Домінік Серісано

Будь ласка, будь ласка, Домінік, прочитайте більше та дізнайтеся більше, перш ніж вимагати речей про терміни, які, на вашу думку, ви розумієте, хоча це не так. Не тільки ці терміни абсолютно точні і добре визначаються в літературі, а також я працюю на місцях, але й ОП доводить, що він розумів терміни на прикладах, які він подав після. Підказка: "безперервний" + "шум" + "як Перлін". en.wikipedia.org/wiki/Perlin_noise
Fabrice NEYRET

Безперервна ситуація є випадком додавання цикличного пункту, багато шумових функцій циклічно та деградують певним чином через бітове округлення, особливо для графіки. Хлопці - це просто відомий з вас зв'язок, використовуйте свій час для важливих досліджень.
com.зрозумілий

1

Пряма, зубчаста версія 1d Perlin, по суті випадковий lfo зигзаг.

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, підручнику, і voronoi тощо, у нього є повна швидка реалізація та коди для них.


1

хеш: На сьогодні 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

Нижче див. Приклад, як додати білий шум до відредагованої текстури. Рішення полягає у використанні двох текстур: оригінальний та чисто білий шум, як цей: wiki white noise

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 все ще є псевдо випадковим. Як я пам’ятаю, цікавим є те, що ви можете шарувати та «масштабувати» шум на різних рівнях, щоб він виглядав дуже безшовно. Не цитуйте мене з цього приводу, але щось над цим подумайте.
Ендрю Месерві

@Zibri На жаль, я не дуже добре знайомий з прямими командами C або .sh. Але схоже, що функція - це просто псевдовипадковий генератор чисел, а не функція шуму. Також пам’ятайте, що піксельні шейдери glsl працюють безпосередньо на gpu. Ви не матимете доступу до жодної із тих додаткових бібліотек чи можливостей процесора, які можуть бути доступні в C.
Andrew Meservy

У книзі шейдерів є чудове пояснення того, як Simplex Noise є більш ефективною версією Perlin Noise за рахунок перекосу сітки та менше необхідних обчислень на очко. Однозначно варто прочитати.
Ендрю Месерві

також дивіться розділи про фрактальний броунівський рух та воруназ
Ендрю Месерві

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