Pular para o conteúdo
Fundamento · RuídoIntermediário

Ruído de valor

Coloque um valor aleatório em cada canto de uma grade e misture entre eles, para ter uma aleatoriedade suave que varia pelo espaço.

Como funciona

Um hash dá a cada entrada um valor sem relação com o vizinho, então pixels lado a lado saltam do escuro para o claro ao acaso. A maioria das coisas da natureza não é assim: uma nuvem, uma mancha ou a superfície de uma rocha variam suavemente de um ponto para o outro e só parecem aleatórias numa escala maior. O ruído é aleatoriedade com essa propriedade. O ruído de valor é a forma mais simples dele: passe os cantos de uma grade pelo hash e misture entre eles.

Cantos e mistura

Separe a posição em célula e posição dentro da célula, a mesma divisão com floor e fract usada no ladrilhamento. Cada um dos quatro cantos da célula recebe um valor aleatório do hash. O valor do pixel é uma mistura dos quatro, ponderada pela proximidade de cada um:

float valueNoise(vec2 p) {
    vec2 i = floor(p); // o canto inferior esquerdo da célula
    vec2 f = fract(p); // onde o pixel está dentro da célula, 0..1

    float a = hash21(i);                   // inferior esquerdo
    float b = hash21(i + vec2(1.0, 0.0));  // inferior direito
    float c = hash21(i + vec2(0.0, 1.0));  // superior esquerdo
    float d = hash21(i + vec2(1.0, 1.0));  // superior direito

    vec2 u = f * f * (3.0 - 2.0 * f);      // transição do smoothstep, por eixo
    return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
}

Os dois mix de dentro misturam ao longo de x nas bordas de baixo e de cima, e o de fora mistura esses dois resultados ao longo de y. Isso é interpolação bilinear, feita com mix. O hash21 é o hash de seno do fundamento de hash; qualquer hash que leve uma célula a 0..1 serve. Células vizinhas compartilham os cantos, então o resultado é contínuo nas bordas: um pixel na borda recebe os mesmos dois valores de canto pelos dois lados.

A escala de p define o tamanho dos detalhes. valueNoise(uv * 8.0) tem umas oito manchas de um lado a outro da tela; uv * 40.0 deixa o ruído fino.

Por que a curva de transição

Misturar com f direto é interpolação linear. Ela é contínua, mas a inclinação dá um salto em toda borda de célula, e o olho percebe esses vincos como uma grade apagada de linhas retas e losangos. Passar o f antes por f * f * (3.0 - 2.0 * f), a mesma cúbica que o smoothstep usa, zera a inclinação nas duas pontas da célula, e as células vizinhas se encontram sem quina.

A segunda derivada da cúbica ainda salta nas bordas. Numa cor chapada isso é invisível, mas aparece quando o ruído vira altura e recebe luz, porque a iluminação depende da inclinação e da rapidez com que ela muda. A transição quíntica resolve isso, ao custo de mais umas multiplicações:

vec2 u = f * f * f * (f * (f * 6.0 - 15.0) + 10.0); // 6f⁵ − 15f⁴ + 10f³

Use a cúbica para cores e máscaras, e a quíntica quando o ruído virar geometria ou normal.

Como ele fica, e seus limites

O ruído de valor vai de 0 a 1, embora valores perto das pontas sejam raros, porque exigem quatro cantos perto da mesma ponta. Ele fica macio e um pouco quadriculado: as manchas claras e escuras ficam nos cantos da grade e, se você procurar, dá para ver o alinhamento dela. Isso é aceitável numa máscara de dissolve, num tremular ou em qualquer coisa que se mexa, e ele é o ruído mais barato que existe: quatro hashes e três mixes.

Quando a grade aparece, a solução de sempre é o ruído de gradiente, o próximo fundamento, que esconde melhor a grade com um pouco mais de conta. Somar várias cópias em escalas diferentes de qualquer um dos dois dá o detalhe mais rico do fBm.

Usando

O ruído é um valor de 0 a 1, então se encaixa em tudo dos fundamentos de funções de forma. Aplique um limiar com smoothstep para fazer manchas, compare com um valor que se move para uma revelação ou use-o num mix entre duas cores:

float n = valueNoise(uv * 6.0);
// uma borda suave que avança conforme progress sobe
float mask = smoothstep(progress - 0.05, progress + 0.05, n);
vec3 color = mix(vec3(0.1, 0.3, 0.1), vec3(0.4, 0.6, 0.2), n);

Anime movendo a entrada (uv * 6.0 + vec2(time * 0.2, 0.0) faz o ruído deslizar) ou misturando dois campos de ruído ao longo do tempo. Somar o tempo a um só eixo de um ruído 2D apenas desliza o padrão; para um ruído que muda no lugar, você precisa de um ruído 3D com o tempo como terceiro eixo, que funciona do mesmo jeito, com oito cantos em vez de quatro.

Leitura complementar