Funções aleatórias / de ruído para GLSL

179

Como os fornecedores de drivers de GPU geralmente não se preocupam em implementar noiseXno GLSL, estou procurando um conjunto de funções de utilitário "faca de exército suíço de randomização gráfica" , de preferência otimizado para uso em shaders de GPU. Eu prefiro o GLSL, mas o código que qualquer idioma fará por mim, estou bem em traduzi-lo sozinho para o GLSL.

Especificamente, eu esperaria:

a) Funções pseudo-aleatórias - N-dimensional, distribuição uniforme acima de [-1,1] ou acima de [0,1], calculada a partir da semente dimensional M (idealmente qualquer valor, mas não tenho problema em restringir a semente digamos 0..1 para distribuição uniforme de resultados). Algo como:

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

b) Ruído contínuo como Perlin Noise - novamente, distribuição N-dimensional, + - uniforme, com conjunto restrito de valores e, bem, com boa aparência (algumas opções para configurar a aparência como níveis de Perlin também podem ser úteis). Eu esperaria assinaturas como:

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

Eu não sou muito fã da teoria da geração de números aleatórios, então eu gostaria muito de encontrar uma solução pré-fabricada , mas também gostaria de respostas como "aqui está uma 1D rand () muito boa e eficiente () e deixe-me explicar você como fazer um bom rand N-dimensional () em cima dele ... " .

Kos
fonte

Respostas:

263

Para coisas muito simples de aparência pseudo-aleatória, eu uso esse oneliner que encontrei na internet em algum lugar:

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

Você também pode gerar uma textura de ruído usando o PRNG que desejar, depois faça o upload da maneira normal e faça uma amostra dos valores em seu shader; Posso desenterrar um exemplo de código mais tarde, se desejar.

Além disso, confira este arquivo para implementações GLSL de ruído Perlin e Simplex, de Stefan Gustavson.

appas
fonte
14
Como você usa vec2 co? é o alcance? semente?
21712 Ross
12
Cuidado com os shaders de fragmento de ponto flutuante de baixa precisão com este algoritmo (por exemplo, o ARM Mali da S3): stackoverflow.com/questions/11293628/… . O projeto github.com/ashima/webgl-noise parece não ter problemas.
PT
4
FWIW, a função descrita aqui é discutida em mais detalhes aqui .
Loomchild
3
FYI: A distribuição dessa função é horrível.
Tara
3
Eu sou newb no GLSL, alguém pode explicar por que co.xyé usado, em vez de co?
Kelin
83

Ocorre-me que você poderia usar uma função hash inteira simples e inserir o resultado na mantissa de um flutuador. IIRC, a especificação GLSL garante números inteiros não assinados de 32 bits e representação flutuante IEEE binary32, portanto deve ser perfeitamente portátil.

Eu tentei agora. Os resultados são muito bons: parece exatamente estático em todas as entradas que tentei, sem padrões visíveis. Em contraste, o trecho popular de sin / fract tem linhas diagonais bastante pronunciadas na minha GPU, com as mesmas informações.

Uma desvantagem é que requer o GLSL v3.30. E, embora pareça rápido o suficiente, não quantifiquei empiricamente seu desempenho. O Shader Analyzer da AMD reivindica 13,33 pixels por relógio para a versão vec2 em um HD5870. Compare com 16 pixels por relógio para o snippet sin / fract. Portanto, é certamente um pouco mais lento.

Aqui está a minha implementação. Deixei em várias permutações da idéia para facilitar a derivação de suas próprias funções.

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

Captura de tela:

Saída aleatória (vec3) no static.frag

Inspecionei a captura de tela em um programa de edição de imagens. Existem 256 cores e o valor médio é 127, o que significa que a distribuição é uniforme e cobre o intervalo esperado.

Espacial
fonte
17
+1 para uma boa ideia e implementação. Eu questionaria a afirmação de que, como existem 256 cores e o valor médio é 127, a distribuição deve ser uniforme (no sentido estrito). Pode ser uniforme, mas acho que ainda não sabemos disso. Por exemplo, uma distribuição de curva de sino poderia ter a mesma média e número de cores, mas não seria uniforme.
Larsh
Votou esta nota pelo motivo exposto por @LarsH.
Autumnsault 24/02
Bem, é bom o suficiente para a maioria dos aplicativos que não precisam de uniformidade. :-)
itmuckel
5
Parece ser muito uniforme, pela minha percepção do histograma ... Eu diria que é bom o suficiente para a maioria dos aplicativos que também precisam de uniformidade. (Os únicos valores que parecem ser gerados menos do que os outros são 0 e 255)
leviathanbadger
Obrigado. Minha probabilidade está enferrujada. Tendo examinado o conjunto de instruções da GCN, isso deve ser muito rápido em hardware mais recente, porque eles suportam diretamente operações de campo de bits em seus conjuntos de instruções. Os testes que fiz foram executados em hardware mais antigo.
Spatial
73

A implementação de Gustavson usa uma textura 1D

Não, não desde 2005. É só que as pessoas insistem em baixar a versão antiga. A versão que está no link que você forneceu usa apenas texturas 2D de 8 bits.

A nova versão de Ian McEwan, do Ashima e de mim, não usa uma textura, mas roda em torno da metade da velocidade em plataformas de desktop típicas com muita largura de banda de textura. Nas plataformas móveis, a versão sem textura pode ser mais rápida, porque a textura costuma ser um gargalo significativo.

Nosso repositório de origem mantido ativamente é:

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

Uma coleção das versões de ruído sem textura e com textura está aqui (usando apenas texturas 2D):

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

Se você tiver alguma dúvida específica, sinta-se à vontade para me enviar um e-mail diretamente (meu endereço de e-mail pode ser encontrado nas classicnoise*.glslfontes).

Stefan Gustavson
fonte
4
Sim, a implementação à qual estou me referindo, seu código no davidcornette.com ao qual @dep está vinculado usa uma textura 1D: glBindTexture(GL_TEXTURE_1D, *texID);etc. Não está claro o que você quer dizer com "o link que você forneceu", pois você cita minha resposta mas essa resposta não estava vinculada à sua implementação. Atualizarei minha resposta para esclarecer a que me refiro e refletir as novas informações que você forneceu. Caracterizar as pessoas como "insistentes" no download da versão antiga é uma distorção que você não acredita.
Larsh
1
PS Você pode escrever para David Cornette (ele tem informações de contato em davidcornette.com ) e pedir que ele altere seu link em davidcornette.com/glsl/links.html para vincular ao seu repositório de origem. Vou mandar um email para ele também.
Larsh
1
PPS Você pode esclarecer qual versão usa apenas texturas 2D de 8 bits? Parece que pode ser uma boa opção para certas plataformas ...
Larsh
31

Ruído de ouro

// Gold Noise ©2015 [email protected]
// - 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);
}

Veja Gold Noise no seu navegador agora mesmo!

insira a descrição da imagem aqui

Essa função melhorou a distribuição aleatória da função atual na resposta @appas em 9 de setembro de 2017:

insira a descrição da imagem aqui

A função @appas também está incompleta, pois não há semente fornecida (uv não é uma semente - o mesmo para todos os quadros) e não funciona com chipsets de baixa precisão. O Gold Noise é executado com baixa precisão por padrão (muito mais rápido).

Dominic Cerisano
fonte
Obrigado por postar isso. Você consideraria publicar uma versão executável, por exemplo, no shadertoy.com, para que as pessoas possam experimentá-la no navegador?
LarsH 16/03
@snb Shadertoy.com está em manutenção este mês, tenha um pouco de paciência. Também documentei claramente o requisito para valores irracionais de sementes no código lá. Como o ruído dourado retorna um escalar, a construção de vetores com ele é trivial e também documentada no código.
Dominic Cerisano
7
Eu não acho que isso seja diferente de outras funções de ruído. Qual é a sua prova de que isso possui propriedades especiais. só porque você usa muitos números irracionais não o torna especial.
M.kazem Akhgary
2
@ Dominic: "Possui distribuição superior a funções similares": isso deve ser comprovado. tan () está realmente mal condicionado. tan () próximo de pi / 2 e sqrt () próximo de zero provavelmente produzirão resultados diferentes em diferentes hardwares, pois todas as fraturas (não lineares * grandes) são baseadas em bits menos significativos. Valores de entrada pequenos ou altos também terão impacto. Além disso, a dinâmica de bits provavelmente varia muito dependendo dos locais.
Fabrice NEYRET
2
NB: Atualmente o GLSL tem números inteiros, portanto não há mais motivo para não usar geradores de hash "sérios" baseados em int quando a distribuição de qualidade (e dinâmica) é necessária, com desempenhos semelhantes. (exceto para dispositivos de gama baixa).
Fabrice NEYRET
12

Também existe uma boa implementação descrita aqui por McEwan e @StefanGustavson que parece com o ruído Perlin, mas "não requer nenhuma configuração, ou seja, não texturas nem matrizes uniformes. Basta adicioná-lo ao código-fonte do shader e chamá-lo onde quiser".

Isso é muito útil, especialmente considerando que a implementação anterior de Gustavson, à qual @dep se vinculou, usa uma textura 1D, que não é suportada no GLSL ES (a linguagem shader do WebGL).

LarsH
fonte
1
Essa é a melhor resposta para a solicitação de tipo de ruído do OP! Aqui está um link direto github.com/ashima/webgl-noise . Existem versões 2d, 3d e 4d prontas para o código GLSL 120.
user362515
3

Use isto:

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

Não use isso:

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

Você pode encontrar a explicação em Melhorias no GLSL canônico de uma linha rand () para OpenGL ES 2.0

hoangdado
fonte
Passei os olhos pelo artigo, mas ainda não tenho certeza, é 3,14 em moduma aproximação de pi?
Kaan E.
2

Acabei de encontrar esta versão do ruído 3D para GPU, supostamente é a mais rápida disponível:

#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
compreensível
fonte
1
O Gold Noise (acima) é obviamente mais rápido, uma vez que possui muito menos operações e executa apenas um hash - este chama sua função de hash 8 vezes, enquanto executa interpolações lineares aninhadas (lerps). Também este possui distribuição inferior, especialmente com baixa precisão.
Dominic Cerisano 31/07
1
Oh, bom ponto, é um gráfico do tipo perlin noise de shadertoh de Inigo Quilez. Código agradável Dominic ill check it l8r
com.prehensible
@Fabrice Você parece não entender a pergunta do OP, minha resposta, meu código ou meu comentário. O Gold Noise é contínuo pela definição dos OPs - aceita uv e uma semente e prova isso fornecendo um sombreador. Tudo no seu comentário está errado. Você mantém funções de hash confusas com funções de ruído pseudo-aleatório. Eles não são os mesmos. As funções de ruído não precisam gerar identificadores exclusivos, como funções de hash (o ponto real real do hash).
Dominic Cerisano
Por favor, por favor, por favor, Dominic, leia mais e saiba mais antes de reivindicar coisas sobre termos que você acha que entende, embora não seja o caso. Não apenas esses termos são totalmente precisos e bem definidos na literatura, mais eu trabalho no campo, mas também o OP prova que ele entendeu os termos pelos exemplos que deu depois. Dica: "contínuo" + "ruído" + "como Perlin". pt.wikipedia.org/wiki/Perlin_noise
Fabrice NEYRET 13/09
Contínuo é o único caso de se adicionar uma cláusula de loop, muitas funções de ruído fazem um loop e se degradam de uma certa maneira devido ao arredondamento de bits, especialmente para gráficos. Pessoal, é apenas uma comunicação interrompida de você, use seu tempo para pesquisas importantes.
com.prehensible
1

Uma versão reta e irregular do 1d Perlin, essencialmente um zigue-zague aleatório do 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);
}

Eu também encontrei o 1-2-3-4d ruído perlin no site do tutorial do proprietário shadertoy, inigo quilez perlin, e voronoi e assim por diante, ele tem implementações e códigos rápidos completos para eles.

compreensível
fonte
1

hash: Atualmente, o webGL2.0 existe, portanto, números inteiros estão disponíveis no (w) GLSL. -> para hash portátil de qualidade (a um custo semelhante ao dos feixes flutuantes feios), agora podemos usar técnicas de hash "sérias". O QI implementou alguns em https://www.shadertoy.com/view/XlXcW4 (e mais)

Por exemplo:

  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));
}
Fabrice NEYRET
fonte
0

Veja abaixo um exemplo de como adicionar ruído branco à textura renderizada. A solução é usar duas texturas: ruído branco puro e original, como este: ruído branco wiki

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";

O fragmento compartilhado contém o parâmetro uNoiseFactor, que é atualizado a cada renderização pelo aplicativo principal:

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

Traduzi uma das implementações Java de Ken Perlin para GLSL e a usei em alguns projetos no ShaderToy.

Abaixo está a interpretação GLSL que fiz:

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

Traduzi-o do Apêndice B do Capítulo 2 do Noise Hardware de Ken Perlin nesta fonte:

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

Aqui está uma sombra pública que fiz no Shader Toy que usa a função de ruído publicada:

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

Algumas outras boas fontes que encontrei sobre o ruído durante minha pesquisa incluem:

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

Eu recomendo o livro de shaders, pois ele não apenas fornece uma ótima explicação interativa do ruído, mas também outros conceitos de shader.

EDITAR:

Talvez seja possível otimizar o código traduzido usando algumas das funções aceleradas por hardware disponíveis no GLSL. Atualizará este post se eu acabar fazendo isso.

Andrew Meservy
fonte
Além disso, tenho certeza de que o Perlin / Simplex Noise ainda é pseudo-aleatório. Pelo que me lembro, o interessante é que você pode colocar em camadas e "ampliar" o ruído em diferentes níveis para torná-lo muito uniforme. Não me cite sobre isso, mas algo em que pensar.
Andrew Meservy
@ Zibri Infelizmente, eu não estou super familiarizado com comandos diretos C ou .sh. Mas parece que a função é simplesmente um gerador de números pseudo-aleatórios e não uma função de ruído. Lembre-se também de que os shaders de pixel glsl são executados diretamente na gpu. Você não terá acesso a qualquer uma dessas bibliotecas extra ou capacidades de CPU que pode estar disponível em C.
Andrew Meservy
O Book Of Shaders tem uma ótima explicação sobre como o Simplex Noise é uma versão mais eficiente do Perlin Noise devido ao desvio da grade e aos cálculos menos necessários por ponto. Definitivamente vale a pena ler.
Andrew Meservy
veja também os capítulos sobre o movimento Browniano fractal e voronoise
Andrew Meservy
Andrew Meservy: nenhuma biblioteca é necessária ... minha função de ruído é muito simples: 2 entradas de 64 bits são o estado x (n) ex (n-1). A fórmula simples e rápida é x (n + 1) = ROTR ( x (n) + x (n-1), 8). se você clonar meu git e executá-lo, você o verá em ação.
Zibri