Unlimited WordPress themes, graphics, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Game Development
  2. Shaders
Gamedevelopment

Menciptakan Water Toon untuk Web: Bagian 2

by
Difficulty:AdvancedLength:LongLanguages:
This post is part of a series called Creating Toon Water for the Web.
Creating Toon Water for the Web: Part 1
Creating Toon Water for the Web: Part 3

Indonesian (Bahasa Indonesia) translation by Suci Rohini (you can also view the original English article)

Selamat datang kembali di bagian ke tiga dari seri ini pada pembuatan gaya water toon di PlayCanvas menggunakan vertex shaders. Di Part 1, kita membahas pengaturan lingkungan dan permukaan air. Bagian ini akan mencakup penerapan daya apung ke objek, menambahkan garis air ke permukaan, dan menciptakan garis busa dengan buffer kedalaman di sekitar tepi objek yang bersinggungan dengan permukaan.

Saya membuat beberapa perubahan kecil pada adegannya agar terlihat sedikit lebih bagus. Anda dapat menyesuaikan adegannya sesuka Anda, tetapi yang saya lakukan adalah:

  • Menambahkan mercusuar dan model gurita.
  • Menambahkan bidang tanah dengan warna #FFA457.
  • Menambahkan warna yang jelas untuk kamera #6CC8FF.
  • Menambahkan warna ambient ke adegan #FFC480 (Anda dapat menemukan ini di pengaturan adegan).

Di bawah ini adalah titik awal saya sekarang.

The scene now includes an octopus and a ligthouse

Daya Apung

Cara paling mudah untuk menciptakan daya apung hanyalah untuk membuat skrip yang akan mendorong objek ke atas dan ke bawah. Buat skrip baru bernama Buoyancy.js dan atur inisialasinya ke:

Sekarang, dalam pembaruan, kita menambah waktu dan memutar objek:

Terapkan skrip ini ke perahu Anda dan lihatlah itu naik-turun di air! Anda dapat menerapkan skrip ini ke beberapa objek (termasuk kamera — cobalah)!

Menekstur Permukaan

Saat ini, satu-satunya cara Anda dapat melihat ombak adalah dengan melihat ke tepi permukaan air. Menambahkan tekstur membantu membuat gerakan di permukaan lebih terlihat dan merupakan cara murah untuk mensimulasikan pantulan dan ketajaman.

Anda dapat mencoba untuk menemukan beberapa tekstur kaustik atau membuat tekstur Anda sendiri. Inilah yang saya gambar di Gimp yang bisa Anda gunakan dengan bebas. Tekstur apa pun akan berfungsi selama dapat disatukan dengan mulus.

Setelah Anda menemukan tekstur yang Anda sukai, seret ke dalam jendela aset proyek Anda. Kita perlu mereferensikan tekstur ini di skrip Water.js kita, jadi buat atribut untuknya:

Lalu taruh di editor:

The water texture is added to the water script

Sekarang kita harus menyebarkannya ke shader. Pergi ke Water.js dan tetapkan parameter baru dalam fungsi CreateWaterMaterial:

Sekarang masuk ke Water.frag dan nyatakan tampilan baru kita:

Kita hampir tiba. Untuk merender tekstur ke pesawat, kita perlu tahu di mana setiap piksel berada di sepanjang mesh. Yang berarti kita harus melewatkan beberapa data dari shader verteks ke shader fragmen.

Variabel yang bervariasi

Variabel yang bervariasi memungkinkan Anda untuk mengirimkan data dari shader verteks ke shader fragmen. Ini adalah jenis variabel khusus ketiga yang dapat Anda miliki dalam shader (dua lainnya adalah seragam dan atribut). Ini didefinisikan untuk setiap titik dan dapat diakses oleh setiap piksel. Karena terdapat lebih banyak piksel daripada simpul, nilainya diinterpolasi di antara simpul (ini adalah tempat nama "bervariasi" berasal — ini bervariasi dari nilai yang Anda berikan).

Untuk mencoba ini, deklarasikan variabel baru di Water.vert sebagai variasi:

Lalu setel ke gl_Position setelah dihitung:

Sekarang kembali ke Water.frag dan nyatakan variabel yang sama. Tidak ada cara untuk mendapatkan beberapa keluaran debug dari dalam shader, tetapi kita dapat menggunakan warna untuk debug visual. Inilah satu cara untuk melakukannya:

Pesawat seharusnya sekarang terlihat hitam dan putih, di mana garis yang memisahkannya adalah di mana ScreenPosition.x adalah 0. Nilai warna hanya pergi dari 0 ke 1, tetapi nilai-nilai di ScreenPosition dapat berada di luar rentang ini. Mereka secara otomatis dijepit, jadi jika Anda melihat hitam, itu bisa menjadi 0, atau negatif.

Apa yang baru saja kita lakukan adalah melewati posisi layar setiap titik ke setiap piksel. Anda dapat melihat bahwa garis yang memisahkan sisi hitam dan putih selalu berada di tengah layar, di mana pun permukaan sebenarnya berada di dunia.

Tantangan #1: Buat variabel baru yang bervariasi untuk melewati posisi dunia alih-alih posisi layar. Visualisasikan dengan cara yang sama seperti yang kami lakukan di atas. Jika warnanya tidak berubah dengan kamera, maka Anda telah melakukannya dengan benar.

Menggunakan UVs

UV adalah koordinat 2D untuk setiap titik di sepanjang mesh, dinormalisasi dari 0 ke 1. Ini adalah persis apa yang kita butuhkan untuk sampel tekstur ke pesawat dengan benar, dan itu seharusnya sudah diatur dari bagian sebelumnya.

Deklarasikan atribut baru di Water.vert (nama ini berasal dari definisi shader di Water.js):

Dan yang perlu kita lakukan adalah meneruskannya ke shader fragmen, jadi buatlah berbagai macam dan atur ke atribut:

Sekarang kita menyatakan variasi yang sama di shader fragmen. Untuk memverifikasi itu berfungsi, kita dapat memvisualisasikannya seperti sebelumnya, sehingga Water.frag sekarang terlihat seperti:

Dan Anda akan melihat gradien, pastikan bahwa kita memiliki nilai 0 di satu ujung dan 1 di ujung lainnya. Sekarang, untuk benar-benar menciptakan teksturnya, yang harus kita lakukan adalah:

Dan Anda harus melihat tekstur di permukaan:

Caustics texture is applied to the water surface

Menyempurnakan Tekstur

Alih-alih hanya mengatur tekstur sebagai warna baru, mari gabungkan dengan warna biru yang kita miliki:

Ini berfungsi karena warna teksturnya hitam (0) di mana-mana kecuali untuk garis air. Dengan menambahkannya, kita tidak mengubah warna biru asli kecuali untuk tempat-tempat di mana ada garis, di mana ia menjadi lebih cerah.

Ini bukan satu-satunya cara untuk menggabungkan warna.

Tantangan #2: Dapatkah Anda menggabungkan warna dengan cara mendapatkan efek subtler yang ditunjukkan di bawah ini?
Water lines applied to the surface with a more subtle color

Memindahkan Tekstur

Sebagai efek akhir, kita ingin garis-garis bergerak di sepanjang permukaan sehingga tidak terlihat begitu statis. Untuk melakukan ini, kita menggunakan fakta bahwa nilai apa pun yang diberikan kepada fungsi texture2D di luar rentang 0 hingga 1 akan membungkus (seperti 1,5 dan 2,5 keduanya menjadi 0,5). Jadi kita dapat menaikkan posisi kita dengan variabel seragam waktu yang telah kita atur dan melipatgandakan posisi untuk menambah atau mengurangi kepadatan garis di permukaan kita, membuat frag shader akhir kita terlihat seperti ini:

Jalur Busa & Penyangga Kedalaman

Rendering garis busa di sekitar benda-benda dalam air membuatnya jauh lebih mudah untuk melihat bagaimana benda-benda tenggelam dan di mana mereka memotong permukaan. Itu juga membuat air kita terlihat jauh lebih bisa dipercaya. Untuk melakukan ini, kita perlu mencari tahu di mana ujung-ujungnya berada pada setiap objek, dan melakukan ini secara efisien.

Trik-nya

Yang kita inginkan adalah bisa memberi tahu, mengingat sebuah pixel di permukaan air, apakah itu dekat dengan objek. Jika demikian, kita bisa mewarnainya sebagai busa. Tidak ada cara langsung untuk melakukan ini (yang saya tahu). Jadi untuk mencari tahu hal ini, kita akan menggunakan teknik pemecahan masalah yang bermanfaat: datang dengan sebuah contoh yang kita tahu jawabannya, dan lihat apakah kita dapat menggeneralisasikannya.

Pertimbangkan tampilan di bawah ini.

Lighthouse in water

Piksel mana yang harus menjadi bagian dari busa? Kita tahu seharusnya terlihat seperti ini:

Lighthouse in water with foam

Jadi mari kita berpikir tentang dua piksel spesifik. Saya telah menandai dua dengan bintang di bawah ini. Yang hitam dalam busa. Yang merah tidak. Bagaimana kita bisa membedakan mereka di dalam shader?

Lighthouse in water with two marked pixels

Apa yang kita ketahui adalah bahwa meskipun kedua piksel tersebut berdekatan di ruang layar (keduanya ditempatkan tepat di atas tubuh mercusuar), mereka sebenarnya jauh terpisah di ruang dunia. Kita dapat memverifikasi ini dengan melihat pemandangan yang sama dari sudut yang berbeda, seperti yang ditunjukkan di bawah ini.

Viewing the lighthouse from above

Perhatikan bahwa bintang merah tidak di atas tubuh mercusuar seperti yang muncul, tetapi bintang hitam sebenarnya. Kita dapat membedakan mereka menggunakan jarak ke kamera, biasanya disebut sebagai "kedalaman", di mana kedalaman 1 berarti sangat dekat dengan kamera dan kedalaman 0 berarti sangat jauh. Tapi itu bukan hanya soal jarak dunia, atau kedalaman, ke kamera. Kedalamannya dibandingkan dengan piksel di belakang.

Lihatlah kembali ke tampilan pertama. Katakanlah tubuh mercu suar memiliki nilai kedalaman 0,5. Kedalaman bintang hitam itu akan sangat dekat dengan 0,5. Jadi dan piksel di belakangnya memiliki nilai kedalaman yang serupa. Bintang merah, di sisi lain, akan memiliki kedalaman yang jauh lebih besar, karena akan lebih dekat ke kamera, katakanlah 0,7. Namun pixel di belakangnya, masih di mercusuar, memiliki nilai kedalaman 0,5, jadi ada perbedaan yang lebih besar di sana.

Ini triknya. Ketika kedalaman pixel di permukaan air cukup dekat dengan kedalaman pixel yang ditarik di atas, kita cukup dekat dengan tepi sesuatu, dan kita dapat menjadikannya sebagai busa.

Jadi kita membutuhkan lebih banyak informasi daripada yang tersedia di setiap piksel yang diberikan. Kita entah bagaimana harus mengetahui kedalaman piksel yang akan ditarik di atas. Di sinilah buffer kedalaman masuk.

Kedalaman Buffer

Anda dapat memikirkan buffer, atau framebuffer, sebagai target render di luar layar, atau tekstur. Anda ingin merender di luar layar saat Anda mencoba membaca data kembali, teknik yang digunakan oleh efek asap ini.

Penyangga kedalaman adalah target render khusus yang menyimpan informasi tentang nilai kedalaman pada setiap piksel. Ingat bahwa nilai dalam gl_Position yang dihitung dalam vertex shader adalah nilai ruang layar, tetapi juga memiliki koordinat ketiga, sebuah nilai Z. Nilai Z ini digunakan untuk menghitung kedalaman yang ditulis ke kedalaman buffer.

Tujuan dari buffer kedalaman adalah menggambar adegan kita dengan benar, tanpa perlu memilah objek kembali ke depan. Setiap piksel yang akan ditarik pertama kali berkonsultasi dengan buffer kedalaman. Jika nilai kedalamannya lebih besar dari nilai dalam buffer, itu ditarik, dan nilainya sendiri menimpa yang ada di buffer. Jika tidak, itu dibuang (karena itu berarti benda lain ada di depannya).

Anda benar-benar dapat mematikan penulisan ke kedalaman buffer untuk melihat bagaimana hal-hal akan terlihat tanpanya. Anda dapat mencoba ini di Water.js:

Anda akan melihat bagaimana air akan selalu ditampilkan di atas, meskipun berada di belakang objek yang buram.

Memvisualisasikan Penyangga Kedalaman

Mari tambahkan cara untuk memvisualisasikan buffer kedalaman untuk keperluan debugging. Buat skrip baru bernama DepthVisualize.js. Lampirkan ini ke kamera Anda.

Yang harus kita lakukan untuk mendapatkan akses ke buffer kedalaman di PlayCanvas adalah dengan mengatakan:

Ini kemudian akan secara otomatis menyuntikkan seragam ke semua shader kita yang dapat kita gunakan dengan menyatakannya sebagai:

Di bawah ini adalah contoh skrip yang meminta peta kedalaman dan menampilkannya di atas adegan kita. Ini disiapkan untuk reload panas.

Coba salin itu di, dan komentar/hapus komentar baris this.app.scene.drawCalls.push (this.command); untuk mengubah render kedalaman. Seharusnya terlihat seperti gambar di bawah ini.

Boat and lighthouse scene rendered as a depth map
Tantangan #3: Permukaan air tidak tertarik ke kedalaman buffer. Mesin PlayCanvas melakukan ini dengan sengaja. Bisakah kamu mencari tahu kenapa? Apa yang spesial dari bahan air? Untuk meletakkannya dengan cara lain, berdasarkan pada aturan pengecekan kedalaman kami, apa yang akan terjadi jika piksel air memang menulis ke kedalaman buffer?

Petunjuk: Ada satu baris yang dapat Anda ubah dalam Water.js yang akan menyebabkan air ditulis ke kedalaman buffer.

Hal lain yang perlu diperhatikan adalah bahwa saya mengalikan nilai kedalaman sebesar 30 pada shader tertanam dalam fungsi inisialisasi. Ini hanya untuk dapat melihatnya dengan jelas, karena jika tidak, kisaran nilai terlalu kecil untuk dilihat sebagai nuansa warna.

Menerapkan Trik

Mesin PlayCanvas mencakup sekelompok fungsi pembantu untuk bekerja dengan nilai-nilai mendalam, tetapi pada saat penulisan mereka tidak dirilis ke dalam produksi, jadi kita hanya akan mengaturnya sendiri.

Tentukan seragam berikut ke Water.frag:

Definisikan fungsi-fungsi pembantu ini di atas fungsi utama:

Berikan beberapa informasi tentang kamera ke shader di Water.js. Letakkan ini di mana Anda lulus seragam lain seperti uTime:

Akhirnya, kita membutuhkan posisi dunia untuk setiap piksel di shader frag. Kita perlu mendapatkan ini dari vertex shader. Jadi tentukan beragam di Water.frag:

Tentukan variasi yang sama di Water.vert. Kemudian atur ke posisi terdistorsi di vertex shader, sehingga kode lengkap akan terlihat seperti:

Sebenarnya Menerapkan Trik

Sekarang kita akhirnya siap untuk menerapkan teknik yang dijelaskan di awal bagian ini. Kita ingin membandingkan kedalaman piksel kita di kedalaman piksel di belakangnya. Pixel yang kita dapatkan berasal dari posisi dunia, dan pixel di belakang berasal dari posisi layar. Jadi ambil kedua kedalaman ini:

Tantangan #4: Salah satu nilai-nilai ini tidak pernah akan lebih besar daripada yang lain (dengan asumsi depthTest = true). Bisa Anda simpulkan yang?

Kita tahu busa akan menjadi tempat jarak antara kedua nilai ini kecil. Jadi mari kita membuat perbedaan itu di setiap piksel. Taruh ini di bagian bawah shader Anda (dan pastikan skrip visualisasi kedalaman dari bagian sebelumnya dimatikan):

Yang seharusnya terlihat seperti ini:

A rendering of the depth difference at each pixel

Yang benar mengambil tepi dari setiap objek yang tenggelam dalam air secara real time! Tentu saja Anda dapat mengukur perbedaan ini kita render untuk membuat busa terlihat lebih tebal/tipis.

Sekarang ada banyak cara di mana Anda dapat menggabungkan output ini dengan warna permukaan air untuk mendapatkan garis busa yang tampak bagus. Anda bisa menyimpannya sebagai gradien, menggunakannya untuk sampel dari tekstur lain, atau atur ke warna tertentu jika perbedaannya kurang dari atau sama dengan beberapa ambang.

Tampilan favorit saya adalah menyetelnya ke warna yang mirip dengan garis air statis, sehingga fungsi utama terakhir saya terlihat seperti ini:

Ringkasan

Kita menciptakan daya apung pada objek yang mengambang di air, kita memberi permukaan tekstur yang bergerak untuk mensimulasikan caustic, dan kita melihat bagaimana kita dapat menggunakan buffer kedalaman untuk membuat garis busa dinamis.

Untuk menyelesaikan ini, bagian berikutnya dan terakhir akan memperkenalkan efek pasca-proses dan bagaimana menggunakannya untuk menciptakan efek distorsi bawah air.

Kode Sumber

Anda dapat menemukan proyek PlayCanvas yang sudah jadi di sini. Port Three.js juga tersedia di repositori ini.

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.