Créez des cartes de bruit fluides

image

La création d'une image transparente dans Photoshop est simple: recadrez l'image, saisissez les parties découpées droite et inférieure, puis collez-les à gauche et en haut à l'aide de l'outil Fondu. Mais pour la bonne mise en œuvre de cartes de bruit homogènes, vous devez bien réfléchir.

Si vous avez une compréhension de base du bruit Perlin , vous savez qu'il se compose de nombres aléatoires interpolés. Il est principalement utilisé en deux dimensions. Mais il est également utile en une dimension (par exemple lors d'un déplacement), en trois dimensions (transformation cylindrique et sphérique d'objets 3D), et même en quatre ou cinq dimensions.

Le bruit en quatre dimensions peut être utilisé pour créer une image 2D transparente. Il n'est pas très courant pour nous de penser en quatre dimensions, nous allons donc prendre une dimension à la fois.

Dans mes exemples, j'ai utilisé du bruit simplex à deux octaves. Le bruit simplex est plus rapide dans les grandes dimensions et, en raison de sa nature triangulaire, il semble meilleur.

J'ai écrit une petite fonction drawNoisepour créer une toile et traiter un tableau de pixels en boucle.

Bruit homogène unidimensionnel


Dans une dimension, le bruit est une ligne lisse infinie (mon implémentation du bruit commence par deux, donc j'utilise une constante comme deuxième paramètre). Ici, nous voyons que ce ne sont que des nombres aléatoires interpolés.

// 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();


Bruit unidimensionnel

Vous pouvez l'utiliser dans l'animation, recalculer la valeur du bruit toutes les millisecondes, mais vous pouvez également créer une boucle et calculer toutes les valeurs à l'avance. Les valeurs de l'image ci-dessus ne bouclent pas sur les bords. Mais la mise en œuvre de la répétabilité est assez simple, juste une dimension et une boucle de plus ... ou un cercle, pour cela.

Boucle unidimensionnelle


Pour la plupart d'entre vous, le bruit de Perlin ressemble à l'image ci-dessous.


Si nous dessinions un cercle ici et comptions les valeurs de bruit sur ce cercle, nous obtiendrions une boucle unidimensionnelle.


Bruit avec un cercle pour créer une boucle unidimensionnelle.

En code, cela ressemble à ceci:

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


Vous avez probablement déjà compris ce que nous allons faire. Pour boucler une image bidimensionnelle, nous avons besoin d'une carte de bruit tridimensionnelle (au moins).

Carte cylindrique


Noise Perlin a été à l'origine créé pour la texturation 3D continue (le film "Tron"). La carte d'image n'est pas une feuille de papier enroulée autour d'un objet, mais est calculée par son emplacement dans un champ de bruit tridimensionnel. Par conséquent, lors de la découpe de l'objet, nous pouvons toujours calculer la carte pour la surface nouvellement créée.

Avant d'atteindre notre objectif ultime d'une image transparente, nous créons d'abord une image qui se joint de manière transparente à gauche et à droite. Ceci est similaire à un cercle bidimensionnel pour une boucle unidimensionnelle, mais avec une dimension supplémentaire: un cylindre.

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


Carte de bruit cylindrique

Image de la carte sphérique


Vous pourriez penser qu'il serait pratique d'utiliser une sphère pour créer une image homogène, mais vous vous trompez.

Je vais faire une petite digression et montrer comment la carte image sphérique est calculée et à quoi elle ressemble.

// 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();


Carte de bruit sphérique


Sphère avec bruit

Carte panoramique cubique


La sphère que nous avons créée peut également être utilisée comme panorama si vous placez une caméra au centre de la sphère. Mais la meilleure façon serait d'utiliser un panorama cubique, car il a beaucoup moins de visages. La sphère est projetée sur les six côtés du cube, comme illustré dans ce croquis.


Superposition d'une sphère sur un cube

Pour chaque pixel de la surface du cube, nous devons calculer l'intersection entre le point de vue C au centre et la sphère. Cela peut sembler compliqué, mais c'est en fait assez simple.

Nous pouvons considérer la ligne CA comme un vecteur. Et les vecteurs peuvent être normalisés afin que leur direction ne change pas, mais la longueur diminue à 1. Pour cette raison, tous les vecteurs ensemble ressembleront à une sphère.

La normalisation est également assez simple, il suffit de diviser les valeurs du vecteur par xyz par la longueur totale du vecteur. La longueur du vecteur peut être calculée en utilisant le théorème de Pythagore.

Dans le code ci-dessous, le calcul de normalisation est d'abord effectué sur une face. Ensuite, le bruit est calculé simultanément pour les six arêtes, car pour obtenir la position de la face suivante, il vous suffit de retourner les valeurs le long 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")});

Voici six côtés dans une image, ainsi qu'une capture d'écran de son apparence lorsqu'elle est vue à partir d'un cube. Le code source a un exemple 3D écrit en trois js .


Carte panoramique cubique


Image 2D transparente


Il peut sembler qu'une image 2D transparente soit facile à mettre en œuvre, mais il me semble que c'est la plus difficile des décrites dans l'article, car pour la comprendre, vous devez penser en quatre dimensions. La chose la plus proche était une carte cylindrique (avec répétition horizontale), nous allons donc la prendre comme base. Dans la carte cylindrique, nous avons utilisé la position horizontale de l'image pour le cercle; c'est-à-dire que la position horizontale de l'image nous donne deux coordonnées x et y dans le champ de bruit xyz. La position verticale de l'image correspond à z dans le champ de bruit.

Nous voulons que l'image soit transparente et verticale, donc si nous ajoutons une autre dimension, nous pouvons l'utiliser pour créer un deuxième cercle et remplacer la valeur linéaire du champ z. Cela revient à créer deux cylindres dans un champ à quatre dimensions. J'ai essayé de visualiser cela sur un croquis, c'est inexact, mais j'ai essayé de transmettre le principe général et de ne pas dessiner un cylindre à quatre dimensions.


Une esquisse de deux cylindres en quatre dimensions

Le code est assez simple: ce ne sont que deux cercles dans un espace de bruit en quatre dimensions.

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

Et voici le résultat:

image

All Articles