Crie mapas de ruído sem costura

imagem

É fácil criar uma imagem perfeita no Photoshop: corte a imagem, pegue as partes direita e inferior cortadas e cole-as na esquerda e na parte superior usando a ferramenta Fade. Mas, para a implementação adequada de mapas de ruído contínuos, é preciso pensar com cuidado.

Se você tem um entendimento básico do ruído Perlin , sabe que ele consiste em números aleatórios interpolados. É usado principalmente em duas dimensões. Mas também é útil em uma dimensão (por exemplo, ao mover), em três dimensões (transformação cilíndrica e esférica de objetos 3D) e até em quatro ou cinco dimensões.

O ruído quadridimensional pode ser usado para criar uma imagem 2D perfeita. Não é muito comum pensarmos em quatro dimensões; portanto, teremos uma dimensão de cada vez.

Nos meus exemplos, usei ruído simplex de duas oitavas. O ruído simplex é mais rápido em grandes dimensões e, devido à sua natureza triangular, parece melhor.

Eu escrevi uma pequena função drawNoisepara criar uma tela e processar uma matriz de pixels em um loop.

Ruído contínuo unidimensional


Em uma dimensão, o ruído é uma linha suave infinita (minha implementação do ruído começa com duas, então eu uso uma constante como o segundo parâmetro). Aqui vemos que esses são apenas números aleatórios interpolados.

// one dimensional line
fNoiseScale = .02;
drawNoise(function(i,x,y){
    var v = Simplex.noise(
         123+x*fNoiseScale
        ,137 // we just need one dimension so this parameter is a constant
    );
    return v*iSize>y?255:0;
}).img();


Ruído unidimensional

Você pode usar isso na animação, recalculando o valor do ruído a cada milissegundo, mas também pode criar um loop e calcular todos os valores antecipadamente. Os valores na imagem acima não passam pelas bordas. Mas implementar a repetibilidade é bastante simples, apenas mais uma dimensão e loop ... ou círculo, para isso.

Loop unidimensional


Para a maioria de vocês, o ruído de Perlin se parece com a imagem abaixo.


Se desenhávamos um círculo aqui e contássemos os valores de ruído nesse círculo, obteríamos um loop unidimensional.


Ruído com um círculo para criar um loop unidimensional.

No código, fica assim:

// one dimensional loop
drawNoise(function(i,x,y){
    var fNX = x/iSize // we let the x-offset define the circle
        ,fRdx = fNX*2*Math.PI // a full circle is two pi radians
        ,a = fRdsSin*Math.sin(fRdx)
        ,b = fRdsSin*Math.cos(fRdx)
        ,v = Simplex.noise(
             123+a*fNoiseScale
            ,132+b*fNoiseScale
        )
    ;
    return v*iSize>y?255:0;
}).img().div(2);


Você provavelmente já entendeu o que vamos fazer. Para fazer um loop em uma imagem bidimensional, precisamos de um mapa de ruído tridimensional (pelo menos).

Cartão cilíndrico


Noise Perlin foi originalmente criado para texturas 3D contínuas (o filme "Tron"). O mapa da imagem não é uma folha de papel enrolada em torno de um objeto, mas é calculado por sua localização em um campo de ruído tridimensional. Portanto, ao cortar o objeto, ainda podemos calcular o mapa para a superfície recém-criada.

Antes de atingirmos o objetivo final de uma imagem perfeita, primeiro criamos uma imagem que se junta perfeitamente à esquerda e à direita. Isso é semelhante a um círculo bidimensional para um loop unidimensional, mas com uma dimensão adicional: um cilindro.

// three dimensional cylindrical map
drawNoise(function(i,x,y){
    var fNX = x/iSize
        ,fRdx = fNX*2*Math.PI
        ,a = fRdsSin*Math.sin(fRdx)
        ,b = fRdsSin*Math.cos(fRdx)
        ,v = Simplex.noise(
             123+a*fNoiseScale
            ,132+b*fNoiseScale
            ,312+y*fNoiseScale // similar to the one dimensional loop but we add a third dimension defined by the image y-offset
        )
    ;
    return v*255<<0;
}).img().div(2);


Mapa de ruído cilíndrico

Imagem de mapa esférico


Você pode pensar que seria conveniente usar uma esfera para criar uma imagem perfeita, mas você está enganado.

Farei uma pequena digressão e mostrarei como o mapa de imagem esférico é calculado e como ele é.

// three dimensional spherical map
document.body.addChild('h2').innerText = 'three dimensional spherical map';
fNoiseScale = .1;
var oSpherical = drawNoise(function(i,x,y){
    var  fNX = (x+.5)/iSize // added half a pixel to get the center of the pixel instead of the top-left
        ,fNY = (y+.5)/iSize
        ,fRdx = fNX*2*Math.PI
        ,fRdy = fNY*Math.PI // the vertical offset of a 3D sphere spans only half a circle, so that is one Pi radians
        ,fYSin = Math.sin(fRdy+Math.PI) // a 3D sphere can be seen as a bunch of cicles stacked onto each other, the radius of each of these is defined by the vertical position (again one Pi radians)
        ,a = fRdsSin*Math.sin(fRdx)*fYSin
        ,b = fRdsSin*Math.cos(fRdx)*fYSin
        ,c = fRdsSin*Math.cos(fRdy)
        ,v = Simplex.noise(
             123+a*fNoiseScale
            ,132+b*fNoiseScale
            ,312+c*fNoiseScale
        )
    ;
    return v*255<<0;
}).img();


Mapa de ruído esférico


Esfera com ruído

Mapa panorâmico cúbico


A esfera que criamos também pode ser usada como panorama se você colocar uma câmera no centro da esfera. Mas a melhor maneira seria usar um panorama cúbico, porque ele tem muito menos rostos. A esfera é projetada nos seis lados do cubo, como mostrado neste esboço.


Aplicando uma esfera a um cubo

Para cada pixel na superfície do cubo, precisamos calcular a interseção entre o ponto de vista C no centro e a esfera. Pode parecer complicado, mas na verdade é bem simples.

Podemos considerar a linha CA como um vetor. E os vetores podem ser normalizados para que sua direção não mude, mas o comprimento diminui para 1. Devido a isso, todos os vetores juntos parecerão uma esfera.

A normalização também é bastante simples, basta dividir os valores do vetor por xyz pelo comprimento total do vetor. O comprimento do vetor pode ser calculado usando o teorema de Pitágoras.

No código abaixo, o cálculo da normalização é realizado primeiro em uma face. Em seguida, o ruído é calculado simultaneamente para todas as seis arestas, porque para obter a posição da próxima face, basta girar os valores ao longo de xyz.

// 3D panoramical cube map
document.body.addChild('h2').innerText = '3D panoramical cube map';
// we're not using the drawNoise function because our canvas is rectangular
var mCubemap = document.createElement('canvas')
    ,iW = 6*iSize;
mCubemap.width = iW;
mCubemap.height = iSize;
var  iHSize = iSize/2 // half the size of the cube
    ,oCtx = mCubemap.getContext('2d')
    ,oImgData = oCtx.getImageData(0,0,iW,iSize)
    ,aPixels = oImgData.data
    ,aa = 123
    ,bb = 231
    ,cc = 321
;
for (var i=0,l=iSize*iSize;i<l;i++) {
    var  x = i%iSize        // x position in image
        ,y = (i/iSize)<<0    // y position in image
        ,a = -iHSize + x+.5    // x position on the cube plane, the added .5 is to get the center of the pixel
        ,b = -iHSize + y+.5 // y position on the cube plane
        ,c = -iHSize        // z position of the cube plane
        ,fDistanceAB = Math.sqrt(a*a+b*b) // to calculate the vectors length we use Pythagoras twice
        ,fDistanceABC = Math.sqrt(fDistanceAB*fDistanceAB+c*c)
        ,fDrds = .5*fDistanceABC // adjust the distance a bit to get a better radius in the noise field
        ,v = 1
    ;
    a /= fDrds; // normalize the vector
    b /= fDrds; // normalize the vector
    c /= fDrds; // normalize the vector
    //
    // since we now know the spherical position for one plane we can derive the positions for the other five planes simply by switching the x, y and z values (the a, b and c variables)
    var aNoisePositions = [
         [a,b,c]    // back
        ,[-c,b,a]    // right
        ,[-a,b,-c]    // front
        ,[c,b,-a]    // left
        ,[a,c,-b]    // top
        ,[a,-c,b]    // bottom
    ];
    for (var j=0;j<6;j++) {
        v = Simplex.noise(
             aa + aNoisePositions[j][0]
            ,bb + aNoisePositions[j][1]
            ,cc + aNoisePositions[j][2]
        );
        var pos = 4*(y*iW+j*iSize+x); // the final position of the rgba pixel
        aPixels[pos] = aPixels[pos+1] = aPixels[pos+2] = v*255<<0;
        aPixels[pos+3] = 255;
    }
}
oCtx.putImageData(oImgData,0,0);
document.body.addChild('img',{src:mCubemap.toDataURL("image/jpeg")});

Aqui estão seis lados em uma imagem, além de uma captura de tela de sua aparência quando vista de um cubo. O código fonte tem um exemplo 3D escrito em threejs .


Mapa panorâmico cúbico


Imagem 2D sem costura


Pode parecer que a imagem 2D sem costura é fácil de implementar, mas me parece que esse é o mais difícil do descrito no artigo, porque para entendê-lo, você precisa pensar em quatro dimensões. O mais próximo disso foi um mapa cilíndrico (com repetição horizontal), então vamos tomá-lo como base. No mapa cilíndrico, usamos a posição horizontal da imagem para o círculo; isto é, a posição horizontal da imagem nos dá duas coordenadas x e y no campo de ruído xyz. A posição vertical da imagem corresponde a z no campo de ruído.

Queremos que a imagem seja perfeita e vertical; portanto, se adicionarmos outra dimensão, podemos usá-la para criar um segundo círculo e substituir o valor linear do campo z. Isso é semelhante à criação de dois cilindros em um campo quadridimensional. Tentei visualizar isso em um esboço, é impreciso, mas tentei transmitir o princípio geral e não desenhar um cilindro quadridimensional.


Um esboço de dois cilindros em quatro dimensões

O código é bastante simples: são apenas dois círculos em um espaço de ruído quadridimensional.

// four dimensional tile
fNoiseScale = .003;
drawNoise(function(i,x,y){
    var  fNX = x/iSize
        ,fNY = y/iSize
        ,fRdx = fNX*2*Math.PI
        ,fRdy = fNY*2*Math.PI
        ,a = fRds*Math.sin(fRdx)
        ,b = fRds*Math.cos(fRdx)
        ,c = fRds*Math.sin(fRdy)
        ,d = fRds*Math.cos(fRdy)
        ,v = Simplex.noise(
             123+a*fNoiseScale
            ,231+b*fNoiseScale
            ,312+c*fNoiseScale
            ,273+d*fNoiseScale
        )
    ;
    return (Math.min(Math.max(2*(v -.5)+.5,0),1)*255)<<0;
}).img().div(2,2);

E aqui está o resultado:

imagem

All Articles