
Artikel ini adalah kelanjutan logis dari pengenalan shader pemrograman untuk desainer tata letak . Di dalamnya, kami membuat templat untuk membuat berbagai efek dua dimensi dengan foto menggunakan shader dan melihat beberapa contoh. Pada artikel ini kami akan menambahkan beberapa tekstur lagi, menerapkan pembelahan Voronoi dalam praktek untuk membuat mosaik dari mereka, berbicara tentang membuat berbagai topeng di shader, tentang pixelation, dan juga menyentuh beberapa masalah sintaks GLSL kuno yang masih ada di browser kami.
Sama seperti terakhir kali, akan ada teori minimum dan maksimum praktik dan penalaran dalam bahasa sehari-hari yang biasa. Pemula akan menemukan di sini serangkaian tindakan dengan tips dan catatan yang berguna, dan vendor front-end yang berpengalaman dapat menemukan beberapa ide untuk inspirasi.
Sebuah survei dalam artikel sebelumnya menunjukkan bahwa topik efek WebGL untuk situs mungkin menarik tidak hanya untuk pengetik huruf, tetapi juga untuk rekan kami dari spesialisasi lainnya. Agar tidak membingungkan mereka dengan fitur ES terbaru, kami sengaja membatasi diri pada konstruksi sintaksis yang lebih tradisional yang dipahami semua orang. Dan sekali lagi saya menarik perhatian pembaca pada fakta bahwa editor bawaan dari CodePen mempengaruhi kinerja apa yang dilakukan di dalamnya.
Tapi mari kita mulai ...
Template untuk bekerja dengan shader
Bagi mereka yang belum membaca artikel sebelumnya, kami membuat template ini untuk bekerja dengan shader:
Sebuah pesawat dibuat di dalamnya (dalam kasus kami, kotak) di mana tekstur gambar "digambar". Tidak ada dependensi yang tidak perlu dan shader vertex yang sangat sederhana. Kemudian kami mengembangkan templat ini, tetapi sekarang kami akan mulai dari saat ketika belum ada logika dalam shader fragmen.
Mosaik
Mosaik adalah bidang yang dipecah menjadi daerah-daerah kecil, di mana masing-masing bidang diisi dengan warna tertentu atau, seperti dalam kasus kami, tekstur. Bagaimana kita dapat mematahkan pesawat kita menjadi beberapa bagian? Jelas, Anda dapat memecahnya menjadi empat persegi panjang. Tapi ini sudah sangat mudah dilakukan dengan bantuan SVG, untuk menyeret WebGL ke tugas ini dan meletakkan semuanya tiba-tiba tanpa tujuan.
Agar mosaik menarik, ia harus memiliki fragmen yang berbeda, baik dalam bentuk maupun ukuran. Ada satu pendekatan yang sangat sederhana, tetapi pada saat yang sama sangat menghibur untuk membangun partisi seperti itu. Dikenal sebagai mosaik Voronoi atau partisi Dirichlet, dan di Wikipedia ditulis bahwa Descartes menggunakan sesuatu yang serupa di abad XVII yang jauh. Idenya kira-kira seperti ini:
- Ambil satu set poin di pesawat.
- Untuk setiap titik di pesawat, cari titik terdekat dari set ini.
- Itu saja. Bidang dibagi menjadi bidang poligonal, yang masing-masing ditentukan oleh salah satu titik dalam himpunan.
Mungkin lebih baik untuk menunjukkan proses ini dengan contoh praktis. Ada algoritma yang berbeda untuk menghasilkan partisi ini, tetapi kami akan bertindak di dahi, karena menghitung sesuatu untuk setiap titik di pesawat hanyalah tugas untuk shader. Pertama, kita perlu membuat satu set poin acak. Agar tidak memuat kode contoh, kami akan membuat variabel global untuk mereka.
function createPoints() { for (let i = 0; i < NUMBER_OF_POINTS; i++) { POINTS.push([Math.random(), Math.random()]); } }
Sekarang kita harus meneruskannya ke shader. Data bersifat global, jadi kami akan menggunakan pengubah uniform
. Tetapi ada satu titik halus: kita tidak bisa hanya melewati sebuah array. Tampaknya abad ke-21 ada di halaman, tetapi tidak ada yang datang darinya. Akibatnya, Anda harus mentransfer sejumlah poin satu per satu.
for (let i = 0; i < NUMBER_OF_POINTS; i++) { GL.uniform2fv(GL.getUniformLocation(PROGRAM, 'u_points[' + i + ']'), POINTS[i]); }
Hari ini, kita akan sering menghadapi masalah ketidakkonsistenan yang serupa antara apa yang diharapkan dan apa yang ada di browser nyata. Biasanya tutorial WebGL menggunakan THREE.js dan pustaka ini menyembunyikan beberapa kotoran itu sendiri, seperti yang pernah dilakukan jQuery dalam tugasnya, tetapi jika Anda menghapusnya, itu benar-benar melukai otak Anda.
Di fragmen shader, kami memiliki variabel array untuk poin. Kami hanya dapat membuat array dengan panjang tetap. Mari kita mulai dengan 10 poin:
#define NUMBER_OF_POINTS 10 uniform vec2 u_points[NUMBER_OF_POINTS];
Pastikan semua ini berfungsi dengan menggambar lingkaran di tempat-tempat titik. Gambar berbagai geometri primitif seperti ini sering digunakan selama debugging - mereka terlihat jelas dan Anda dapat segera memahami apa yang berada dan di mana ia bergerak.
Gunakan "gambar" lingkaran, garis, dan tengara lainnya untuk objek tak terlihat yang menjadi dasar pembuatan animasi. Ini akan memberikan petunjuk yang jelas tentang cara kerjanya, terutama jika algoritmanya rumit untuk dipahami dengan cepat tanpa persiapan sebelumnya. Kemudian semua ini dapat dikomentari dan diserahkan kepada kolega - mereka akan mengucapkan terima kasih.
for (int i = 0; i < NUMBER_OF_POINTS; i++) { if (distance(texture_coord, u_points[i]) < 0.02) { gl_FragColor = WHITE; break; } }
Bagus Mari kita juga menambahkan beberapa gerakan ke poin. Biarkan mereka bergerak dalam lingkaran untuk memulai, maka kita akan kembali ke masalah ini nanti. Koefisien juga diletakkan pada mata, hanya untuk sedikit memperlambat gerakan mereka dan mengurangi amplitudo osilasi.
function movePoints(timeStamp) { if (timeStamp) { for (let i = 0; i < NUMBER_OF_POINTS; i++) { POINTS[i][0] += Math.sin(i * timeStamp / 5000.0) / 500.0; POINTS[i][1] += Math.cos(i * timeStamp / 5000.0) / 500.0; } } }
Kembali ke shader. Untuk percobaan di masa mendatang, kami akan menemukan sejumlah area yang berguna di mana semuanya akan dibagi. Jadi kami menemukan titik terdekat dengan piksel saat ini dari set dan menyimpan jumlah titik itu - itu adalah nomor area.
float min_distance = 1.0; int area_index = 0; for (int i = 0; i < NUMBER_OF_POINTS; i++) { float current_distance = distance(texture_coord, u_points[i]); if (current_distance < min_distance) { min_distance = current_distance; area_index = i; } }
Untuk menguji kinerja, kami sekali lagi mengecat semuanya dengan warna-warna cerah:
gl_FragColor = texture2D(u_texture, texture_coord); gl_FragColor.g = abs(sin(float(area_index))); gl_FragColor.b = abs(sin(float(area_index)));
Kombinasi modul (abs) dan fungsi terbatas (khususnya sin dan cos) sering digunakan ketika bekerja dengan efek yang sama. Di satu sisi, ini menambahkan sedikit keacakan, dan di sisi lain, itu segera memberikan hasil yang dinormalisasi dari 0 hingga 1, yang sangat nyaman - kami memiliki banyak nilai yang terletak tepat di dalam batas-batas ini.
Kami juga akan menemukan poin kurang lebih sama dari beberapa poin dari set, dan mewarnai mereka. Tindakan ini tidak membawa muatan khusus, tetapi melihat hasilnya masih menarik.
int number_of_near_points = 0; for (int i = 0; i < NUMBER_OF_POINTS; i++) { if (distance(texture_coord, u_points[i]) < min_distance + EPSILON) { number_of_near_points++; } } if (number_of_near_points > 1) { gl_FragColor.rgb = vec3(1.0); }
Anda harus mendapatkan sesuatu seperti ini:
Ini masih konsep, kami masih akan menyelesaikannya. Tapi sekarang konsep umum pemisahan pesawat seperti itu sudah jelas.
Mosaik dari foto
Jelas bahwa dalam bentuknya yang murni tidak banyak manfaat dari partisi semacam itu. Untuk memperluas wawasan Anda dan hanya untuk bersenang-senang, Anda dapat bermain dengannya, tetapi di situs nyata ada baiknya menambahkan beberapa foto lagi dan membuat mosaik dari mereka. Mari kita ulangi fungsi membuat tekstur sedikit, sehingga ada lebih dari satu.
function createTextures() { for (let i = 0; i < URLS.textures.length; i++) { createTexture(i); } } function createTexture(index) { const image = new Image(); image.crossOrigin = 'anonymous'; image.onload = () => { const texture = GL.createTexture(); GL.activeTexture(GL['TEXTURE' + index]); GL.bindTexture(GL.TEXTURE_2D, texture); GL.pixelStorei(GL.UNPACK_FLIP_Y_WEBGL, true); GL.texImage2D(GL.TEXTURE_2D, 0, GL.RGB, GL.RGB, GL.UNSIGNED_BYTE, image); GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_S, GL.CLAMP_TO_EDGE); GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_T, GL.CLAMP_TO_EDGE); GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.LINEAR); GL.uniform1i(GL.getUniformLocation(PROGRAM, 'u_textures[' + index + ']'), index); }; image.src = URLS.textures[index]; }
Tidak ada yang tidak biasa terjadi, kami hanya mengganti nol dengan parameter index
dan menggunakan kembali kode yang ada untuk memuat tiga tekstur. Di shader, kami sekarang memiliki array tekstur:
#define NUMBER_OF_TEXTURES 3 uniform sampler2D u_textures[NUMBER_OF_TEXTURES];
Sekarang kita dapat menggunakan nomor area yang disimpan sebelumnya untuk memilih salah satu dari tiga tekstur. Tapi ...
Tetapi sebelum itu saya ingin melakukan penyimpangan kecil. Tentang sakit. Tentang sintaksisnya. Javascript modern (bersyarat ES6 +) adalah bahasa yang bagus. Hal ini memungkinkan Anda untuk mengekspresikan pikiran Anda saat muncul, tidak membatasi kerangka kerja untuk paradigma pemrograman tertentu, menyelesaikan beberapa poin untuk kami dan memungkinkan Anda untuk lebih fokus pada ide daripada pada implementasinya. Untuk pencipta - itu dia. Beberapa orang percaya bahwa itu memberi terlalu banyak kebebasan dan beralih ke TypeScript misalnya. Pure C adalah bahasa yang lebih keras. Ini juga memungkinkan banyak, Anda dapat memikat apa pun di atasnya, tetapi setelah JS dianggap sedikit canggung, kuno atau sesuatu. Meskipun demikian, dia masih baik. GLSL seperti yang ada di browser hanyalah sesuatu. Tidak hanya urutan besarnya lebih ketat dari C, ia masih kekurangan banyak operator dan konstruksi sintaks yang dikenal. Ini mungkin masalah terbesar ketika menulis shader yang lebih kompleks untuk WebGL. Di balik kengerian bahwa kode berubah menjadi, bisa sangat sulit untuk melihat pada algoritma aslinya. Beberapa coders berpikir bahwa sampai mereka belajar C, jalur menuju shader ditutup untuk mereka. Jadi: pengetahuan tentang C tidak akan sangat membantu di sini. Ini semacam dunia tersendiri. Dunia kegilaan, dinosaurus dan kruk.
Bagaimana saya bisa memilih salah satu dari tiga tekstur yang memiliki satu nomor - nomor area. Sisanya terlintas dalam pikiran dari membagi nomor dengan jumlah tekstur. Ide bagus Hanya operator %
, yang sudah menulis sendiri, tidak ada di sini. Kesan memahami fakta ini dijelaskan dengan baik oleh gambar:

Tentu saja, Anda berkata, "Ya, tidak masalah, ada fungsi mod
- ayo ambil!". Tetapi ternyata dia tidak menerima dua bilangan bulat, hanya bilangan pecahan. Ok, well, buat float
dari mereka. Kami juga mendapatkan float
, tetapi kami membutuhkan int
. Anda harus mengonversi semuanya kembali, jika tidak ada kemungkinan palsu untuk mendapatkan kesalahan kompilasi.
int texture_index = int(mod(float(area_index), float(NUMBER_OF_TEXTURES)))
Dan ini adalah pertanyaan retoris: mungkin akan lebih mudah untuk merealisasikan fungsinya dari sisa divisi integer daripada mencoba merakitnya dari metode standar? Dan ini masih merupakan fungsi yang sederhana, dan kebetulan sekuens transformasi yang tertanam sangat dalam diperoleh di mana tidak lagi jelas apa yang terjadi.
Oke, mari kita biarkan seperti sekarang. Ambil saja warna piksel yang diinginkan dari tekstur yang dipilih dan tetapkan ke variabel gl_FragColor
. Jadi? Apakah kita sudah melakukan ini? Dan kemudian kucing ini muncul lagi. Anda tidak dapat menggunakan non-konstan saat mengakses array. Dan semua yang kita hitung tidak lagi konstan. Ba-dum-tsss !!!
Anda harus melakukan sesuatu seperti ini:
if (texture_index == 0) { gl_FragColor = texture2D(u_textures[0], texture_coord); } else if (texture_index == 1) { gl_FragColor = texture2D(u_textures[1], texture_coord); } else if (texture_index == 2) { gl_FragColor = texture2D(u_textures[2], texture_coord); }
Setuju, kode seperti itu adalah jalan langsung ke govnokod.ru , namun demikian, ini berbeda dalam hal apa pun. Bahkan switch-case
tidak ada di sini untuk setidaknya memuliakan aib ini. Sebenarnya ada kruk lain yang kurang jelas yang memecahkan masalah yang sama:
for (int i = 0; i < 3; i++) { if (texture_index == i) { gl_FragColor = texture2D(u_textures[i], texture_coord); } }
Penghitung siklus, yang bertambah satu, kompiler dapat dihitung sebagai konstanta. Tapi ini tidak berhasil dengan array tekstur - di Chrome terakhir, muncul kesalahan mengatakan bahwa tidak mungkin untuk melakukan ini dengan array tekstur. Dengan deretan angka, itu berhasil. Coba tebak mengapa ia bekerja dengan satu array, tetapi tidak dengan yang lain? Jika Anda berpikir bahwa sistem tipe casting di JS penuh dengan sihir - bereskan sistem "konstan - tidak konstan" di GLSL. Yang lucu adalah bahwa hasilnya juga tergantung pada kartu video yang digunakan, sehingga kruk rumit yang bekerja pada kartu grafis NVIDIA mungkin sangat rusak pada AMD.
Lebih baik untuk menghindari keputusan seperti itu berdasarkan asumsi tentang kompiler. Mereka cenderung pecah dan sulit untuk diuji.
Kesedihan adalah kesedihan. Tetapi, jika kita ingin melakukan hal-hal menarik, kita perlu mengabstraksi dari semua ini dan melanjutkan.
Saat ini, kami mendapat mosaik foto. Tapi ada satu detail: jika poinnya sangat dekat satu sama lain, maka ada transisi cepat dari dua area. Tidak terlalu cantik. Anda perlu menambahkan beberapa algoritma yang tidak memungkinkan poin untuk mendekati. Anda dapat membuat opsi sederhana, di mana jarak antara titik diperiksa dan, jika kurang dari nilai tertentu, maka kami dorong keduanya. Pilihan ini bukan tanpa kekurangan, khususnya, kadang-kadang mengarah ke sedikit kejang poin, tetapi dalam banyak kasus mungkin cukup, terutama karena tidak ada banyak perhitungan di sini. Opsi yang lebih maju adalah sistem pemindahan biaya dan "sarang laba-laba" di mana pasangan poin dihubungkan oleh mata air yang tidak terlihat. Jika Anda tertarik untuk mengimplementasikannya, maka Anda dapat dengan mudah menemukan semua rumus dalam buku referensi fisika untuk sekolah menengah.
for (let i = 0; i < NUMBER_OF_POINTS; i++) { for (let j = i; j < NUMBER_OF_POINTS; j++) { let deltaX = POINTS[i][0] - POINTS[j][0]; let deltaY = POINTS[i][1] - POINTS[j][1]; let distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); if (distance < 0.1) { POINTS[i][0] += 0.001 * Math.sign(deltaX); POINTS[i][1] += 0.001 * Math.sign(deltaY); POINTS[j][0] -= 0.001 * Math.sign(deltaX); POINTS[j][1] -= 0.001 * Math.sign(deltaY); } } }
Masalah utama dengan pendekatan ini, serta yang kami gunakan dalam shader, adalah membandingkan semua poin dengan semua. Anda tidak perlu menjadi ahli matematika yang hebat untuk memahami bahwa jumlah perhitungan jarak akan luar biasa jika kita tidak melakukan 10 poin, tetapi 1000. Ya, bahkan 100 sudah cukup untuk semuanya memperlambat. Karena itu, masuk akal untuk menerapkannya hanya untuk sejumlah kecil poin.
Jika kita ingin membuat mosaik seperti itu untuk sejumlah besar poin, maka kita dapat menggunakan pembagian pesawat yang sudah dikenal menjadi kotak yang identik. Idenya adalah untuk menempatkan satu titik di setiap kotak, dan kemudian melakukan semua perbandingan hanya dengan poin dari kotak tetangga. Ide yang bagus, tetapi percobaan telah menunjukkan bahwa dengan sejumlah besar poin, laptop murah dengan kartu video terintegrasi masih belum dapat mengatasinya. Oleh karena itu, perlu dipikirkan sepuluh kali sebelum memutuskan untuk membuat mosaik di situs Anda dari sejumlah besar fragmen.
Jangan lobak, periksa kinerja kerajinan Anda tidak hanya di pertanian pertambangan Anda, tetapi juga pada laptop biasa. Pengguna pada dasarnya akan menjadi orang-orang.
Mempartisi bidang menurut grafik fungsi
Mari kita lihat opsi lain untuk membagi pesawat menjadi beberapa bagian. Ini tidak lagi membutuhkan daya komputasi yang besar. Gagasan utamanya adalah mengambil beberapa fungsi matematika dan membangun grafiknya. Garis yang dihasilkan hanya akan membagi pesawat menjadi dua bagian. Jika kita menggunakan fungsi dari bentuk y = f(x)
, kita mendapatkan pembagian dalam bentuk potongan. Mengganti X dengan Y, kita dapat mengubah bagian horizontal menjadi vertikal. Jika Anda mengambil fungsi dalam koordinat kutub, maka Anda perlu menerjemahkan semuanya ke dalam Cartesian dan sebaliknya, tetapi esensi dari perhitungan tidak akan berubah. Dalam hal ini, hasilnya bukan memotong menjadi dua bagian, melainkan memotong lubang. Tapi kita akan melihat opsi pertama.
Untuk setiap Y, kami akan menghitung nilai X untuk membuat bagian vertikal. Kita bisa mengambil gelombang sinus untuk tujuan ini, misalnya, tetapi terlalu membosankan. Lebih baik mengambil beberapa potong sekaligus dan melipatnya.
Kami mengambil beberapa sinusoid, yang masing-masing terikat ke koordinat sepanjang Y dan ke waktu, dan menambahkannya. Fisikawan akan menyebut adisi superposisi ini. Jelas, mengalikan seluruh hasil dengan beberapa angka, kami mengubah amplitudo. Keluarkan dalam makro terpisah. Jika Anda mengalikan koordinat - parameter sinus, maka frekuensinya akan berubah. Kami telah melihat ini di artikel sebelumnya. Kami juga menghapus pengubah frekuensi yang umum untuk semua sinusoid dari rumus. Tidak akan berlebihan untuk bermain dengan waktu, tanda negatif akan memberikan efek memindahkan garis ke arah yang berlawanan.
float time = u_time * SPEED; float x = (sin(texture_coord.y * FREQUENCY) + sin(texture_coord.y * FREQUENCY * 2.1 + time) + sin(texture_coord.y * FREQUENCY * 1.72 + time * 1.121) + sin(texture_coord.y * FREQUENCY * 2.221 + time * 0.437) + sin(texture_coord.y * FREQUENCY * 3.1122 + time * 4.269)) * AMPLITUDE;
Setelah membuat pengaturan global untuk fungsi kita, kita akan menghadapi masalah mengulangi gerakan yang sama pada interval yang cukup singkat. Untuk mengatasi masalah ini, kita perlu melipatgandakan semuanya dengan koefisien yang kelipatan terkecilnya sangat besar. Sesuatu yang serupa juga digunakan dalam generator angka acak, ingat? Dalam hal ini, kami tidak berpikir dan mengambil angka yang sudah jadi dari beberapa contoh dari Internet, tetapi tidak ada yang mengganggu untuk bereksperimen dengan nilai-nilai kami.
Tetap hanya untuk memilih salah satu dari dua tekstur untuk poin di atas grafik fungsi kami dan yang kedua untuk poin di bawahnya. Lebih tepatnya ke kiri dan kanan, kita semua berbalik:
if (texture_coord.x - 0.5 > x) { gl_FragColor = texture2D(u_textures[0], texture_coord); } else { gl_FragColor = texture2D(u_textures[1], texture_coord); }
Apa yang kami terima menyerupai gelombang suara. Lebih tepatnya, gambar mereka di osiloskop. Memang, kita bisa bukannya sinusoid kita mengirimkan data dari beberapa jenis file suara. Tetapi bekerja dengan suara adalah topik untuk artikel terpisah.
Topeng
Contoh sebelumnya harus mengarah pada komentar yang cukup logis: semua ini terlihat seperti karya topeng di SVG (jika Anda belum bekerja dengan mereka, lihat contoh dari artikel topeng SVG dan efek wow ). Hanya saja di sini kita melakukannya sedikit berbeda. Dan hasilnya sama - beberapa area dicat dengan satu tekstur, beberapa dengan yang lain. Hanya transisi yang mulus belum. Jadi mari kita lakukan satu.
Kami menghapus semua yang tidak perlu dan mengembalikan koordinat mouse. Buat gradien radial dengan pusat di lokasi kursor dan gunakan sebagai mask. Dalam contoh ini, perilaku shader akan lebih mirip dengan logika topeng di SVG daripada di contoh sebelumnya. Kita membutuhkan fungsi mix
dan beberapa fungsi jarak. Yang pertama akan mencampur nilai-nilai warna piksel dari kedua tekstur, dengan mengambil sebagai parameter ketiga koefisien (dari 0 ke 1) yang menentukan nilai mana yang akan menang sebagai hasilnya. Kami mengambil modulus sinus sebagai fungsi jarak - itu hanya akan memberikan perubahan halus dalam nilai antara 0 dan 1.
gl_FragColor = mix( texture2D(u_textures[0], texture_coord), texture2D(u_textures[1], texture_coord), abs(sin(length(texture_coord - u_mouse_position / u_canvas_size))))
Itu saja. Mari kita lihat hasilnya:
Keuntungan utama dibandingkan SVG jelas:
Tidak seperti SVG, di sini kita dapat dengan mudah membuat gradien halus untuk berbagai fungsi matematika, dan tidak mengumpulkannya dari banyak gradien linier.
Jika Anda memiliki tugas yang lebih sederhana yang tidak memerlukan transisi mulus atau bentuk rumit yang dihitung dalam proses, maka kemungkinan besar akan lebih mudah untuk diterapkan tanpa menggunakan shader. Ya, dan kinerja perangkat keras yang lemah cenderung lebih baik. Pilih alat berdasarkan tugas Anda.
Untuk tujuan pendidikan, mari kita lihat contoh lain. Pertama, buat lingkaran di mana teksturnya akan tetap seperti itu:
gl_FragColor = texture2D(u_textures[0], texture_coord); float dist = distance(texture_coord, u_mouse_position / u_canvas_size); if (dist < 0.3) { return; }
Dan isi sisanya dengan garis-garis diagonal:
float value = sin((texture_coord.y - texture_coord.x) * 200.0); if (value > 0.0) { gl_FragColor.rgb *= dist; } else { gl_FragColor.rgb *= dist / 10.0; }
Penerimaannya sama - kami mengalikan parameter untuk sinus untuk meningkatkan frekuensi garis-garis; bagilah nilai yang diperoleh menjadi dua bagian; untuk masing-masing bagian, kami mengubah warna piksel dengan cara kami sendiri. Penting untuk diingat bahwa menggambar garis-garis diagonal biasanya dikaitkan dengan menambahkan koordinat dalam X dan Y. Perhatikan bahwa kami juga menggunakan jarak ke kursor mouse saat mengubah warna, sehingga menciptakan semacam bayangan. Dengan cara yang sama, Anda dapat menggunakannya dengan transformasi geometris, kita akan segera melihat ini pada contoh pixelation. Sementara itu, lihat hasil shader ini:
Sederhana dan cantik.
Dan ya, jika Anda sedikit bingung, Anda dapat membuat tekstur bukan dari gambar, tetapi dari bingkai dari video (ada banyak contoh di jaringan, Anda dapat dengan mudah menemukan mereka), dan menerapkan semua efek kami kepada mereka. Banyak situs direktori seperti Awwwards menggunakan efek ini bersamaan dengan video.
Patut diingat satu pikiran lagi:
Tidak ada yang mengganggu menggunakan salah satu tekstur sebagai topeng. Kita dapat mengambil gambar dan menggunakan nilai warna pikselnya dalam transformasi kami, apakah itu perubahan warna lain, bergeser ke samping atau sesuatu yang lain yang muncul di pikiran Anda.
Namun kembali membagi pesawat menjadi beberapa bagian.
Pixelisasi
Efek ini agak jelas, tetapi pada saat yang sama sangat umum sehingga akan salah untuk melewati. Bagilah bidang kita menjadi bujur sangkar, dengan cara yang sama seperti pada contoh dengan generator derau, dan kemudian untuk semua piksel di dalam setiap kotak kita mengatur warna yang sama. Itu diperoleh dengan mencampurkan nilai-nilai dari sudut-sudut sebuah bujur sangkar, kami sudah melakukan hal serupa. Untuk efek ini, kita tidak perlu rumus rumit, jadi tambahkan saja semua nilai dan bagi dengan 4 - jumlah sudut kotak.
float block_size = abs(sin(u_time)) / 20.0; vec2 block_position = floor(texture_coord / block_size) * block_size; gl_FragColor = ( texture2D(u_textures[0], block_position) + texture2D(u_textures[0], block_position + vec2(1.0, 0.0) * block_size) + texture2D(u_textures[0], block_position + vec2(0.0, 1.0) * block_size) + texture2D(u_textures[0], block_position + vec2(1.0, 1.0) * block_size) ) / 4.0;
Kami kembali mengikat salah satu parameter ke waktu melalui modul sinus untuk melihat secara visual apa yang terjadi ketika itu berubah.
Gelombang piksel
Akhirnya, kita akan sekali lagi menerapkan teknik yang sudah kita ketahui di satu tempat untuk menambahkan gelombang ke contoh piksel.
float block_size = abs(sin( length(texture_coord - u_mouse_position / u_canvas_size) * 2.0 - u_time)) / 100.0 + 0.001;
Kami menggunakan modul sinus untuk menggerakkan semuanya dari 0 hingga 1; tambahkan jarak dari posisi saat ini ke kursor mouse, waktu, dan pilih beberapa koefisien untuk membuat semuanya terlihat indah. Kami menambahkan konstanta kecil ke hasil sehingga tidak ada ukuran nol blok.
"" , , -. . " ", , . . — . .
Ringkasan
, , , , . -. - - . . . , , , .
PS: , WebGL ( ) ? , , . ?