Advertisement
  1. Game Development
  2. Shaders
Gamedevelopment

Menciptakan Air Toon untuk Web: Bagian 1

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 2

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

Dalam Panduan Beginners to Shaders saya memfokuskan secara eksklusif pada shader fragment, yang cukup untuk efek 2D dan setiap contoh ShaderToy. Tapi ada seluruh kategori teknik yang membutuhkan shader vertex. Tutorial ini akan memandu Anda melalui pembuatan air toon bergaya saat memperkenalkan vertex shaders. Saya juga akan memperkenalkan buffer kedalaman dan cara menggunakannya untuk mendapatkan informasi lebih lanjut tentang adegan Anda dan membuat garis busa.

Inilah efek akhir yang seharusnya terlihat. Anda dapat mencoba demo langsung di sini (mouse kiri ke orbit, mouse kanan ke panci, gulir roda untuk memperbesar).

Kayak and lighthouse in water

Secara khusus, efek ini terdiri dari:

  1. Jaring air yang tembus pandang dengan simpul yang terlantar untuk membuat gelombang.
  2. Garis air statis di permukaan.
  3. Daya apung palsu di perahu.
  4. Garis busa dinamis di sekitar tepi objek di dalam air.
  5. Sebuah distorsi pasca-proses segala sesuatu di bawah air.

Apa yang saya suka tentang efek ini adalah bahwa ia menyentuh banyak konsep yang berbeda dalam grafik komputer, sehingga akan memungkinkan kita untuk menarik ide-ide dari tutorial masa lalu, serta mengembangkan teknik yang dapat kita gunakan untuk berbagai efek masa depan.

Saya akan menggunakan PlayCanvas untuk ini hanya karena ia memiliki IDE berbasis web gratis yang nyaman, tetapi semuanya harus berlaku untuk lingkungan apa pun yang menjalankan WebGL. Anda dapat menemukan versi kode sumber Three.js di bagian akhir. Saya akan mengasumsikan Anda merasa nyaman menggunakan shader fragmen dan menavigasi antarmuka PlayCanvas. Anda dapat memoles shader di sini dan skim intro ke PlayCanvas di sini.

Pengaturan Lingkungan

Tujuan dari bagian ini adalah untuk mengatur proyek PlayCanvas kami dan menempatkan beberapa objek lingkungan untuk menguji air terhadap.

Jika Anda belum memiliki akun dengan PlayCanvas, daftarlah dan buat proyek kosong baru. Secara default, Anda harus memiliki beberapa objek, kamera, dan cahaya di scene Anda.

A blank PlayCanvas project showing the objects the scene contains

Memasukkan Model

Proyek Poly Google adalah sumber daya yang sangat bagus untuk model 3D untuk web. Ini model perahu yang saya gunakan. Setelah Anda mengunduh dan meng-unzip itu, Anda harus menemukan file .obj dan .png.

  1. Seret kedua file ke jendela aset di proyek PlayCanvas Anda.
  2. Pilih materi yang dibuat secara otomatis, dan atur peta penyebarannya ke file .png.
Click on the diffuse tab and select the boat image

Sekarang Anda dapat menyeret Tugboat.json ke dalam adegan Anda dan menghapus objek Kotak dan Pesawat. Anda dapat menskalakan perahu jika terlihat terlalu kecil (saya menetapkan tambang menjadi 50).

You can scale the model up using the properties panel on the right once its selected

Anda dapat menambahkan model lain ke adegan Anda dengan cara yang sama.

Orbit Camera

Untuk mengatur kamera orbit, kita akan menyalin skrip dari contoh PlayCanvas ini. Pergi ke tautan itu, dan klik Editor untuk memasuki proyek.

  1. Salin isi mouse-input.js dan orbit-kamera.js dari proyek tutorial itu ke dalam file dengan nama yang sama di proyek Anda sendiri.
  2. Tambahkan komponen Script ke kamera Anda.
  3. Pasang kedua skrip ke kamera.

Tip: Anda dapat membuat folder di jendela aset untuk menjaga semuanya tetap teratur. Saya menempatkan dua skrip kamera ini di bawah Skrip / Kamera /, model saya di bawah Model /, dan materi saya di bawah Materi /.

Sekarang, ketika Anda meluncurkan permainan (tombol putar di kanan atas tampilan adegan), Anda harus dapat melihat perahu Anda dan mengitarinya dengan mouse.

Permukaan Air Terbagi

Tujuan dari bagian ini adalah untuk menghasilkan mesh yang terbagi untuk digunakan sebagai permukaan air kita.

Untuk menghasilkan permukaan air, kita akan menyesuaikan beberapa kode dari tutorial generasi terrain. Buat file skrip baru bernama Water.js. Edit skrip ini dan buat fungsi baru bernama GeneratePlaneMesh yang terlihat seperti ini:

Sekarang Anda dapat memanggil ini di fungsi inisialisasi:

Anda seharusnya hanya melihat pesawat datar saat Anda meluncurkan gim ini sekarang. Tapi ini bukan hanya pesawat datar. Ini adalah jaring yang terdiri dari seribu simpul. Sebagai tantangan, coba verifikasi ini (ini adalah alasan yang bagus untuk membaca kode yang baru saja Anda salin).

Tantangan #1: Menonaktifkan koordinat Y dari setiap titik dengan jumlah acak untuk membuat pesawat terlihat seperti gambar di bawah.
A subdivided plane with displaced vertices

Gelombang

Tujuan dari bagian ini adalah untuk memberi permukaan air bahan khusus dan membuat gelombang animasi.

Untuk mendapatkan efek yang kami inginkan, kami perlu menyiapkan bahan khusus. Sebagian besar mesin 3D akan memiliki beberapa shader yang telah ditentukan sebelumnya untuk merender objek dan cara menimpanya. Berikut referensi bagus untuk melakukan ini di PlayCanvas.

Melampirkan Shader

Mari kita membuat fungsi baru yang disebut CreateWaterMaterial yang mendefinisikan materi baru dengan shader khusus dan mengembalikannya:

Fungsi ini mengambil kode shader titik dan fragmen dari atribut skrip. Jadi mari kita mendefinisikan mereka di bagian atas file (setelah baris pc.createScript):

Sekarang kita bisa membuat file shader ini dan melampirkannya ke skrip kita. Kembali ke editor, dan buat dua file shader baru: Water.frag dan Water.vert. Lampirkan shader ini ke skrip Anda seperti yang ditunjukkan di bawah ini.

Watervert and Waterfrag are attached to WaterInit

Jika atribut baru tidak muncul di editor, klik tombol Parse untuk menyegarkan skrip.

Sekarang tempatkan shader dasar ini di Water.frag:

Dan ini di Water.vert:

Akhirnya, kembalilah ke Water.js dan gunakan materi kustom baru kami sebagai ganti bahan standar. Jadi, bukannya:

Lakukan:

Sekarang, jika Anda meluncurkan game, pesawat seharusnya berwarna biru.

The shader we wrote renders the plane as blue

Reload Panas

Sejauh ini, kita baru saja menyiapkan beberapa dummy shader pada materi baru kita. Sebelum kita menulis efek yang sebenarnya, satu hal terakhir yang ingin saya atur adalah pemuatan kode otomatis.

Tidak mengubah fungsi swap dalam file skrip apa pun (seperti Water.is) memungkinkan hot-reload. Kita akan melihat cara menggunakan ini nanti untuk mempertahankan keadaan bahkan saat kita memperbarui kode secara real time. Namun untuk saat ini kita hanya ingin menerapkan kembali shader setelah kita mendeteksi perubahan. Shaders dikompilasi sebelum dijalankan di WebGL, jadi kita harus membuat ulang materi kustom untuk memicu ini.

Kita akan memeriksa apakah isi kode shader kita telah diperbarui dan, jika demikian, buat ulang materi. Pertama, simpan shader saat ini di inisialisasi:

Dan dalam pembaruan, periksa apakah ada perubahan:

Sekarang, untuk memastikan ini berfungsi, luncurkan game dan ubah warna pesawat di Water.frag menjadi biru yang lebih berselera. Setelah Anda menyimpan file, itu harus memperbarui tanpa harus me-refresh atau meluncurkan kembali! Ini adalah warna yang saya pilih:

Vertex Shaders

Untuk membuat gelombang, kita perlu memindahkan setiap titik di setiap frame kita. Ini terdengar seolah-olah itu akan sangat tidak efisien, tetapi setiap titik dari setiap model sudah berubah pada setiap frame yang kita buat. Inilah yang dilakukan oleh vertex shader.

Jika Anda memikirkan shader fragmen sebagai fungsi yang berjalan pada setiap piksel, mengambil posisi, dan mengembalikan warna, maka shader vertex adalah fungsi yang berjalan pada setiap titik, mengambil posisi, dan mengembalikan posisi.

Vertex shader default akan mengambil posisi dunia dari model yang diberikan, dan mengembalikan posisi layar. Adegan 3D kita didefinisikan dalam hal x, y, dan z, tetapi monitor Anda adalah bidang datar dua dimensi, jadi kita memproyeksikan dunia 3D kita ke layar 2D kita. Proyeksi ini adalah apa yang dilihat oleh matriks pandangan, proyeksi, dan model dan berada di luar cakupan tutorial ini, tetapi jika Anda ingin mempelajari apa yang terjadi pada langkah ini, inilah panduan yang sangat bagus.

Jadi baris ini:

Membawa aPosisi sebagai posisi dunia 3D dari vertex tertentu dan mengubahnya menjadi gl_Position, yang merupakan posisi layar 2D akhir. Awalan 'a' pada aPosition adalah untuk menandakan bahwa nilai ini adalah atribut. Ingat bahwa variabel seragam adalah nilai yang dapat kita definisikan pada CPU untuk diteruskan ke shader yang mempertahankan nilai yang sama di semua piksel / simpul. Nilai atribut, di sisi lain, berasal dari array yang didefinisikan pada CPU. Titik vertikal shader disebut sekali untuk setiap nilai dalam susunan atribut tersebut.

Anda dapat melihat atribut ini ditetapkan dalam definisi shader yang kami tetapkan di Water.js:

PlayCanvas mengatur pengaturan dan mengirimkan array posisi vertex untuk aPosition ketika kita melewati enum ini, tetapi secara umum Anda bisa mengirimkan array data ke vertex shader.

Memindahkan Vertices

Katakanlah Anda ingin memangkas bidang dengan mengalikan semua nilai x hingga setengahnya. Haruskah Anda mengubah aPosisi atau gl_Position?

Ayo coba aPosisi dulu. Kita tidak dapat memodifikasi atribut secara langsung, tetapi kita dapat membuat salinan:

Pesawat seharusnya sekarang terlihat lebih persegi panjang. Tidak ada yang aneh di sana. Sekarang apa yang terjadi jika kita mencoba memodifikasi gl_Position?

Mungkin terlihat sama sampai Anda mulai memutar kamera. Kami memodifikasi koordinat ruang layar, yang berarti itu akan terlihat berbeda tergantung pada bagaimana Anda melihatnya.

Jadi itulah bagaimana Anda dapat memindahkan simpul, dan penting untuk membuat perbedaan antara apakah Anda berada di dunia atau ruang layar.

Tantangan #2: Dapatkah Anda memindahkan seluruh permukaan pesawat beberapa unit ke atas (sepanjang sumbu Y) di vertex shader tanpa mendistorsikan bentuknya?
Tantangan # 3: Saya mengatakan gl_Position adalah 2D, tetapi gl_Position.z memang ada. Dapatkah Anda menjalankan beberapa tes untuk menentukan apakah nilai ini memengaruhi apa pun, dan jika demikian, untuk apa digunakan?

Menambahkan waktu

Satu hal yang kita butuhkan sebelum kita dapat membuat gelombang bergerak adalah variabel seragam untuk digunakan sebagai waktu. Deklarasikan sebuah seragam dalam shader verteks Anda:

Kemudian, untuk meneruskan ini ke shader kami, kembali ke Water.js dan tentukan variabel waktu di inisialisasi:

Sekarang, untuk meneruskan ini ke shader kami, kami menggunakan material.setParameter. Pertama kami menetapkan nilai awal pada akhir fungsi CreateWaterMaterial:

Sekarang dalam fungsi pembaruan kita dapat menambah waktu dan mengakses materi menggunakan referensi yang kami buat untuk itu:

Sebagai langkah terakhir, dalam fungsi swap, salin nilai lama waktu, sehingga bahkan jika Anda mengubah kode itu akan terus bertambah tanpa ulang ke 0.

Sekarang semuanya sudah siap. Luncurkan game untuk memastikan tidak ada kesalahan. Sekarang mari kita pindahkan pesawat kita berdasarkan fungsi waktu di Water.vert:

Dan pesawat Anda harus bergerak naik dan turun sekarang! Karena kami memiliki fungsi swap sekarang, Anda juga dapat memperbarui Water.js tanpa harus meluncurkan kembali. Cobalah membuat kenaikan waktu lebih cepat atau lebih lambat untuk memastikan ini berhasil.

Moving the plane up and down with a vertex shader
Tantangan # 4: Bisakah Anda memindahkan simpul sehingga terlihat seperti gelombang di bawah ini?

Sebagai petunjuk, saya berbicara secara mendalam tentang berbagai cara untuk menciptakan ombak di sini. Itu dalam 2D, tetapi matematika yang sama berlaku di sini. Jika Anda lebih suka hanya mengintip solusi, inilah intinya.

Translucency

Tujuan dari bagian ini adalah untuk membuat permukaan air tembus cahaya.

Anda mungkin telah memperhatikan bahwa warna yang kita kembalikan di Water.frag memang memiliki nilai alpha 0,5, tetapi permukaannya masih benar-benar buram. Transparansi dalam banyak hal masih merupakan masalah terbuka dalam grafik komputer. Salah satu cara murah untuk mencapainya adalah dengan menggunakan blending.

Biasanya, ketika sebuah piksel akan diambil, ia akan memeriksa nilai dalam buffer kedalaman terhadap nilai kedalamannya sendiri (posisinya di sepanjang sumbu Z) untuk menentukan apakah akan menimpa piksel saat ini di layar atau membuangnya sendiri. Inilah yang memungkinkan Anda untuk membuat adegan dengan benar tanpa harus mengurutkan objek kembali ke depan.

Dengan pencampuran, bukan hanya membuang atau menimpa, kita dapat menggabungkan warna piksel yang sudah ditarik (tujuan) dengan piksel yang akan ditarik (sumber). Anda dapat melihat semua fungsi pencampuran yang tersedia di WebGL di sini.

Untuk membuat alfa bekerja dengan cara yang kita harapkan, kita ingin warna gabungan hasil menjadi sumber dikalikan dengan alfa plus tujuan dikalikan dengan satu dikurangi alfa. Dengan kata lain, jika alfa adalah 0,4, warna terakhir seharusnya:

Di PlayCanvas, opsi pc.BLEND_NORMAL melakukan hal ini.

Untuk mengaktifkan ini, cukup setel properti pada materi di dalam CreateWaterMaterial:

Jika Anda meluncurkan game sekarang, air akan tembus cahaya! Ini tidak sempurna. Masalah muncul jika permukaan tembus tumpang tindih dengan dirinya sendiri, seperti yang ditunjukkan di bawah ini.

Artifacts arise when a translucent surface overlaps with itself

Kami dapat memperbaikinya dengan menggunakan alpha ke cakupan, yang merupakan teknik multi-sampling untuk mencapai transparansi, bukan pencampuran:

Tapi ini hanya tersedia di WebGL 2. Untuk sisa tutorial ini, saya akan menggunakan blending untuk membuatnya tetap sederhana.

Ringkasan

Sejauh ini kita telah mengatur lingkungan kita dan menciptakan permukaan air kita yang tembus cahaya dengan gelombang animasi dari vertex shader kita. Bagian kedua akan mencakup penerapan daya apung pada objek, menambahkan garis air ke permukaan, dan menciptakan garis busa di sekitar tepi objek yang bersinggungan dengan permukaan.

Bagian terakhir akan mencakup penerapan efek distorsi pasca proses bawah air dan beberapa ide untuk ke mana harus pergi berikutnya.

Kode sumber

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

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.