Pengantar Pemrograman Shader untuk Tata Letak


WebGL telah ada sejak lama, banyak artikel telah ditulis tentang shader, ada serangkaian pelajaran. Tetapi sebagian besar, mereka terlalu rumit untuk perancang tata letak. Bahkan lebih baik untuk mengatakan bahwa mereka mencakup sejumlah besar informasi yang dibutuhkan pengembang mesin game daripada perancang tata letak. Mereka segera mulai dengan membangun pemandangan yang kompleks, kamera, cahaya ... Di situs biasa, untuk membuat sepasang efek dengan foto, semua pengetahuan ini berlebihan. Akibatnya, orang membuat struktur arsitektur yang sangat kompleks dan menulis shader panjang, panjang untuk tindakan esensi yang sangat sederhana.


Semua ini mendorong pengantar aspek-aspek bekerja dengan shader yang paling mungkin berguna bagi perancang tata letak untuk membuat berbagai efek 2d dengan gambar di situs. Tentu saja, disesuaikan dengan fakta bahwa mereka sendiri relatif jarang digunakan dalam desain antarmuka. Kami akan membuat templat awal dalam JS murni tanpa pustaka pihak ketiga dan mempertimbangkan ide-ide untuk membuat beberapa efek populer berdasarkan pergeseran piksel, yang sulit dilakukan pada SVG, tetapi pada saat yang sama mereka dengan mudah diimplementasikan menggunakan shader.


Diasumsikan bahwa pembaca sudah terbiasa dengan canvas , menguraikan apa itu WebGL, dan memiliki pengetahuan matematika yang minimal. Beberapa poin akan dijelaskan secara sederhana, bukan akademis, untuk memberikan pemahaman praktis tentang teknologi untuk bekerja dengan mereka, dan bukan teori lengkap tentang dapur batin mereka atau istilah untuk belajar. Ada buku-buku pintar untuk ini.

Harus segera dicatat bahwa editor yang diintegrasikan ke dalam artikel dari CodePen memiliki kemampuan untuk mempengaruhi kinerja dari apa yang dilakukan di dalamnya. Jadi sebelum menulis komentar bahwa ada sesuatu yang melambat di macbook Anda, pastikan masalahnya tidak berasal dari mereka.


Ide utama


Apa itu shader?


Apa itu shader fragmen? Ini pada dasarnya adalah program kecil. Ini dieksekusi untuk setiap pixel di anvas . Jika kita memiliki canvas ukuran 1000x500px, maka program ini akan mengeksekusi 500.000 kali, setiap kali menerima sebagai input parameter koordinat piksel yang saat ini sedang dijalankan. Ini semua terjadi pada GPU dalam berbagai utas paralel. Pada prosesor pusat, perhitungan seperti itu akan memakan waktu lebih lama.


Vertex shader juga merupakan program, tetapi tidak dieksekusi untuk setiap piksel pada canvas , tetapi untuk setiap simpul dalam bentuk dari mana semuanya dibangun dalam ruang tiga dimensi. Juga sejajar dengan semua simpul. Oleh karena itu, input menerima koordinat titik, bukan piksel.


Lebih jauh dalam konteks tugas kita, berikut ini terjadi:


  • Kami mengambil satu set koordinat simpul persegi panjang, di mana foto itu kemudian akan "digambar".
  • Vertex shader untuk setiap titik mempertimbangkan lokasinya di ruang angkasa. Bagi kami, ini akan datang ke kasing khusus - pesawat sejajar dengan layar. Foto dalam 3d tidak kita butuhkan. Proyeksi selanjutnya ke bidang layar dapat mengatakan apa-apa.
  • Lebih jauh untuk setiap fragmen yang terlihat, dan dalam konteks kami untuk semua fragmen piksel, shader fragmen dijalankan, ia mengambil foto dan koordinat saat ini, menghitung sesuatu dan memberikan warna untuk piksel tertentu ini.
  • Jika tidak ada logika dalam fragmen shader, maka perilaku semua ini akan menyerupai metode drawImage() canvas . Tapi kemudian kita tambahkan logika ini dan dapatkan banyak hal menarik.

Ini adalah deskripsi yang sangat sederhana, tetapi harus jelas siapa yang melakukan apa.


Sedikit tentang sintaksis


Shader ditulis dalam GLSL - OpenGL Shading Language. Bahasa ini sangat mirip dengan C. Tidak masuk akal untuk menggambarkan seluruh sintaksis dan metode standar di sini, tetapi Anda selalu dapat menggunakan lembar contekan:


Spoiler dengan gambar





Setiap shader memiliki fungsi utama, yang dengannya eksekusi dimulai. Parameter input standar untuk shader dan output dari hasil pekerjaan mereka diimplementasikan melalui variabel khusus dengan awalan gl_ . Mereka dilindungi terlebih dahulu dan tersedia di dalam shader yang sama. Jadi koordinat vertex untuk letak vertex shader dalam variabel gl_Position , koordinat fragmen (pixel) untuk fragmen shader terletak di gl_FragCoord , dll. Anda selalu dapat menemukan daftar lengkap variabel khusus yang tersedia di lembar contekan yang sama.


Jenis variabel utama dalam GLSL agak bersahaja - void , bool , int , float ... Jika Anda bekerja dengan bahasa C-like, Anda sudah melihatnya. Ada jenis lain, khususnya vektor dimensi yang berbeda - vec2 , vec3 , vec4 . Kami akan terus menggunakannya untuk koordinat dan warna. Variabel yang dapat kita buat adalah dari tiga modifikasi penting:


  • Seragam - Data global dalam segala hal. Melewati dari luar, sama untuk semua panggilan shader vertex dan fragmen.
  • Atribut - Data ini ditransfer lebih tepat dan untuk setiap panggilan shader mungkin berbeda.
  • Memvariasikan - Diperlukan untuk mentransfer data dari vertex shaders ke fragmen shaders.

Sangat berguna untuk awalan u / a / v ke semua variabel di shader untuk membuatnya lebih mudah untuk memahami dari mana data berasal.

Saya percaya bahwa ada baiknya beralih ke contoh praktis untuk segera menonton semua ini dalam tindakan dan tidak memuat memori Anda.


Template mulai memasak


Mari kita mulai dengan JS. Seperti yang biasanya terjadi ketika bekerja dengan canvas , kita membutuhkannya dan konteksnya. Agar tidak memuat kode sampel, kami akan membuat variabel global:


 const CANVAS = document.getElementById(IDs.canvas); const GL = canvas.getContext('webgl'); 

Lewati momen yang terkait dengan ukuran canvas dan perhitungan ulangnya saat mengubah ukuran jendela browser. Kode ini termasuk dalam contoh dan biasanya tergantung pada sisa tata letak. Tidak masuk akal untuk fokus padanya. Mari beralih ke tindakan dengan WebGL.


 function createProgram() { const shaders = getShaders(); PROGRAM = GL.createProgram(); GL.attachShader(PROGRAM, shaders.vertex); GL.attachShader(PROGRAM, shaders.fragment); GL.linkProgram(PROGRAM); GL.useProgram(PROGRAM); } 

Pertama, kita kompilasi shader (itu akan sedikit lebih rendah), buat program, tambahkan kedua shader kita ke dalamnya dan buat tautan. Pada titik ini, kompatibilitas shader diperiksa. Ingat tentang berbagai variabel yang diteruskan dari verteks ke fragmen? - Secara khusus, set mereka diperiksa di sini sehingga kemudian dalam proses itu tidak ternyata sesuatu belum ditransmisikan atau ditransmisikan, tetapi tidak sama sekali. Tentu saja, pemeriksaan ini tidak akan mengungkapkan kesalahan logis, saya pikir ini bisa dimengerti.


Koordinat simpul akan disimpan dalam array buffer khusus dan akan dikirim dalam bentuk potongan, satu simpul, ke setiap panggilan shader. Selanjutnya, kami menjelaskan beberapa detail untuk bekerja dengan potongan-potongan ini. Pertama, kita akan menggunakan koordinat vertex di shader melalui a_position atribut a_position . Itu bisa disebut berbeda, itu tidak masalah. Kami mendapatkan lokasinya (ini adalah sesuatu seperti pointer di C, tetapi bukan pointer, melainkan nomor entitas yang hanya ada di dalam program).


 const vertexPositionAttribute = GL.getAttribLocation(PROGRAM, 'a_position'); 

Selanjutnya, kami menunjukkan bahwa array dengan koordinat akan melewati variabel ini (dalam shader itu sendiri, kami akan menganggapnya sebagai vektor). WebGL akan secara mandiri mencari tahu koordinat mana dari titik-titik dalam bentuk kami yang harus diteruskan ke panggilan shader mana. Kami hanya mengatur parameter untuk array vektor yang akan ditransmisikan: dimensi - 2 (kami akan mengirimkan koordinat (x,y) ), terdiri dari angka dan tidak dinormalisasi. Parameter terakhir tidak menarik bagi kami, kami membiarkan nol secara default.


 GL.enableVertexAttribArray(vertexPositionAttribute); GL.vertexAttribPointer(vertexPositionAttribute, 2, GL.FLOAT, false, 0, 0); 

Sekarang buat buffer itu sendiri dengan koordinat simpul pesawat kami, di mana foto itu akan ditampilkan. Koordinat "2d" lebih jelas, tetapi untuk tugas kita ini adalah hal yang paling penting.


 function createPlane() { GL.bindBuffer(GL.ARRAY_BUFFER, GL.createBuffer()); GL.bufferData( GL.ARRAY_BUFFER, new Float32Array([ -1, -1, -1, 1, 1, -1, 1, 1 ]), GL.STATIC_DRAW ); } 

Kotak ini akan cukup untuk semua contoh kami. STATIC_DRAW berarti bahwa buffer dimuat satu kali dan kemudian akan digunakan kembali. Kami tidak akan mengunggah apa pun lagi.


Sebelum beralih ke shader sendiri, mari kita lihat kompilasi mereka:


 function getShaders() { return { vertex: compileShader( GL.VERTEX_SHADER, document.getElementById(IDs.shaders.vertex).textContent ), fragment: compileShader( GL.FRAGMENT_SHADER, document.getElementById(IDs.shaders.fragment).textContent ) }; } function compileShader(type, source) { const shader = GL.createShader(type); GL.shaderSource(shader, source); GL.compileShader(shader); return shader; } 

Kami mendapatkan kode shader dari elemen-elemen pada halaman, membuat shader dan mengompilasinya. Secara teori, Anda dapat menyimpan kode shader dalam file terpisah dan memuatnya selama perakitan sebagai string di tempat yang tepat, tetapi CodePen tidak memberikan kesempatan seperti itu untuk contoh. Banyak pelajaran menyarankan penulisan kode langsung di JS, tetapi bahasa tidak mengubahnya menjadi bahasa yang nyaman. Meskipun tentu saja rasanya dan warnanya ...


Jika kesalahan terjadi selama kompilasi, skrip akan terus berjalan menampilkan beberapa peringatan di konsol yang tidak masuk akal. Sangat berguna untuk melihat log setelah kompilasi agar tidak memutar otak Anda atas apa yang tidak dikompilasi di sana:


 console.log(GL.getShaderInfoLog(shader)); 

WebGL menyediakan beberapa opsi berbeda untuk melacak masalah saat menyusun shader dan membuat program, tetapi dalam praktiknya ternyata secara real time kami tidak dapat memperbaiki apa pun. Begitu sering kita akan dibimbing oleh pemikiran "jatuh - lalu jatuh" dan kita tidak akan memuat kode dengan banyak cek tambahan.

Mari kita beralih ke shader sendiri


Karena kita hanya akan memiliki satu bidang yang kita tidak akan melakukan apa pun, satu vertex shader sederhana sudah cukup bagi kita, yang akan kita lakukan di awal. Upaya utama akan difokuskan pada fragmen shader dan semua contoh berikutnya akan relevan bagi mereka.


Cobalah untuk menulis kode shader dengan nama variabel yang kurang lebih bermakna. Pada jaringan Anda akan menemukan contoh-contoh di mana fungsi dengan matematika yang kuat untuk 200 baris teks berkelanjutan akan dirangkai dari variabel satu huruf, tetapi hanya karena seseorang tidak berarti itu perlu diulang. Pendekatan semacam itu bukan "kekhususan bekerja dengan GL", itu adalah copy-paste kode sumber dangkal dari abad terakhir yang ditulis oleh orang-orang yang di masa muda mereka memiliki batasan pada panjang nama variabel.

Pertama, vertex shader. Vektor 2d dengan koordinat (x,y) akan ditransfer ke variabel atribut a_position , seperti yang kami katakan. Shader harus mengembalikan vektor empat nilai (x,y,z,w) . Itu tidak akan memindahkan apa pun di ruang angkasa, jadi pada sumbu z, kita cukup nol semuanya dan mengatur nilai w ke unit standar. Jika Anda bertanya-tanya mengapa ada empat daripada tiga koordinat, maka Anda dapat menggunakan pencarian jaringan untuk "koordinat seragam".


 <script id='vertex-shader' type='x-shader/x-vertex'> precision mediump float; attribute vec2 a_position; void main() { gl_Position = vec4(position, 0, 1); } </script> 

Hasil pekerjaan direkam dalam variabel khusus gl_Position . Shader tidak memiliki return dalam arti penuh kata, mereka menuliskan semua hasil pekerjaan mereka dalam variabel yang khusus disediakan untuk tujuan ini.


Perhatikan pekerjaan presisi untuk tipe data float. Untuk menghindari beberapa masalah pada perangkat seluler, akurasi harus lebih buruk daripada highp dan harus sama di kedua shader. Ini diperlihatkan sebagai contoh di sini, tetapi merupakan praktik yang baik pada ponsel untuk mematikan kecantikan semacam itu dengan shader sama sekali.

Shader fragmen akan selalu mengembalikan warna yang sama untuk memulai. Kotak kami akan menempati seluruh canvas , jadi sebenarnya di sini kami mengatur warna untuk setiap piksel:


 <script id='fragment-shader' type='x-shader/x-fragment'> precision mediump float; #define GOLD vec4(1.0, 0.86, 0.6, 1.0) void main() { gl_FragColor = GOLD; } </script> 

Anda bisa memperhatikan angka yang menggambarkan warna. Ini biasa bagi semua jenis huruf RGBA, hanya dinormalisasi. Nilai bukan bilangan bulat dari 0 hingga 255, tetapi fraksional dari 0 hingga 1. Urutannya sama.


Jangan lupa untuk menggunakan preprocessor untuk semua konstanta ajaib dalam proyek nyata - ini membuat kode lebih dimengerti tanpa mempengaruhi kinerja (substitusi, seperti dalam C, terjadi selama kompilasi).

Perlu diperhatikan poin lain tentang preprosesor:


Penggunaan pemeriksaan konstan #jikaef GL_ES dalam berbagai pelajaran tidak memiliki arti praktis. di browser kami hari ini, tidak ada opsi GL lain yang ada.

Tapi sudah waktunya untuk melihat hasilnya:



Kotak emas menunjukkan bahwa shader berfungsi seperti yang diharapkan. Masuk akal untuk bermain-main dengan mereka sedikit sebelum beralih ke bekerja dengan foto.


Gradien dan mengubah vektor


Biasanya, tutorial WebGL mulai dengan menggambar gradien. Ini praktis tidak masuk akal, tetapi akan berguna untuk mencatat beberapa poin.


 void main() { gl_FragColor = vec4(gl_FragCoord.zxy / 500.0, 1.0); } 

Dalam contoh ini, kami menggunakan koordinat piksel saat ini sebagai warna. Anda akan sering melihat ini dalam contoh di internet. Keduanya adalah vektor. Jadi tidak ada yang mengganggu untuk mencampur semuanya dalam tumpukan. Penginjil TypeScript harus memiliki serangan di sini. Poin penting adalah bagaimana kita hanya mendapatkan sebagian dari koordinat dari vektor. Properti .xy , .zy , .zyx , .xyzw , .xyzw , .xyzw , .xyzw , .xyzw , dll. dalam urutan yang berbeda memungkinkan Anda untuk mengeluarkan elemen-elemen vektor dalam urutan tertentu dalam bentuk vektor lain. Sangat mudah diimplementasikan. Juga, vektor dimensi tinggi dapat dibuat dari vektor dimensi rendah dengan menambahkan nilai yang hilang, seperti yang kami lakukan.


Selalu nyatakan bagian pecahan angka secara eksplisit. Tidak ada konversi int otomatis -> mengambang di sini.


Seragam dan berlalunya waktu


Contoh berguna berikutnya adalah penggunaan seragam. Ini adalah data yang paling umum untuk semua panggilan shader. Kami mendapatkan lokasi mereka dengan cara yang sama seperti untuk variabel atribut, misalnya:


 GL.getUniformLocation(PROGRAM, 'u_time') 

Lalu kita bisa mengatur nilai-nilai mereka sebelum setiap frame. Seperti halnya vektor, ada banyak metode serupa di sini, dimulai dengan kata uniform , kemudian muncul dimensi variabel (1 untuk angka, 2, 3 atau 4 untuk vektor) dan ketik (f - float, i - int, v - vector) .


 function draw(timeStamp) { GL.uniform1f(GL.getUniformLocation(PROGRAM, 'u_time'), timeStamp / 1000.0); GL.drawArrays(GL.TRIANGLE_STRIP, 0, 4); window.requestAnimationFrame(draw); } 

Faktanya, kita tidak selalu membutuhkan antarmuka 60fps. Sangat mungkin untuk menambahkan pelambatan ke requestAnimationFrame dan mengurangi frekuensi menggambar ulang frame.

Sebagai contoh, kami akan mengubah warna isian. Dalam shader, semua fungsi matematika dasar tersedia - sin , cos , tan , asin , acos , atan , pow , exp , log , sqrt , abs , dan lainnya. Kami akan menggunakan dua dari mereka.


 uniform float u_time; void main() { gl_FragColor = vec4( abs(sin(u_time)), abs(sin(u_time * 3.0)), abs(sin(u_time * 5.0)), 1.0); } 

Waktu dalam animasi semacam itu adalah konsep yang relatif. Di sini kita menggunakan nilai-nilai yang diberikan oleh requestAnimationFrame , tetapi kita dapat membuat "waktu" kita sendiri. Idenya adalah bahwa jika beberapa parameter dijelaskan oleh fungsi waktu, maka kita dapat mengubah waktu dalam arah yang berlawanan, memperlambat, mempercepatnya atau kembali ke keadaan semula. Ini bisa sangat membantu.



Tapi contoh abstrak yang cukup, mari kita beralih ke menggunakan gambar.


Memuat gambar ke dalam tekstur


Untuk menggunakan gambar, kita perlu membuat tekstur, yang kemudian akan ditampilkan di pesawat kita. Untuk memulai, muat gambar itu sendiri:


 function createTexture() { const image = new Image(); image.crossOrigin = 'anonymous'; image.onload = () => { // .... }; image.src = 'example.jpg'; } 

Setelah dimuat, buat tekstur dan tunjukkan bahwa ia akan pergi di nomor 0. Di WebGL, bisa ada banyak tekstur pada saat yang sama dan kita harus secara eksplisit menunjukkan di mana perintah selanjutnya akan berhubungan. Dalam contoh kami, hanya akan ada satu tekstur, tetapi kami masih secara eksplisit menunjukkan bahwa itu akan menjadi nol.


 const texture = GL.createTexture(); GL.activeTexture(GL.TEXTURE0); GL.bindTexture(GL.TEXTURE_2D, texture); 

Tetap menambahkan gambar. Kami juga segera mengatakan bahwa itu perlu diputar sepanjang sumbu Y, karena di WebGL, porosnya terbalik:


 GL.pixelStorei(GL.UNPACK_FLIP_Y_WEBGL, true); GL.texImage2D(GL.TEXTURE_2D, 0, GL.RGB, GL.RGB, GL.UNSIGNED_BYTE, image); 

Secara teori, teksturnya harus persegi. Lebih tepatnya, mereka bahkan harus memiliki ukuran yang sama dengan kekuatan dua - 32px, 64px, 128px, dll. Tapi kita semua mengerti bahwa tidak ada yang akan memproses foto dan mereka akan berada dalam proporsi yang berbeda setiap kali. Ini akan menyebabkan kesalahan bahkan jika canvas dalam ukuran cocok dengan tekstur. Oleh karena itu, kami mengisi seluruh ruang ke tepi pesawat dengan piksel gambar yang ekstrem. Ini adalah praktik standar, meskipun tampaknya sedikit penopang.


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

Tetap mentransfer tekstur ke shader. Data ini umum untuk semua, jadi kami menggunakan pengubah uniform .


 GL.uniform1i(GL.getUniformLocation(PROGRAM, 'u_texture'), 0); 

Sekarang kita bisa menggunakan warna dari tekstur dalam shader fragmen. Tapi kami juga ingin gambarnya menempati seluruh canvas . Jika gambar dan canvas memiliki proporsi yang sama, maka tugas ini menjadi sepele. Pertama, kami mentransfer ukuran canvas ke shader (ini harus dilakukan setiap kali Anda mengubah ukurannya):


 GL.uniform1f(GL.getUniformLocation(PROGRAM, 'u_canvas_size'), Math.max(CANVAS.height, CANVAS.width)); 

Dan bagikan koordinat ke dalamnya:


 uniform sampler2D u_texture; uniform float u_canvas_size; void main() { gl_FragColor = texture2D(u_texture, gl_FragCoord.xy / u_canvas_size); } 


Pada titik ini, Anda dapat menghentikan sebentar dan menyeduh teh. Kami telah melakukan semua pekerjaan persiapan dan beralih ke menciptakan berbagai efek.


Efek


Dalam menciptakan berbagai efek, intuisi dan eksperimen memainkan peran penting. Seringkali, Anda dapat mengganti algoritma yang kompleks dengan sesuatu yang sangat sederhana dan memberikan hasil yang serupa. Pengguna akhir tidak akan melihat perbedaannya, tetapi kami mempercepat pekerjaan dan menyederhanakan dukungan. WebGL tidak menyediakan alat yang masuk akal untuk debugging shader, sehingga bermanfaat bagi kita untuk memiliki potongan kode kecil yang dapat masuk di kepala secara keseluruhan.


Lebih sedikit kode berarti lebih sedikit masalah. Dan lebih mudah dibaca. Selalu periksa shader yang ditemukan di jaringan untuk tindakan yang tidak perlu. Itu terjadi bahwa Anda dapat menghapus setengah kode dan tidak ada yang akan berubah.

Mari kita bermain sedikit dengan shader. Sebagian besar efek kami akan didasarkan pada kenyataan bahwa kami mengembalikan warna bukan dari piksel pada tekstur yang seharusnya ada di tempat ini, tetapi beberapa yang bersebelahan. Berguna untuk menambahkan koordinat hasil fungsi standar koordinat. Waktu juga akan berguna untuk digunakan - sehingga hasil eksekusi akan lebih mudah dilacak, dan pada akhirnya, kami masih akan membuat efek animasi. Mari kita coba gunakan sinus:


 gl_FragColor = texture2D(u_texture, gl_FragCoord.xy / u_canvas_size + sin(u_time + gl_FragCoord.y)); 

Hasilnya aneh. Jelas, semuanya bergerak dengan terlalu banyak amplitudo. Bagilah semuanya dengan beberapa nomor:


 gl_FragColor = texture2D(u_texture, gl_FragCoord.xy / u_canvas_size + sin(u_time + gl_FragCoord.y) / 250.0); 

Sudah lebih baik. Sekarang jelas bahwa kami mendapat sedikit kegembiraan. Secara teori, untuk meningkatkan setiap gelombang, kita perlu membagi argumen sinus - koordinat. Mari kita lakukan:


 gl_FragColor = texture2D(u_texture, gl_FragCoord.xy / u_canvas_size + sin(u_time + gl_FragCoord.y / 30.0) / 250.0); 


Efek serupa sering disertai dengan pemilihan koefisien. Ini dilakukan dengan mata. Seperti halnya memasak, pada awalnya akan sulit ditebak, tetapi kemudian akan terjadi dengan sendirinya. Hal utama adalah untuk setidaknya memahami kira-kira apa koefisien ini atau itu dalam formula yang dihasilkan mempengaruhi. Setelah koefisien dipilih, masuk akal untuk memasukkannya ke dalam makro (seperti contoh pertama) dan memberikan nama yang bermakna.


Cermin bengkok, sepeda dan eksperimen


Berpikir itu baik. Ya, ada algoritma yang sudah jadi untuk memecahkan beberapa masalah yang bisa kita ambil dan gunakan. , .


, " ", . Apa yang harus dilakukan


, , ? . , rand() - . , , , , . . . , . . . -, . . , , , . , "":


 float rand(vec2 seed) { return fract(sin(dot(seed, vec2(12.9898,78.233))) * 43758.5453123); } 

, , , NVIDIA ATI . , .


, , :


 gl_FragColor = texture2D(u_texture, gl_FragCoord.xy / u_canvas_size + rand(gl_FragCoord.xy) / 100.0); 

:


 gl_FragColor = texture2D(u_texture, gl_FragCoord.xy / u_canvas_size + rand(gl_FragCoord.xy + vec2(sin(u_time))) / 250.0); 

, , :



, . , , . — . Bagaimana cara melakukannya? . .


0 1, - . 5 — . , .


 vec2 texture_coord = gl_FragCoord.xy / u_canvas_size; gl_FragColor = texture2D(u_texture, texture_coord + rand(floor(texture_coord * 5.0) + vec2(sin(u_time))) / 100.0); 

, - . - . , , . ?


, , , - . , . , .. -. , . . , , . .


sin cos , . . .


 gl_FragColor = texture2D(u_texture, texture_coord + vec2( noise(texture_coord * 10.0 + sin(u_time + texture_coord.x * 5.0)) / 10.0, noise(texture_coord * 10.0 + cos(u_time + texture_coord.y * 5.0)) / 10.0)); 

. fract . 1 1 — :


 float noise(vec2 position) { vec2 block_position = floor(position); float top_left_value = rand(block_position); float top_right_value = rand(block_position + vec2(1.0, 0.0)); float bottom_left_value = rand(block_position + vec2(0.0, 1.0)); float bottom_right_value = rand(block_position + vec2(1.0, 1.0)); vec2 computed_value = fract(position); // ... } 

. WebGL smoothstep , :


 vec2 computed_value = smoothstep(0.0, 1.0, fract(position)); 

, . , X :


 return computed_value.x; 


… , , ...


- , , ... .

y — , . ?


 return length(computed_value); 


.


. 0.5 — .


 return mix(top_left_value, top_right_value, computed_value.x) + (bottom_left_value - top_left_value) * computed_value.y * (1.0 - computed_value.x) + (bottom_right_value - top_right_value) * computed_value.x * computed_value.y - 0.5; 

:



, , , .



, , . - .


uniform-, . 0 1, 0 — , 1 — .


 uniform float u_intensity; 

:


 gl_FragColor = texture2D(u_texture, texture_coord + vec2(noise(texture_coord * 10.0 + sin(u_time + texture_coord.x * 5.0)) / 10.0, noise(texture_coord * 10.0 + cos(u_time + texture_coord.y * 5.0)) / 10.0) * u_intensity); 

, .



( 0 1), .


, , , . — requestAnimationFrame. , FPS.


, . uniform-.


 document.addEventListener('mousemove', (e) => { let rect = CANVAS.getBoundingClientRect(); MOUSE_POSITION = [ e.clientX - rect.left, rect.height - (e.clientY - rect.top) ]; GL.uniform2fv(GL.getUniformLocation(PROGRAM, 'u_mouse_position'), MOUSE_POSITION); }); 

, . — , .


 void main() { vec2 texture_coord = gl_FragCoord.xy / u_canvas_size; vec2 direction = u_mouse_position / u_canvas_size - texture_coord; float dist = distance(gl_FragCoord.xy, u_mouse_position) / u_canvas_size; if (dist < 0.4) { gl_FragColor = texture2D(u_texture, texture_coord + u_intensity * direction * dist * 1.2 ); } else { gl_FragColor = texture2D(u_texture, texture_coord); } } 

- . .


. , .



. Glitch- , SVG. . — . ? — , , , .


 float random_value = rand(vec2(texture_coord.y, u_time)); if (random_value < 0.05) { gl_FragColor = texture2D(u_texture, vec2(texture_coord.x + random_value / 5.0, texture_coord.y)); } else { gl_FragColor = texture2D(u_texture, texture_coord); } 

" ?" — , . .

. — , .


 float random_value = rand(vec2(floor(texture_coord.y * 20.0), u_time)); 

. , :


 gl_FragColor = texture2D(u_texture, vec2(texture_coord.x + random_value / 4.0, texture_coord.y)) + vec4(vec3(random_value), 1.0); 

. — . , — .r , .g , .b , .rg , .rb , .rgb , .bgr , ... .


:


 float random_value = u_intensity * rand(vec2(floor(texture_coord.y * 20.0), u_time)); 


Apa hasilnya?


, , . , , — .

Source: https://habr.com/ru/post/id420847/


All Articles