Como você gera ruído Perlin inclinável?

127

Palavras-chave:

Eu gostaria de gerar ruído Perlin inclinável. Estou trabalhando com as PerlinNoise*() funções de Paul Bourke , que são assim:

// alpha is the "division factor" (how much to damp subsequent octaves with (usually 2))
// beta is the factor that multiplies your "jump" into the noise (usually 2)
// n is the number of "octaves" to add in
double PerlinNoise2D(double x,double y,double alpha,double beta,int n)
{
   int i;
   double val,sum = 0;
   double p[2],scale = 1;

   p[0] = x;
   p[1] = y;
   for (i=0;i<n;i++) {
      val = noise2(p);
      sum += val / scale;
      scale *= alpha;
      p[0] *= beta;
      p[1] *= beta;
   }
   return(sum);
}

Usando código como:

real val = PerlinNoise2D( x,y, 2, 2, 12 ) ; // test

return val*val*skyColor + 2*val*(1-val)*gray + (1-val)*(1-val)*cloudColor ;

Dá céu como

indelével

O que não é inclinável.

Os valores de pixel são 0-> 256 (largura e altura), e pixel (0,0) usa (x, y) = (0,0) e pixel (256,256) usa (x, y) = (1,1)

Como posso torná-lo inclinável?

bobobobo
fonte
14
Apenas para sua informação, o que você tem aqui não é ruído Perlin; é ruído fractal. O ruído Perlin é provavelmente a função "noise2" que gera cada oitava do ruído fractal.
Nathan Reed

Respostas:

80

Existem duas partes para produzir um ruído fBm perfeitamente inclinável como este. Primeiro, é necessário tornar a função de ruído Perlin inclinável. Aqui está um código Python para uma função de ruído Perlin simples que funciona com qualquer período de até 256 (você pode estendê-lo trivialmente o quanto quiser, modificando a primeira seção):

import random
import math
from PIL import Image

perm = range(256)
random.shuffle(perm)
perm += perm
dirs = [(math.cos(a * 2.0 * math.pi / 256),
         math.sin(a * 2.0 * math.pi / 256))
         for a in range(256)]

def noise(x, y, per):
    def surflet(gridX, gridY):
        distX, distY = abs(x-gridX), abs(y-gridY)
        polyX = 1 - 6*distX**5 + 15*distX**4 - 10*distX**3
        polyY = 1 - 6*distY**5 + 15*distY**4 - 10*distY**3
        hashed = perm[perm[int(gridX)%per] + int(gridY)%per]
        grad = (x-gridX)*dirs[hashed][0] + (y-gridY)*dirs[hashed][1]
        return polyX * polyY * grad
    intX, intY = int(x), int(y)
    return (surflet(intX+0, intY+0) + surflet(intX+1, intY+0) +
            surflet(intX+0, intY+1) + surflet(intX+1, intY+1))

O ruído Perlin é gerado a partir de um somatório de pequenas "excedentes", que são o produto de um gradiente orientado aleatoriamente e uma função polinomial de queda separável. Isso fornece uma região positiva (amarela) e uma região negativa (azul)

Núcleo

As sobras têm uma extensão de 2x2 e estão centradas nos pontos inteiros da rede, de modo que o valor do ruído Perlin em cada ponto no espaço é produzido pela soma das sobras nos cantos da célula que ocupa.

Somatório

Se você definir as direções do gradiente com um certo período, o ruído será reduzido com o mesmo período. É por isso que o código acima leva o módulo de coordenadas da treliça o período antes de colocá-lo na tabela de permutação.

O outro passo é que, ao somar as oitavas, você desejará escalar o período com a frequência da oitava. Basicamente, você desejará que cada oitava agrupe toda a imagem apenas uma vez, e não várias vezes:

def fBm(x, y, per, octs):
    val = 0
    for o in range(octs):
        val += 0.5**o * noise(x*2**o, y*2**o, per*2**o)
    return val

Coloque isso junto e você terá algo como isto:

size, freq, octs, data = 128, 1/32.0, 5, []
for y in range(size):
    for x in range(size):
        data.append(fBm(x*freq, y*freq, int(size*freq), octs))
im = Image.new("L", (size, size))
im.putdata(data, 128, 128)
im.save("noise.png")

Ruído fBm inclinável

Como você pode ver, isso realmente combina perfeitamente:

Ruído fBm, lado a lado

Com alguns pequenos ajustes e mapeamento de cores, aqui está uma imagem de nuvem lado a lado 2x2:

Nuvens!

Espero que isto ajude!

Boojum
fonte
3
eu não sou um cara python, então eu pergunto, como x*2**oconverter para C? é isso x*pow(2,o)ou pow(x*2,o)?
Idev
7
x*pow(2, o), já que a exponenciação tem maior precedência que a multiplicação.
John Calsbeek
11
alguém poderia converter isso em C? Eu tenho grandes problemas para entender esse código, pois nunca fiz nada com python. por exemplo, o que é avalor? e não tenho certeza de como as funções são convertidas em C ... recebo apenas linhas retas na saída.
Idev 12/02
11
Esta é definitivamente a melhor solução, desde que você esteja bem com o domínio do seu ruído ligado ao formato do seu ladrilho. Por exemplo, isso não permite rotações arbitrárias. Mas se você não precisa disso, esta é a resposta ideal.
John Calsbeek
11
Nota: se você deseja gerar outro tamanho que não seja 128, NÃO altere os valores numéricos na linha im.putdata(data, 128, 128). : (Para aqueles não familiarizados com python ou PIL eles significam escala e offset, não tamanho da imagem.)
Antti Kissaniemi
87

Aqui está uma maneira bastante inteligente de usar o ruído 4D Perlin.

Basicamente, mapeie a coordenada X do seu pixel para um círculo 2D e a coordenada Y do seu pixel para um segundo círculo 2D e coloque esses dois círculos ortogonais entre si no espaço 4D. A textura resultante é inclinável, não tem distorção óbvia e não se repete da maneira que uma textura espelhada faria.

Copie e cole o código do artigo:

for x=0,bufferwidth-1,1 do
    for y=0,bufferheight-1,1 do
        local s=x/bufferwidth
        local t=y/bufferheight
        local dx=x2-x1
        local dy=y2-y1

        local nx=x1+cos(s*2*pi)*dx/(2*pi)
        local ny=y1+cos(t*2*pi)*dy/(2*pi)
        local nz=x1+sin(s*2*pi)*dx/(2*pi)
        local nw=y1+sin(t*2*pi)*dy/(2*pi)

        buffer:set(x,y,Noise4D(nx,ny,nz,nw))
    end
end
John Calsbeek
fonte
3
Esta é definitivamente a resposta certa. Adicionar dimensões é um velho truque matemático. Olinde Rodrigues docet (Sir WR Hamilton DOCET também, mas um pouco menos)
FXIII
@FxIII, você sabe como esse Noise4D () deve ser implementado? Eu gostaria de tentar isso, mas não tenho idéia de como esse Noise4D () deve funcionar.
Idev
4
Você pode usar qualquer função de ruído 4D. Ruído simplex seria minha recomendação. webstaff.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf
John Calsbeek
2
obrigado john! conseguiu funcionar, doce! ninguém disse isso, mas: o x1, y1, x2, y2 parece ser algum tipo de escala, a maior distância, o ruído detalhado. se isso ajudar alguém.
Idev
5
Observe que isso é topologicamente equivalente à resposta de bobobobo: seu mapeamento incorpora um 2-toro em um ℝ⁴, o que é possível sem as distorções métricas que você inevitavelmente obtém ao incorporá-lo em ³³.
leftaroundabout
22

OK, entendi. A resposta é andar em um toro no ruído 3D, gerando uma textura 2D a partir dele.

toro envolve 2 dirs

Código:

Color Sky( double x, double y, double z )
{
  // Calling PerlinNoise3( x,y,z ),
  // x, y, z _Must be_ between 0 and 1
  // for this to tile correctly
  double c=4, a=1; // torus parameters (controlling size)
  double xt = (c+a*cos(2*PI*y))*cos(2*PI*x);
  double yt = (c+a*cos(2*PI*y))*sin(2*PI*x);
  double zt = a*sin(2*PI*y);
  double val = PerlinNoise3D( xt,yt,zt, 1.5, 2, 12 ) ; // torus

  return val*val*cloudWhite + 2*val*(1-val)*gray + (1-val)*(1-val)*skyBlue ;
}

Resultados:

Uma vez:

céu inclinável

E lado a lado:

mostrando telhas

bobobobo
fonte
6
Isso meio que funciona, mas parece que você está recebendo um monte de distorção devido à curvatura do toro.
Nathan Reed
11
você pode realmente apenas modular a posição, mas eu amo todas as respostas incríveis / criativas para esta pergunta. Tantas maneiras diferentes de fazer a mesma coisa.
notei que você realmente não quer usar valores 0-1, mas 0-0.9999 ... valores! então você usaria: x / largura, y / altura etc., caso contrário, as costuras não coincidem (faz com que as bordas opostas tenham exatamente os mesmos pixels). também parece que a função PerlinNoise3D () também precisa ser fixada para o valor do resultado, ou alguns valores de pixel excedem.
Idev
@ Nathan, você sabe como corrigir a distorção?
Idev
2
@idev Acredito que a maneira de corrigir a distorção é usar o método 4D na resposta principal desta pergunta. ;)
Nathan Reed
16

Uma maneira simples de pensar seria pegar a saída da função de ruído e espelhá-la / transformá-la em uma imagem com o dobro do tamanho. É difícil de explicar, então aqui está uma imagem: insira a descrição da imagem aqui

Agora, neste caso, é bastante óbvio o que você fez quando olha para isso. Posso pensar em duas maneiras de (possivelmente :-)) resolver isso:

  1. Você pode pegar essa imagem maior e gerar mais ruído em cima dela, mas (e não tenho certeza se isso é possível) focada no meio (para que as bordas permaneçam as mesmas). Isso poderia adicionar um pouco mais de diferença que faria seu cérebro pensar que não são apenas imagens espelhadas.

  2. (Também não tenho certeza se isso é possível) Você pode tentar mexer nas entradas da função de ruído para gerar a imagem inicial de maneira diferente. Você precisaria fazer isso por tentativa e erro, mas procure por recursos que chamam sua atenção quando o ladrilha / espelha e, em seguida, tenta fazer com que não os gere.

Espero que isto ajude.

Richard Marskell - Drackir
fonte
3
Muito bom, mas muito simétrico!
22412 bobobobo
11
@obobobo Era o que eu pensava que os outros passos iriam aliviar. Portanto, você pode gerar uma "base" usando esse método e, em seguida, adicionar mais detalhes sobre a coisa toda para fazer parecer que não está (tão) espelhado.
Richard Marskell - Drackir
Você começa a ter alguns padrões estranhos quando faz esse tipo de coisa. Este em particular parece uma espécie de borboleta. Solução fácil, no entanto.
notlesh
Essa foi a minha primeira abordagem também, mas há um problema visível aqui: dl.dropbox.com/u/6620757/noise_seam.png Ao cruzar um limite de inversão, você causa uma disjunção na função de ruído, invertendo instantaneamente a inclinação do função. Mesmo se você aplicar uma segunda função de ruído na parte superior, isso ainda poderá ser visível na saída.
Jherico
Boa ideia. Isso pode ser feito facilmente em um pixel shader usando onda triangular função:tex2d(abs(abs(uv.x)%2.0-1.0), abs(abs(uv.y)%2.0-1.0))
tigrou
10

A primeira versão desta resposta estava realmente errada, eu a atualizei

Um método que usei com sucesso é tornar o domínio de ruído lado a lado. Em outras palavras, torne sua noise2()função básica periódica. Se noise2()for periódico e betainteiro, o ruído resultante terá o mesmo período que noise2().

Como podemos fazer noise2()periódicos? Na maioria das implementações, essa função usa algum tipo de ruído de rede. Ou seja, obtém números aleatórios em coordenadas inteiras e os interpola. Por exemplo:

function InterpolatedNoise_1D(float x)

  integer_X    = int(x)
  fractional_X = x - integer_X

  v1 = SmoothedNoise1(integer_X)
  v2 = SmoothedNoise1(integer_X + 1)

  return Interpolate(v1 , v2 , fractional_X)

end function

Esta função pode ser modificada trivialmente para se tornar periódica com o período inteiro. Basta adicionar uma linha:

integer_X = integer_X % Period

antes de calcular v1e v2. Dessa forma, os valores nas coordenadas inteiras repetirão todas as unidades do período e a interpolação garantirá que a função resultante seja suave.

Observe, no entanto, que isso só funciona quando o Período é maior que 1. Portanto, para realmente usá-lo na criação de texturas perfeitas, você teria que experimentar um quadrado Período x Período, e não 1x1.

deixa pra lá
fonte
Mas como você faz noise2periódicos (com um período curto, como 1 unidade)? Eu acho que é isso que a pergunta está fazendo. O ruído Perlin padrão é periódico com um período de 256 em cada eixo, mas você deseja um ruído modificado com um período menor.
Nathan Reed
@ Nathan Reed Se você chamar noise2como sugerido, você vai obter resultados periódicas, quer a própria função é periódica ou não. Porque os argumentos envolvem cada 1 unidade.
Nevermind
11
Mas então você tem costuras nas linhas de grade, não é? Como não há garantia de que noise2 (0, 0,999) esteja próximo de noise2 (0, 0), a menos que eu tenha perdido alguma coisa.
Nathan Reed
11
@ Nathan Reed Esse é um bom ponto. Na verdade, acabei de checar novamente meu código antigo e acontece que eu estava errado. Vou editar a resposta agora.
Nevermind
Ótimo! Esta é realmente uma boa resposta agora. +1 :)
Nathan Reed
6

Outra alternativa é gerar ruído usando bibliotecas libnoise. Você pode gerar ruído em uma quantidade infinita de espaço teórico, sem problemas.

Dê uma olhada no seguinte: http://libnoise.sourceforge.net/tutorials/tutorial3.html#tile

Há também uma porta XNA acima: http://bigblackblock.com/tools/libnoisexna

Se você acabar usando a porta XNA, poderá fazer algo assim:

Perlin perlin = new Perlin();
perlin.Frequency = 0.5f;                //height
perlin.Lacunarity = 2f;                 //frequency increase between octaves
perlin.OctaveCount = 5;                 //Number of passes
perlin.Persistence = 0.45f;             //
perlin.Quality = QualityMode.High;
perlin.Seed = 8;

//Create our 2d map
Noise2D _map = new Noise2D(CHUNKSIZE_WIDTH, CHUNKSIZE_HEIGHT, perlin);

//Get a section
_map.GeneratePlanar(left, right, top, down);

GeneratePlanar é a função a ser chamada para obter as seções em cada direção que se conectam perfeitamente ao restante das texturas.

Obviamente, esse método é mais caro do que simplesmente ter uma única textura que pode ser usada em várias superfícies. Se você deseja criar algumas texturas inclináveis ​​aleatórias, isso pode ser algo que lhe interessa.

jgallant
fonte
6

Embora existam aqui algumas respostas que funcionariam, a maioria delas é complicada, lenta e problemática.

Tudo o que você realmente precisa fazer é usar uma função periódica de geração de ruído. É isso aí!

Uma excelente implementação de domínio público baseada no algoritmo de ruído "avançado" da Perlin pode ser encontrada aqui . A função que você precisa é pnoise2. O código foi escrito por Stefan Gustavson, que fez um comentário aqui sobre exatamente esse problema e como outros adotaram a abordagem errada. Escute Gustavson, ele sabe do que está falando.

Em relação às várias projeções esféricas que alguns aqui sugeriram: bem, elas funcionam essencialmente (lentamente), mas também produzem uma textura 2D que é uma esfera achatada, para que as bordas se condensem mais, provavelmente produzindo um efeito indesejado. Obviamente, se você pretende que sua textura 2D seja projetada em uma esfera, esse é o caminho a seguir, mas não é isso que estava sendo solicitado.

Tal Liron
fonte
4

Aqui está uma maneira muito mais simples de fazer barulho lado a lado:

ruído perlin lado a lado do código shadertoy

Você usa um invólucro modular para cada escala do ruído. Eles se ajustam às bordas da área, independentemente da escala de frequência usada. Então você só precisa usar ruído 2D normal, que é muito mais rápido. Aqui está o código WebGL ao vivo que pode ser encontrado em ShaderToy: https://www.shadertoy.com/view/4dlGW2

As três principais funções executam todo o trabalho e o fBM passa um vetor x / y em um intervalo de 0,0 a 1,0.

// Tileable noise, for creating useful textures. By David Hoskins, Sept. 2013.
// It can be extrapolated to other types of randomised texture.

#define SHOW_TILING
#define TILES 2.0

//----------------------------------------------------------------------------------------
float Hash(in vec2 p, in float scale)
{
    // This is tiling part, adjusts with the scale...
    p = mod(p, scale);
    return fract(sin(dot(p, vec2(35.6898, 24.3563))) * 353753.373453);
}

//----------------------------------------------------------------------------------------
float Noise(in vec2 x, in float scale )
{
    x *= scale;

    vec2 p = floor(x);
    vec2 f = fract(x);
    f = f*f*(3.0-2.0*f);
    //f = (1.0-cos(f*3.1415927)) * .5;
    float res = mix(mix(Hash(p,                  scale),
        Hash(p + vec2(1.0, 0.0), scale), f.x),
        mix(Hash(p + vec2(0.0, 1.0), scale),
        Hash(p + vec2(1.0, 1.0), scale), f.x), f.y);
    return res;
}

//----------------------------------------------------------------------------------------
float fBm(in vec2 p)
{
    float f = 0.4;
    // Change starting scale to any integer value...
    float scale = 14.0;
    float amp = 0.55;
    for (int i = 0; i < 8; i++)
    {
        f += Noise(p, scale) * amp;
        amp *= -.65;
        // Scale must be multiplied by an integer value...
        scale *= 2.0;
    }
    return f;
}

//----------------------------------------------------------------------------------------
void main(void)
{
    vec2 uv = gl_FragCoord.xy / iResolution.xy;

#ifdef SHOW_TILING
    uv *= TILES;
#endif

    // Do the noise cloud (fractal Brownian motion)
    float bri = fBm(uv);

    bri = min(bri * bri, 1.0); // ...cranked up the contrast for no reason.
    vec3 col = vec3(bri);

#ifdef SHOW_TILING
    vec2 pixel = (TILES / iResolution.xy);
    // Flash borders...
    if (uv.x > pixel.x && uv.y > pixel.y                                        // Not first pixel
    && (fract(uv.x) < pixel.x || fract(uv.y) < pixel.y) // Is it on a border?
    && mod(iGlobalTime-2.0, 4.0) < 2.0)                 // Flash every 2 seconds
    {
        col = vec3(1.0, 1.0, 0.0);
    }
#endif
    gl_FragColor = vec4(col,1.0);
}
Krondike
fonte
11
O link da sua imagem ficou inoperante. Adotei o melhor palpite e o substituí por uma captura de tela da saída do código shadertoy que você postou. Se isso não estiver correto, carregue novamente a imagem desejada diretamente no servidor Stack Exchange.
Pikalek
3

Tive alguns resultados não ruins interpolando perto das bordas do bloco, mas isso depende do efeito que você está tentando obter e dos parâmetros de ruído exatos. Funciona muito bem para ruídos um tanto embaçados, não tão bons com ruídos pontiagudos / finos.

kaoD
fonte
0

Eu estava checando esse tópico em busca de uma resposta para um problema semelhante, e obtive uma solução limpa e compacta do desenvolvedor desse código python para gerar ruído fractal a partir do ruído perlin / simplex. O código atualizado é fornecido nesta edição (fechada) e pode ser retomado ao definir os gradientes para o lado direito do "gerador" iguais aos do lado esquerdo (e o mesmo para cima e para baixo), como em

# Gradients
angles = 2*np.pi*np.random.rand(res[0]+1, res[1]+1)
gradients = np.dstack((np.cos(angles), np.sin(angles)))
# Make the noise tileable
gradients[-1,:] = gradients[0,:]
gradients[:,-1] = gradients[:,0]

Parece uma solução elegante e limpa, evito copiar todo o código aqui (já que não é minha própria solução), mas está disponível no link fornecido acima. Espero que isso possa ser útil para alguém que deseja produzir uma imagem 2D bidimensional como esta que eu precisava, livre de artefatos ou distorções.

terreno fractal inclinável

Stefano
fonte