Efek doodle shader

Dalam tutorial ini, saya akan berbicara tentang cara membuat ulang efek doodle sprite populer di Unity menggunakan shader. Jika gaya Anda memerlukan gaya ini, maka dari artikel ini Anda akan belajar cara mencapainya tanpa membuat banyak gambar tambahan.

Selama beberapa tahun terakhir, gaya ini telah menjadi semakin populer dan secara aktif digunakan dalam permainan seperti GoNNER dan Baba adalah Anda .


Tutorial ini mencakup semua yang Anda butuhkan, dari dasar-dasar pengkodean shader hingga matematika yang digunakan. Di akhir artikel ada tautan untuk mengunduh paket Unity lengkap.

Keberhasilan Doodle Studio 95 menginspirasi saya untuk membuat tutorial ini ! .

Pendahuluan


Di blog saya, saya mengeksplorasi topik yang cukup kompleks, dari matematika kinematika terbalik hingga hamburan Rayleigh atmosfer . Saya benar-benar ingin membuat topik yang sulit dimengerti oleh khalayak luas. Tetapi jumlah orang yang tertarik pada mereka dan memiliki tingkat teknis yang memadai tidak begitu besar. Karena itu, Anda tidak perlu heran bahwa artikel paling populer adalah yang paling sederhana. Ini juga berlaku untuk tweet Nick Caman baru-baru ini di mana ia menunjukkan cara membuat efek doodle di Unity.


Setelah 1000 suka dan 4000 retweet, menjadi jelas bahwa ada permintaan kuat untuk tutorial sederhana yang dapat dipelajari bahkan oleh orang-orang yang hampir tidak memiliki pengetahuan membuat shader.

Jika Anda mencari cara profesional dan efektif untuk menghidupkan sprite 2D dengan kontrol artistik tingkat tinggi, maka saya sangat merekomendasikan Doodle Studio 95! (lihat GIF di bawah). Di sini Anda dapat melihat beberapa game yang menggunakan alat ini.


Anatomi Efek Doodle


Untuk membuat ulang efek doodle, pertama-tama kita perlu memahami cara kerjanya dan teknik apa yang digunakan di dalamnya.

Efek shader. Pertama, kami ingin efek ini sesederhana mungkin dan tidak memerlukan skrip tambahan. Ini dimungkinkan berkat penggunaan shader yang memberi tahu Unity cara merender model 3D (termasuk yang datar!) Di layar. Jika Anda tidak terbiasa dengan dunia pengkodean shader , maka Anda harus mempelajari artikel saya Pengantar Lembut untuk Shader .

Sprite shader. Unity hadir dengan banyak jenis shader. Jika Anda menggunakan alat Unity 2D yang disediakan, maka kemungkinan besar Anda bekerja dengan sprite . Jika demikian, maka Anda memerlukan Sprite Shader - jenis khusus shader yang kompatibel dengan SpriteRenderer Unity. Atau Anda dapat mulai dengan shader Unlit yang lebih tradisional.

Offset verteks. Saat menggambar sprite secara manual, tidak ada bingkai yang sepenuhnya bertepatan dengan yang lainnya. Kami ingin membuat sprite "goyah" dalam beberapa cara untuk mensimulasikan efek ini. Shader memiliki cara yang sangat efektif untuk melakukan ini, menggunakan vertex offset . Ini adalah teknik yang memungkinkan Anda untuk mengubah posisi simpul dari objek 3D. Jika kita memindahkannya secara acak, kita mendapatkan efek yang diinginkan.

Waktu perjalanan Animasi tangan bebas biasanya memiliki frame rate yang rendah. Jika kita ingin mensimulasikan, katakanlah, lima frame per detik, maka kita perlu mengubah posisi simpul sprite lima kali per detik. Namun, Unity cenderung menjalankan game dengan kecepatan refresh yang jauh lebih tinggi; mungkin dengan 30 atau bahkan 60 frame per detik. Agar sprite tidak berubah 60 kali per detik, Anda perlu mengerjakan komponen pewaktu animasi.

Langkah 1: menyelesaikan sprite shader


Jika Anda ingin membuat shader baru di Unity, maka pilihannya akan sangat terbatas. Shader terdekat yang dapat kita mulai adalah Unlit Shader , meskipun belum tentu paling cocok untuk tujuan kita.

Jika Anda ingin doodle shader sepenuhnya kompatibel dengan SpriteRenderer Unity, maka kita perlu melengkapi Sprite Shader yang ada . Sayangnya, kami tidak dapat mengaksesnya langsung dari Unity itu sendiri.

Anda dapat melakukannya dengan membuka halaman arsip unduhan Unity dan mengunduh paket Build in shaders untuk versi Unity yang sedang Anda tangani. Ini adalah file zip yang berisi kode sumber dari semua shader yang datang bersama build Unity Anda.


Setelah mengunduh, unzip dan temukan file Sprites-Diffuse.shader di builtin_shaders-2018.1.6f1\DefaultResourcesExtra Sprites-Diffuse.shader . Ini adalah file yang akan kita gunakan dalam tutorial.


Sprite-Diffuse bukan sprite shader standar!
Saat membuat sprite baru, materi standarnya menggunakan shader yang disebut Sprites-Default.shader , bukan Sprites-Diffuse.shader .

Perbedaan antara keduanya adalah bahwa yang pertama tidak menggunakan pencahayaan, dan yang kedua merespons pencahayaan di tempat kejadian. Karena sifat implementasi Unity, versi difus jauh lebih mudah untuk diedit daripada versi tanpa pencahayaan.

Di akhir tutorial ini ada tautan untuk mengunduh doodle shader dengan dan tanpa penerangan.

Langkah 2: offset simpul


Di dalam Sprites-Diffuse.shader ada fungsi yang disebut vert , yang merupakan fungsi simpul yang kita bicarakan di atas. Namanya tidak penting, hal utama adalah bahwa itu bertepatan dengan yang ditentukan di bagian vertex: dari #pragma :

 #pragma surface surf Lambert vertex:vert nofog nolightmap nodynlightmap keepalpha noinstancing 

Singkatnya, fungsi simpul dipanggil untuk setiap simpul model 3D dan memutuskan bagaimana menempatkannya pada ruang layar dua dimensi. Dalam tutorial ini, kami hanya tertarik pada cara memindahkan objek.

Parameter appdata_full v berisi bidang vertex , yang berisi posisi 3D dari setiap titik dalam ruang objek . Saat nilainya berubah, verteks bergeser. Misalnya, kode yang ditunjukkan di bawah ini akan mentransfer objek dengan shadernya satu unit di sepanjang sumbu X.

 void vert (inout appdata_full v, out Input o) { v.vertex = UnityFlipSprite(v.vertex, _Flip); v.vertex.x += 1; #if defined(PIXELSNAP_ON) v.vertex = UnityPixelSnap (v.vertex); #endif UNITY_INITIALIZE_OUTPUT(Input, o); o.color = v.color * _Color * _RendererColor; } 

Secara default, game 2D yang dibuat di Unity hanya beroperasi dengan sumbu X dan Y, jadi kita perlu mengubah v.vertex.xy untuk memindahkan sprite pada bidang dua dimensi.

Apa itu ruang objek?
Bidang vertex dari struktur appdata_full berisi posisi titik saat ini diproses oleh shader di ruang objek. Ini adalah posisi simpul dengan asumsi bahwa objek tersebut terletak di pusat dunia (0,0,0), tanpa mengubah skala dan tanpa rotasi.

Puncak yang diungkapkan dalam ruang dunia mencerminkan posisi mereka yang sebenarnya dalam kancah Persatuan.

Mengapa objek tidak bergerak dengan kecepatan satu meter per frame?
Jika kita menambahkan +1 ke komponen x dari nilai transform.position dalam metode Update skrip C #, kita akan melihat bagaimana objek terbang ke kanan dengan kecepatan 1 meter per bingkai, yaitu sekitar 216 kilometer per jam.

Ini karena perubahan yang dilakukan C # mengubah posisi itu sendiri. Dalam fungsi vertex, ini tidak terjadi. Shader hanya mengubah representasi visual dari model, tetapi tidak memperbarui atau memodifikasi simpul yang tersimpan dari model. Itulah mengapa menambahkan +1 ke v.vertex.x menggeser objek hanya satu meter saja.

Ingatlah untuk mengimpor sprite sebagai Ketat!
Efek ini menggeser bagian atas sprite. Secara tradisional, sprite diimpor ke Unity sebagai quadrangles (lihat gambar di sebelah kiri). Ini berarti mereka hanya memiliki empat puncak. Jika demikian, maka hanya titik-titik ini yang dapat dipindahkan, yang mengurangi kekuatan efek orat-oret.

Untuk distorsi yang lebih kuat dan lebih realistis, Anda perlu mengimpor sprite dengan memilih Tight untuk parameter Mesh Type , yang mengubahnya menjadi bentuk cembung (lihat gambar di sebelah kanan).


Ini meningkatkan jumlah simpul. Ini tidak selalu diinginkan, tetapi itulah yang kita butuhkan sekarang.

Offset acak


Efek doodle secara acak menggeser posisi setiap dhuwur. Mengambil sampel angka acak dalam shader selalu menjadi tugas yang sulit. Ini terutama disebabkan oleh arsitektur GPU terdistribusi, yang merumitkan dan mengurangi efisiensi pembuatan algoritma yang digunakan di sebagian besar perpustakaan (termasuk Mathf.Random ).

Posting Nick Caman menggunakan tekstur suara yang, ketika disampel, memberikan ilusi keacakan. Dalam konteks proyek Anda, ini mungkin bukan pendekatan yang paling efektif, karena dalam hal ini jumlah operasi pencarian tekstur yang dilakukan oleh shader berlipat ganda.

Oleh karena itu, di sebagian besar bayangan, fungsi yang agak membingungkan dan kacau digunakan, yang, terlepas dari determinisme mereka, bagi kita tampaknya tidak memiliki keteraturan. Dan karena mereka harus didistribusikan, setiap angka acak harus dihasilkan dengan benihnya sendiri. Ini bagus untuk kita, karena posisi setiap titik harus unik. Kita dapat menggunakan ini untuk mengikat nomor acak ke setiap titik. Kami akan membahas implementasi fungsi acak ini nanti; sebut saja random3 .

Kita dapat menggunakan random3 untuk menghasilkan offset acak dari setiap titik. Dalam contoh di bawah ini, angka acak diskalakan menggunakan properti _NoiseScale , yang memungkinkan Anda untuk mengontrol kekuatan perpindahan.

 void vert (inout appdata_full v, out Input o) { ... float2 noise = random3(v.vertex.xyz).xy * _NoiseScale; v.vertex.xy += noise; ... } 

Sekarang kita perlu menulis kode random3 .

gambar

Keacakan dalam shader


Salah satu fungsi pseudorandom ikonik yang paling umum dan digunakan dalam shader diambil dari artikel 1998 oleh W. Ray dengan judul " Pada menghasilkan angka acak, dengan bantuan y = [(a + x) sin (bx)] mod 1 ".

 float rand(float2 co) { return fract(sin(dot(co.xy ,float2(12.9898,78.233))) * 43758.5453); } 

Fungsi ini bersifat deterministik (artinya, itu tidak benar - benar acak), tetapi berperilaku begitu acak sehingga terlihat benar-benar acak. Fungsi seperti ini disebut pseudo-random . Untuk tutorial saya, saya memilih fungsi yang lebih kompleks, yang ditemukan oleh Nikita Miropolsky.

Menghasilkan angka acak semu dalam shader adalah topik yang sangat kompleks. Jika Anda tertarik untuk mempelajari lebih lanjut tentang dia, maka The Book of Shaders memiliki bab yang bagus tentangnya. Selain itu, Patricio Gonzales Vivo telah membangun repositori besar fungsi pseudo-acak yang disebut noise GLSL , yang dapat digunakan dalam shader.

Langkah 3: tambahkan waktu


Berkat kode yang kami tulis, setiap titik di setiap bingkai digeser dengan jumlah yang sama. Jadi kami mendapatkan sprite terdistorsi, bukan efek doodle. Untuk memperbaikinya, Anda perlu menemukan cara untuk mengubah efek dari waktu ke waktu. Salah satu cara termudah untuk melakukan ini adalah dengan menggunakan posisi titik dan waktu saat ini untuk menghasilkan angka acak.

Dalam kasus kami, saya baru saja menambahkan waktu saat ini dalam detik _Time.y ke posisi titik.

 float time = float3(_Time.y, 0, 0); float2 noise = random3(v.vertex.xyz + time).xy * _NoiseScale; v.vertex.xy += noise; 

Efek yang lebih kompleks mungkin memerlukan cara yang lebih canggih untuk menambah waktu pada persamaan. Tetapi karena kita hanya tertarik pada efek acak intermiten, maka dua nilai lebih dari cukup.


Pergantian waktu


Masalah utama dengan menambahkan _Time.y adalah itu menyebabkan sprite dianimasikan di setiap frame. Ini tidak diinginkan bagi kami, karena sebagian besar animasi yang digambar tangan memiliki frame rate yang rendah. Komponen waktu tidak boleh kontinu, tetapi diskrit. Ini berarti bahwa jika kita ingin menampilkan lima frame per detik, maka itu harus berubah hanya lima kali per detik. Artinya, waktu harus dikaitkan dengan seperlima detik. Satu-satunya nilai yang valid seharusnya  frac05=0,  frac15=0,2,  frac25=0,4,  frac35=0,6,  frac45=0,8,  frac55=1dengan, dan seterusnya ...

Saya sudah membahas snap pada blog saya di How To Snap To Grid . Pada artikel ini, saya mengusulkan solusi untuk masalah pengikatan posisi suatu objek pada grid spasial. Jika kita perlu mengikat waktu ke kotak waktu, maka matematika, dan karena itu kodenya, akan sama.

Fungsi yang ditunjukkan di bawah ini mengambil angka x dan mengikatnya ke nilai integer yang merupakan kelipatan dari snap .

 inline float snap (float x, float snap) { return snap * round(x / snap); } 

Artinya, kode kita menjadi seperti ini:

 float time = snap(_Time.y, _NoiseSnap); float2 noise = random3(v.vertex.xyz + float3(time, 0.0, 0.0) ).xy * _NoiseScale; v.vertex.xy += noise; 


Kesimpulan


Paket Unity untuk efek ini dapat diunduh secara gratis di Patreon .

Sumber Daya Tambahan


Selama beberapa bulan terakhir, sejumlah besar permainan dalam gaya corat-coret telah muncul. Menurut saya, alasannya adalah kesuksesan Doodle Studio 95! - Alat untuk Persatuan, yang dikembangkan oleh Fernando Ramallo . Jika gaya ini cocok untuk gim Anda, maka saya sarankan untuk membeli alat yang luar biasa ini.

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


All Articles