Buat shader air kartun untuk web. Bagian 3

gambar

Pada bagian kedua, kami memeriksa garis apung dan busa. Pada bagian terakhir ini, kami menerapkan distorsi bawah air sebagai efek pasca pemrosesan.

Efek refraksi dan pasca pemrosesan


Tujuan kami adalah untuk menyampaikan pembiasan cahaya dalam air secara visual. Kami sudah bicara tentang cara membuat distorsi jenis ini dalam fragmen shader untuk adegan 2D. Di sini satu-satunya perbedaan adalah bahwa kita perlu memahami area layar di bawah air dan hanya menerapkan distorsi.

Pemrosesan pos


Dalam kasus umum, efek pasca pemrosesan adalah efek apa pun yang diterapkan ke seluruh adegan setelah rendering, misalnya, nuansa warna atau efek layar CRT lama . Alih-alih merender adegan langsung ke layar, kami pertama-tama merendernya ke buffer atau tekstur, dan kemudian, melewati adegan melalui shader kami, kami merender ke layar.

Di PlayCanvas, Anda dapat menyesuaikan efek pasca pemrosesan ini dengan membuat skrip baru. Sebut saja Refraction.js dan salin templat ini ke dalamnya sebagai kosong:

//---------------   ------------------------// pc.extend(pc, function () { //  -      var RefractionPostEffect = function (graphicsDevice, vs, fs, buffer) { var fragmentShader = "precision " + graphicsDevice.precision + " float;\n"; fragmentShader = fragmentShader + fs; //      this.shader = new pc.Shader(graphicsDevice, { attributes: { aPosition: pc.SEMANTIC_POSITION }, vshader: vs, fshader: fs }); this.buffer = buffer; }; //      pc.PostEffect RefractionPostEffect = pc.inherits(RefractionPostEffect, pc.PostEffect); RefractionPostEffect.prototype = pc.extend(RefractionPostEffect.prototype, { //      render, //    ,    , //       render: function (inputTarget, outputTarget, rect) { var device = this.device; var scope = device.scope; //       .  ,     scope.resolve("uColorBuffer").setValue(inputTarget.colorBuffer); //       .       . //          pc.drawFullscreenQuad(device, outputTarget, this.vertexBuffer, this.shader, rect); } }); return { RefractionPostEffect: RefractionPostEffect }; }()); //---------------  ------------------------// var Refraction = pc.createScript('refraction'); Refraction.attributes.add('vs', { type: 'asset', assetType: 'shader', title: 'Vertex Shader' }); Refraction.attributes.add('fs', { type: 'asset', assetType: 'shader', title: 'Fragment Shader' }); //  initialize       Refraction.prototype.initialize = function() { var effect = new pc.RefractionPostEffect(this.app.graphicsDevice, this.vs.resource, this.fs.resource); //     postEffects var queue = this.entity.camera.postEffects; queue.addEffect(effect); this.effect = effect; //       this.savedVS = this.vs.resource; this.savedFS = this.fs.resource; }; Refraction.prototype.update = function(){ if(this.savedFS != this.fs.resource || this.savedVS != this.vs.resource){ this.swap(this); } }; Refraction.prototype.swap = function(old){ this.entity.camera.postEffects.removeEffect(old.effect); this.initialize(); }; 

Ini mirip dengan skrip biasa, tetapi kami mendefinisikan kelas RefractionPostEffect yang dapat diterapkan pada kamera. Untuk rendering, perlu shader vertex dan fragmen. Atribut sudah dikonfigurasi, jadi mari kita buat Refraction.frag dengan konten berikut:

 precision highp float; uniform sampler2D uColorBuffer; varying vec2 vUv0; void main() { vec4 color = texture2D(uColorBuffer, vUv0); gl_FragColor = color; } 

Dan Refraction.vert dengan vertex shader dasar:

 attribute vec2 aPosition; varying vec2 vUv0; void main(void) { gl_Position = vec4(aPosition, 0.0, 1.0); vUv0 = (aPosition.xy + 1.0) * 0.5; } 

Sekarang lampirkan skrip Refraction.js ke kamera dan tetapkan atribut yang sesuai untuk shader. Saat Anda memulai permainan, Anda akan melihat pemandangannya dengan cara yang sama seperti sebelumnya. Ini adalah efek pasca-kosong yang hanya merender ulang adegan. Untuk memastikan itu bekerja, mari kita coba berikan adegan warna merah.

Alih-alih mengembalikan warna ke Refraction.frag, coba atur komponen merah ke 1.0, yang akan memberi gambar gambar yang ditunjukkan di bawah ini.


Shader distorsi


Untuk membuat distorsi animasi, kita perlu menambahkan variabel waktu yang seragam, jadi mari kita buat di dalam konstruktor pasca-efek ini di Refraction.js:

 var RefractionPostEffect = function (graphicsDevice, vs, fs) { var fragmentShader = "precision " + graphicsDevice.precision + " float;\n"; fragmentShader = fragmentShader + fs; //       this.shader = new pc.Shader(graphicsDevice, { attributes: { aPosition: pc.SEMANTIC_POSITION }, vshader: vs, fshader: fs }); // >>>>>>>>>>>>>    this.time = 0; }; 

Sekarang di dalam fungsi render, kami meneruskannya ke shader kami untuk meningkatkannya:

 RefractionPostEffect.prototype = pc.extend(RefractionPostEffect.prototype, { //      render, //      , //       render: function (inputTarget, outputTarget, rect) { var device = this.device; var scope = device.scope; //       .  ,     scope.resolve("uColorBuffer").setValue(inputTarget.colorBuffer); /// >>>>>>>>>>>>>>>>>>    uniform-  scope.resolve("uTime").setValue(this.time); this.time += 0.1; //       .       . //          pc.drawFullscreenQuad(device, outputTarget, this.vertexBuffer, this.shader, rect); } }); 

Sekarang kita dapat menggunakan kode shader yang sama dari tutorial distorsi air, mengubah shader fragmen penuh kami menjadi sebagai berikut:

 precision highp float; uniform sampler2D uColorBuffer; uniform float uTime; varying vec2 vUv0; void main() { vec2 pos = vUv0; float X = pos.x*15.+uTime*0.5; float Y = pos.y*15.+uTime*0.5; pos.y += cos(X+Y)*0.01*cos(Y); pos.x += sin(XY)*0.01*sin(Y); vec4 color = texture2D(uColorBuffer, pos); gl_FragColor = color; } 

Jika semuanya dilakukan dengan benar, maka seluruh gambar akan terlihat seperti benar-benar di bawah air.


Tugas 1: Pastikan distorsi hanya berlaku di bagian bawah layar.

Masker kamera


Kami hampir selesai. Tetap bagi kita untuk menerapkan efek distorsi ini ke bagian bawah air layar. Cara termudah yang saya pikirkan adalah membuat kembali pemandangan dengan permukaan air yang berwarna putih pekat, seperti yang ditunjukkan pada gambar di bawah ini.


Ini akan membuat tekstur yang kita gunakan sebagai topeng. Kemudian kami akan mentransfer tekstur ini ke shader refraksi kami, yang akan mendistorsi piksel pada gambar yang sudah jadi hanya ketika piksel yang sesuai dalam topeng berwarna putih.

Mari kita tambahkan atribut boolean ke permukaan air untuk mengetahui apakah itu digunakan sebagai topeng. Tambahkan yang berikut ke Water.js:

 Water.attributes.add('isMask', {type:'boolean',title:"Is Mask?"}); 

Kemudian, seperti biasa, kita bisa meneruskannya ke shader menggunakan material.setParameter('isMask',this.isMask); . Kemudian nyatakan dalam Water.frag dan warnai piksel putih jika atributnya benar.

 //    uniform uniform bool isMask; //      ,    //    true if(isMask){ color = vec4(1.0); } 

Pastikan ini berfungsi dengan menyalakan properti "Is Mask?". di editor dan memulai kembali game. Seharusnya terlihat putih, seperti pada gambar di atas.

Sekarang, untuk merender ulang adegan itu, kita perlu kamera kedua. Buat kamera baru di editor dan sebut saja CameraMask . Kami juga menduplikasi entitas Air di editor dan memberi nama WaterMask duplikat. Pastikan untuk entitas "Water Is Is Mask?" salah, dan WaterMask benar.

Untuk memesan kamera baru agar dirender menjadi tekstur, bukan ke layar, buat skrip CameraMask.js baru dan pasangkan ke kamera baru. Kami membuat Target Render untuk menangkap output dari kamera ini:

 //  initialize       CameraMask.prototype.initialize = function() { //  512x512x24-      var colorBuffer = new pc.Texture(this.app.graphicsDevice, { width: 512, height: 512, format: pc.PIXELFORMAT_R8_G8_B8, autoMipmap: true }); colorBuffer.minFilter = pc.FILTER_LINEAR; colorBuffer.magFilter = pc.FILTER_LINEAR; var renderTarget = new pc.RenderTarget(this.app.graphicsDevice, colorBuffer, { depth: true }); this.entity.camera.renderTarget = renderTarget; }; 

Sekarang setelah meluncurkan aplikasi, Anda akan melihat bahwa kamera ini tidak lagi menampilkan layar. Kita bisa mendapatkan output dari target render di Refraction.js sebagai berikut:

 Refraction.prototype.initialize = function() { var cameraMask = this.app.root.findByName('CameraMask'); var maskBuffer = cameraMask.camera.renderTarget.colorBuffer; var effect = new pc.RefractionPostEffect(this.app.graphicsDevice, this.vs.resource, this.fs.resource, maskBuffer); // ... //     ,    }; 

Perhatikan bahwa saya meneruskan tekstur topeng ini sebagai argumen ke konstruktor pasca-efek. Kita perlu membuat tautan di konstruktor kita, sehingga akan terlihat seperti ini:

 ////       var RefractionPostEffect = function (graphicsDevice, vs, fs, buffer) { var fragmentShader = "precision " + graphicsDevice.precision + " float;\n"; fragmentShader = fragmentShader + fs; //       this.shader = new pc.Shader(graphicsDevice, { attributes: { aPosition: pc.SEMANTIC_POSITION }, vshader: vs, fshader: fs }); this.time = 0; //// <<<<<<<<<<<<<    this.buffer = buffer; }; 

Akhirnya, dalam fungsi render, kami meneruskan buffer ke shader kami:

 scope.resolve("uMaskBuffer").setValue(this.buffer); 

Sekarang, untuk memastikan bahwa semua ini berfungsi, saya akan menyerahkannya kepada Anda sebagai tugas.

Tugas 2: render uMaskBuffer di layar untuk memastikan bahwa itu adalah output dari kamera kedua.

Yang berikut harus dipertimbangkan: target render dikonfigurasikan dalam inisialisasi skrip CameraMask.js, dan harus siap pada saat Refraction.js dipanggil. Jika skrip bekerja secara berbeda, maka kami mendapatkan kesalahan. Untuk memastikan mereka bekerja dalam urutan yang benar, seret CameraMask ke atas daftar entitas di editor, seperti yang ditunjukkan di bawah ini.


Kamera kedua harus selalu terlihat dengan tampilan yang sama seperti yang asli, jadi mari kita membuatnya selalu mengikuti posisi dan rotasi skrip CameraMask.js dalam pembaruan:

 CameraMask.prototype.update = function(dt) { var pos = this.CameraToFollow.getPosition(); var rot = this.CameraToFollow.getRotation(); this.entity.setPosition(pos.x,pos.y,pos.z); this.entity.setRotation(rot); }; 

Dalam inisialisasi, tentukan CameraToFollow :

 this.CameraToFollow = this.app.root.findByName('Camera'); 

Masker kliping


Kedua kamera sekarang memberikan hal yang sama. Kami ingin kamera topeng membuat semuanya kecuali air sungguhan, dan kamera nyata membuat semuanya kecuali air topeng.

Untuk melakukan ini, kita dapat menggunakan topeng kliping bit kamera. Ia bekerja mirip dengan topeng tabrakan . Objek akan dipotong (mis., Tidak dirender) jika hasil bitwise AND antara mask-nya dan mask kamera adalah 1.

Misalkan Air memiliki bit 2 dan WaterMask memiliki bit 3. Semua bit kecuali 3 harus ditetapkan untuk kamera nyata, dan semua bit kecuali 2 untuk kamera topeng. Cara termudah untuk mengatakan "semua bit kecuali N" adalah sebagai berikut cara:

 ~(1 << N) >>> 0 

Baca lebih lanjut tentang operasi bitwise di sini .

Untuk mengonfigurasi topeng kliping kamera, kita dapat memasukkan yang berikut di bagian bawah inisialisasi skrip CameraMask.js :

  //   ,  2 this.entity.camera.camera.cullingMask &= ~(1 << 2) >>> 0; //   ,  3 this.CameraToFollow.camera.camera.cullingMask &= ~(1 << 3) >>> 0; //      ,   : // console.log((this.CameraToFollow.camera.camera.cullingMask >>> 0).toString(2)); 

Sekarang di Water.js kita akan menetapkan bit 2 dari topeng jala air, dan dan versi topeng itu pada bit 3:

 //      initialize  Water.js //    var bit = this.isMask ? 3 : 2; meshInstance.mask = 0; meshInstance.mask |= (1 << bit); 

Sekarang satu spesies akan dengan air biasa, dan yang kedua dengan air putih solid. Gambar di sebelah kiri menunjukkan tampilan dari kamera asli, dan di sebelah kanan tampilan dari kamera topeng.


Aplikasi topeng


Dan sekarang langkah terakhir! Kita tahu bahwa area bawah laut ditandai dengan piksel putih. Kami hanya perlu memeriksa apakah kami memiliki piksel putih, dan jika tidak, matikan distorsi di Refraction.frag :

 //   ,      vec4 maskColor = texture2D(uMaskBuffer, pos); vec4 maskColor2 = texture2D(uMaskBuffer, vUv0); //     ? if(maskColor != vec4(1.0) || maskColor2 != vec4(1.0)){ //      pos = vUv0; } 

Dan ini harus menyelesaikan masalah kita!

Perlu juga dicatat bahwa karena tekstur untuk topeng diinisialisasi pada saat startup, ketika Anda mengubah ukuran jendela pada waktu berjalan, itu tidak akan lagi sesuai dengan ukuran layar.

Menghaluskan


Anda mungkin memperhatikan bahwa tepi adegan sekarang terlihat agak tajam. Ini terjadi karena setelah menerapkan efek pasca, kami kehilangan perataan.

Kita dapat menerapkan perataan tambahan di atas efek kita sebagai efek-pos lain. Untungnya, ada variabel lain di toko PlayCanvas yang bisa kita gunakan. Buka halaman aset skrip , klik tombol unduhan besar berwarna hijau, dan pilih proyek Anda dari daftar yang muncul. Skrip akan muncul di root jendela Aset sebagai posteffect-fxaa.js . Cukup pasangkan ke entitas Kamera dan adegan Anda akan mulai terlihat jauh lebih baik!

Pikiran dalam kesimpulan


Jika Anda sampai di sini, Anda bisa memuji diri sendiri! Dalam tutorial ini kami membahas beberapa teknik. Sekarang Anda harus merasa percaya diri ketika bekerja dengan vertex shader, rendering dalam tekstur, menerapkan efek post-processing, kliping objek selektif, menggunakan buffer kedalaman dan bekerja dengan blending dan transparansi. Meskipun kami telah menerapkan semua ini di PlayCanvas, Anda dapat memenuhi semua konsep umum grafis komputer ini dalam satu atau lain bentuk pada platform apa pun.

Semua teknik ini juga berlaku untuk banyak efek lainnya. Satu aplikasi yang sangat menarik ditemukan untuk vertex shaders, saya temukan dalam laporan pada grafik Abzu , di mana pengembang menjelaskan bagaimana mereka menggunakan vertex shaders untuk secara efektif menggerakkan puluhan ribu ikan di layar.

Sekarang Anda memiliki efek air yang indah yang dapat Anda terapkan dalam gim Anda! Anda dapat menyesuaikannya dan menambahkan detail Anda sendiri. Jauh lebih banyak yang dapat dilakukan dengan air (saya bahkan tidak menyebutkan jenis refleksi apa pun). Di bawah ini adalah beberapa ide.

Gelombang kebisingan


Alih-alih hanya menggerakkan gelombang dengan kombinasi cosinus dan sinus, Anda dapat mencicipi teksturnya sehingga ombak terlihat sedikit lebih alami dan kurang dapat diprediksi.

Jejak busa dinamis


Alih-alih garis air yang benar-benar statis di permukaan, Anda dapat menarik ke tekstur saat memindahkan objek untuk membuat jejak busa yang dinamis. Ini dapat dilakukan dengan berbagai cara, sehingga tugas ini sendiri dapat menjadi proyek.

Kode sumber


Proyek PlayCanvas yang sudah selesai dapat ditemukan di sini . Repositori kami juga memiliki port proyek di bawah Three.js .

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


All Articles