Buat peta kebisingan mulus

gambar

Membuat gambar tanpa cacat di Photoshop mudah: potong gambar, ambil bagian kanan dan bawah yang dipangkas, lalu tempelkan ke kiri dan atas menggunakan alat Fade. Tetapi untuk implementasi yang tepat dari peta kebisingan mulus, Anda harus berpikir dengan hati-hati.

Jika Anda memiliki pemahaman dasar tentang kebisingan Perlin , maka Anda tahu bahwa itu terdiri dari angka acak yang diinterpolasi. Ini terutama digunakan dalam dua dimensi. Tetapi ini juga berguna dalam satu dimensi (misalnya, ketika bergerak), dalam tiga dimensi (transformasi silindris dan bola benda 3D), dan bahkan dalam empat atau lima dimensi.

Noise empat dimensi dapat digunakan untuk membuat gambar 2D yang mulus. Tidak umum bagi kita untuk berpikir dalam empat dimensi, jadi kita akan mengambil satu dimensi pada satu waktu.

Dalam contoh saya, saya menggunakan noise simplex dua oktaf. Kebisingan simpleks lebih cepat dalam dimensi besar, dan karena sifatnya yang segitiga, ia terlihat lebih baik.

Saya menulis fungsi kecil drawNoiseuntuk membuat kanvas dan memproses array piksel dalam satu lingkaran.

Suara mulus satu dimensi


Dalam satu dimensi, noise adalah garis halus tanpa batas (implementasi saya terhadap noise dimulai dengan dua, jadi saya menggunakan konstanta sebagai parameter kedua). Di sini kita melihat bahwa ini hanyalah angka acak yang diinterpolasi.

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


Kebisingan satu dimensi

Anda dapat menggunakan ini dalam animasi, menghitung ulang nilai kebisingan setiap milidetik, tetapi Anda juga dapat membuat lingkaran dan menghitung semua nilai di muka. Nilai-nilai pada gambar di atas tidak berputar di sekitar tepi. Tetapi menerapkan pengulangan cukup sederhana, hanya satu dimensi dan loop ... atau lingkaran, untuk itu.

Lingkaran satu dimensi


Bagi sebagian besar dari Anda, kebisingan Perlin terlihat seperti gambar di bawah ini.


Jika kita menggambar sebuah lingkaran di sini dan menghitung nilai noise pada lingkaran ini, kita akan mendapatkan loop satu dimensi.


Noise with a circle untuk membuat loop satu dimensi.

Dalam kode, tampilannya seperti ini:

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


Anda mungkin sudah mengerti apa yang akan kita lakukan. Untuk mengulang gambar dua dimensi, kita membutuhkan peta noise tiga dimensi (setidaknya).

Kartu silinder


Noise Perlin pada awalnya dibuat untuk 3D-texturing kontinu (film "Tron"). Peta gambar bukan selembar kertas yang melilit objek, tetapi dihitung dengan lokasinya di bidang kebisingan tiga dimensi. Karena itu, ketika memotong objek, kita masih bisa menghitung peta untuk permukaan yang baru dibuat.

Sebelum kita mencapai tujuan akhir dari gambar tanpa batas, pertama-tama kita membuat gambar yang menyatu dengan mulus ke kiri dan kanan. Ini mirip dengan lingkaran dua dimensi untuk loop satu dimensi, tetapi dengan satu dimensi tambahan: silinder.

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


Peta kebisingan silinder

Gambar peta bulat


Anda mungkin berpikir bahwa akan lebih mudah menggunakan bola untuk membuat gambar yang mulus, tetapi Anda salah.

Saya akan membuat penyimpangan kecil dan menunjukkan bagaimana peta gambar bulat dihitung dan seperti apa.

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


Peta kebisingan bola


Bulatkan dengan kebisingan

Peta panorama kubik


Bola yang kami buat juga dapat digunakan sebagai panorama jika Anda menempatkan kamera di tengah bola. Tetapi cara terbaik adalah menggunakan panorama kubik, karena memiliki wajah yang jauh lebih sedikit. Bola diproyeksikan ke enam sisi kubus, seperti yang ditunjukkan dalam sketsa ini.


Menumpangkan bola pada sebuah kubus

Untuk setiap piksel pada permukaan kubus, kita perlu menghitung persimpangan antara sudut pandang C di tengah dan bola. Ini mungkin terlihat rumit, tetapi sebenarnya cukup sederhana.

Kita dapat menganggap garis CA sebagai vektor. Dan vektor dapat dinormalisasi sehingga arahnya tidak berubah, tetapi panjangnya berkurang menjadi 1. Karena ini, semua vektor bersama-sama akan terlihat seperti bola.

Normalisasi juga cukup sederhana, kita hanya perlu membagi nilai-nilai vektor dengan xyz dengan total panjang vektor. Panjang vektor dapat dihitung menggunakan teorema Pythagoras.

Dalam kode di bawah ini, perhitungan normalisasi pertama kali dilakukan pada satu wajah. Kemudian noise dihitung secara bersamaan untuk keenam tepi, karena untuk mendapatkan posisi wajah berikutnya, Anda hanya perlu membalik nilai-nilai sepanjang 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")});

Berikut adalah enam sisi dalam satu gambar, plus tangkapan layar tampilannya ketika dilihat dari kubus. Kode sumber memiliki contoh 3D yang ditulis dalam threejs .


Peta panorama kubik


Gambar 2D mulus


Mungkin terlihat bahwa gambar 2D tanpa batas mudah untuk diimplementasikan, tetapi bagi saya ini adalah yang paling sulit dari yang dijelaskan dalam artikel, karena untuk memahaminya Anda perlu berpikir dalam empat dimensi. Hal yang paling dekat dengan ini adalah peta silindris (dengan pengulangan horizontal), jadi kami akan menganggapnya sebagai dasar. Dalam peta silindris, kami menggunakan posisi horizontal gambar untuk lingkaran; yaitu, posisi horizontal gambar memberi kita dua koordinat x dan y dalam bidang noise xyz. Posisi vertikal gambar sesuai dengan z dalam bidang derau.

Kami ingin gambar menjadi mulus dan vertikal, jadi jika kami menambahkan dimensi lain, kami dapat menggunakannya untuk membuat lingkaran kedua dan mengganti nilai linier bidang z. Ini mirip dengan membuat dua silinder dalam bidang empat dimensi. Saya mencoba memvisualisasikan ini pada sketsa, itu tidak akurat, tetapi saya mencoba menyampaikan prinsip umum, dan tidak menggambar silinder empat dimensi.


Sketsa dua silinder dalam empat dimensi

Kode ini cukup sederhana: ini hanya dua lingkaran dalam ruang kebisingan empat dimensi.

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

Dan inilah hasilnya:

gambar

All Articles