Students Save 30%! Learn & create with unlimited courses & creative assets Students Save 30%! Save Now
Advertisement
  1. Game Development
  2. Shaders
Gamedevelopment

Cara Menulis Shader Berasap

Malay (Melayu) translation by Seurion (you can also view the original English article)

Selalunya ada misteri di sekitar asap. Ia adalah estetika yang menyenangkan untuk ditonton dan sukar difahami untuk model. Seperti banyak fenomena fizikal, ia adalah sistem yang huru-hara, yang menjadikannya sangat sukar untuk diramal. Keadaan simulasi sangat bergantung pada interaksi antara zarah masing-masing.

Inilah yang menjadikannya satu masalah besar untuk mengatasi GPU: ia boleh dipecahkan kepada perilaku satu zarah, berulang-ulang berulang kali di lokasi yang berbeza.

Dalam tutorial ini, saya akan memandu anda melalui menulis shader asap dari awal, dan mengajar anda beberapa teknik shader yang berguna supaya anda dapat mengembangkan senjata anda dan mengembangkan kesan anda sendiri!

Apa yang Anda Akan Belajar

Ini adalah hasil akhir yang akan kami jalankan ke arah:

Klik untuk menjana lebih banyak asap. Anda boleh menanggalkan dan mengeditnya di CodePen.

Kami akan melaksanakan algoritma yang dibentangkan dalam karya Jon Stam mengenai Dinamik Fluida Dinamik dalam Masa Permainan. Anda juga akan belajar cara membuat tekstur, juga dikenali sebagai menggunakan buffer bingkai, yang merupakan teknik yang sangat berguna dalam pengaturcaraan shader untuk mencapai banyak kesan.

Sebelum Anda Bermula

Contoh-contoh dan butiran pelaksanaan spesifik dalam tutorial ini menggunakan JavaScript dan ThreeJS, tetapi anda harus dapat mengikuti di mana-mana platform yang menyokong shader. (Jika anda tidak biasa dengan asas-asas pengaturcara shader, pastikan anda melalui sekurang-kurangnya dua tutorial pertama dalam siri ini.)

Semua contoh kod dihoskan pada CodePen, tetapi anda juga boleh mencarinya dalam repositori GitHub yang berkaitan dengan artikel ini (yang mungkin lebih mudah dibaca).

Teori dan Latar Belakang

Algoritma dalam kertas Jos Stam nikmat kelajuan dan kualiti visual ke atas ketepatan fizikal, iaitu apa yang kita mahu dalam tetapan permainan.

Kertas ini boleh kelihatan lebih rumit daripada yang sebenarnya, terutamanya jika anda tidak mahir dalam persamaan pembezaan. Walau bagaimanapun, inti keseluruhan teknik ini disimpulkan dalam angka ini:

Figure taken from Jos Stams paper cited above

Ini semua yang kita perlukan untuk mendapatkan kesan asap yang realistik: nilai dalam setiap sel menghilangkan kepada semua sel jirannya pada setiap lelaran. Jika ia tidak segera menjelaskan bagaimana ini berfungsi, atau jika anda hanya mahu melihat bagaimana ini akan kelihatan, anda boleh mengintai dengan demo interaktif ini:

Smoke shader algorithm interactive demo
Lihat demo interaktif di CodePen.

Mengklik mana-mana sel menetapkan nilainya kepada 100. Anda dapat melihat bagaimana setiap sel perlahan kehilangan nilainya kepada jiran-jirannya dari masa ke masa. Ia mungkin mudah dilihat dengan mengklik Seterusnya untuk melihat bingkai individu. Tukar Mod Paparan untuk melihat bagaimana ia kelihatan jika kami membuat nilai warna sesuai dengan nombor ini.

Demo di atas semuanya berjalan pada CPU dengan gelung yang melewati setiap sel. Inilah yang kelihatan seperti gelung itu:

Coretan ini benar-benar teras algoritma. Setiap sel mendapat sedikit daripada empat jiran tetangganya, tolak nilai sendiri, di mana f adalah faktor yang kurang daripada 1. Kami membiak nilai sel semasa sebanyak 4 untuk memastikan ia berbeza dari nilai yang lebih tinggi kepada nilai yang lebih rendah.

Untuk menjelaskan perkara ini, pertimbangkan senario ini:

Grid of values to diffuse

Ambil sel di tengah (di kedudukan [1,1] dalam grid) dan gunakan persamaan penyebaran di atas. Mari kita anggap f ialah 0.1:

Tiada penyebaran berlaku kerana semua sel mempunyai nilai yang sama!

Sekiranya kita menganggap sel di kiri atas sebaliknya (anggap sel-sel di luar grid digambarkan adalah 0):

Jadi kita mendapat peningkatan bersih sebanyak 20! Mari kita pertimbangkan kes terakhir. Selepas satu masa (memohon formula ini kepada semua sel), grid kami akan kelihatan seperti ini:

Grid of diffused values

Mari lihat pada meresap di sel di tengah sekali lagi:

Kami mendapat penurunan sebanyak 12! Jadi ia sentiasa mengalir dari nilai yang lebih tinggi kepada yang lebih rendah.

Sekarang, jika kita mahu ini kelihatan lebih realistik, kita boleh mengurangkan saiz sel (yang boleh anda lakukan dalam demo), tetapi pada satu ketika, perkara akan menjadi sangat perlahan, kerana kita terpaksa menjalankan secara berurutan melalui setiap sel. Matlamat kami adalah untuk dapat menulis ini dalam shader, di mana kita boleh menggunakan kuasa GPU untuk memproses semua sel (sebagai piksel) pada masa yang sama secara bersamaan.

Jadi, untuk meringkaskan, teknik umum kami adalah untuk memberikan setiap piksel memberikan beberapa nilai warna, setiap bingkai, kepada piksel tetangga. Bunyi agak mudah, bukan? Mari kita laksanakan itu dan lihat apa yang kita dapat!

Pelaksanaan

Kami akan bermula dengan shader asas yang menarik di seluruh skrin. Untuk memastikan ia berfungsi, cuba tetapkan skrin kepada hitam pepejal (atau mana-mana warna sewenang-wenangnya). Inilah cara persediaan yang saya gunakan dalam Javascript.

Anda boleh menanggalkan dan mengeditnya di CodePen. Klik butang di bahagian atas untuk melihat HTML, CSS, dan JS.

Shader kami semata-mata:

res dan piksel ada di sana untuk memberi kita koordinat piksel semasa. Kami lulus dimensi skrin dalam res sebagai pemboleh ubah seragam. (Kami tidak menggunakannya sekarang, tetapi kami tidak lama lagi.)

Langkah 1: Memindahkan Nilai di Separuh Piksel

Inilah yang kami mahu melaksanakan lagi:

Teknik umum kami ialah untuk memberikan setiap piksel memberikan beberapa nilai warna setiap bingkai kepada piksel yang berdekatan.

Nyatakan dalam bentuk semasa, ini tidak mungkin dilakukan dengan shader. Bolehkah anda melihat mengapa? Ingat bahawa semua shader boleh lakukan adalah mengembalikan nilai warna untuk piksel semasa ia diproses—jadi kita perlu menyatakan semula ini dengan cara yang hanya mempengaruhi piksel semasa. Kita boleh kata:

Setiap piksel sepatutnya mendapat beberapa warna dari jirannya, manakala kehilangan sebahagiannya sendiri.

Kini ini adalah sesuatu yang boleh kita laksanakan. Jika anda benar-benar cuba untuk melakukan ini, anda mungkin menghadapi masalah asas ...

Pertimbangkan satu kes yang lebih mudah. Katakan anda hanya mahu membuat shader yang bertukar imej merah perlahan dari masa ke masa. Anda mungkin menulis shader seperti ini:

Dan menjangkakan bahawa, setiap bingkai, komponen merah setiap piksel akan meningkat sebanyak 0.01. Sebaliknya, semua yang anda akan dapatkan ialah imej statik di mana semua piksel hanya sedikit redder daripada yang bermula. Komponen merah setiap piksel hanya akan meningkat sekali, walaupun pada hakikatnya shader berjalan setiap bingkai.

Bolehkah anda melihat mengapa?

Masalah

Masalahnya adalah bahawa apa-apa operasi yang kami lakukan dalam shader kami dihantar ke skrin dan kemudian hilang selama-lamanya. Proses kami sekarang kelihatan seperti ini:

Shader process

Kami melewati pemboleh ubah seragam dan tekstur kepada shader, ia menjadikan piksel sedikit redder, menariknya ke skrin, dan kemudian bermula dari awal lagi. Segala apa yang kita buat dalam shader akan dibersihkan dengan masa yang akan datang.

Apa yang kita mahu adalah seperti ini:

Repeatedly applying the shader to the texture

Daripada melukis ke skrin secara langsung, kita boleh melukis kepada beberapa tekstur, dan kemudian menarik tekstur itu ke skrin. Anda mendapat imej yang sama pada skrin seperti yang anda akan sebaliknya, kecuali sekarang anda boleh lulus output anda kembali sebagai input. Jadi anda boleh mempunyai shader yang membina atau menyebarkan sesuatu, dan bukannya dibersihkan setiap kali. Itulah yang saya panggil "tipu penimbal bingkai".

Bingkai Trick Penimbal

Teknik umum adalah sama pada mana-mana platform. Mencari "membuat tekstur" dalam apa jua bahasa atau alat yang anda gunakan harus memaparkan butiran pelaksanaan yang diperlukan. Anda juga boleh mencari cara menggunakan objek penampan bingkai, yang merupakan nama lain untuk dapat memberikan kepada beberapa penampan bukannya menjadikan rendering ke skrin.

Dalam ThreeJS, bersamaan dengan ini ialah WebGLRenderTarget. Inilah yang akan kami gunakan sebagai tekstur perantara kami untuk diberikan kepada. Terdapat satu kaveat kecil yang tinggal: anda tidak boleh membaca dan memberi tekstur yang sama pada masa yang sama. Cara paling mudah untuk mendapatkan sekitar itu adalah dengan hanya menggunakan dua tekstur.

Biarkan A dan B menjadi dua tekstur yang telah anda buat. Kaedah anda kemudiannya akan:

  1. Lulus A melalui shader anda, memberi ke B.
  2. Render B ke skrin.
  3. Lulus B melalui shader, menyebabkan A.
  4. Render A ke skrin anda.
  5. Ulang 1.

Atau, cara yang lebih ringkas untuk kod ini ialah:

  1. Lulus A melalui shader anda, memberi ke B.
  2. Render B ke skrin.
  3. Pertukaran A dan B (jadi pembolehubah A kini memegang tekstur yang berada di B dan sebaliknya).
  4. Ulang 1.

Itu sahaja yang diperlukan. Inilah pelaksanaannya dalam ThreeJS:

Anda boleh menanggalkan dan mengeditnya di CodePen. Kod shader baru berada dalam tab HTML.

Ini masih merupakan skrin hitam, yang merupakan permulaannya. Shader kami tidak terlalu berbeza sama ada:

Kecuali sekarang jika anda menambah baris ini (cuba!):

Anda akan melihat skrin perlahan-lahan bertukar merah, berbanding dengan peningkatan sebanyak 0.01 sekali sahaja. Ini adalah langkah yang cukup penting, jadi anda perlu mengambil sedikit masa untuk bermain-main dan membandingkannya dengan cara persediaan awal kami berfungsi.

Cabaran: Apa yang berlaku jika anda meletakkan gl_FragColor.r + =pixel.x; apabila menggunakan contoh penampan bingkai, berbanding apabila menggunakan contoh persediaan? Luangkan masa untuk berfikir tentang mengapa keputusan berbeza dan mengapa mereka masuk akal.

Langkah 2: Mendapatkan Sumber Asap

Sebelum kita boleh membuat apa-apa yang bergerak, kita memerlukan cara untuk mencipta asap di tempat pertama. Cara yang paling mudah adalah dengan menetapkan beberapa kawasan sewenang-wenang untuk putih di shader anda secara manual.

Jika kita mahu menguji sama ada penampan kerangka kami berfungsi dengan betul, kami boleh cuba menambah nilai warna bukan hanya menetapkannya. Anda harus melihat bulatan perlahan-lahan menjadi lebih putih dan putih.

Cara lain ialah menggantikan titik tetap dengan kedudukan tetikus. Anda boleh lulus nilai ketiga sama ada tetikus ditekan atau tidak, dengan cara itu anda boleh mengklik untuk membuat asap. Inilah pelaksanaannya.

Klik untuk menambah "asap". Anda boleh menanggalkan dan mengeditnya di CodePen.

Inilah yang kelihatan seperti shader kita sekarang:

Cabaran: Ingat bahawa cawangan (kondisional) biasanya mahal di shaders. Bolehkah anda menulis semula ini tanpa menggunakan pernyataan jika? (Penyelesaiannya adalah dalam CodePen.)

Sekiranya ini tidak masuk akal, terdapat penjelasan yang lebih terperinci tentang penggunaan tetikus dalam shader dalam tutorial pencahayaan sebelumnya.

Langkah 3: Selaraskan Asap

Sekarang ini adalah bahagian yang mudah—dan yang paling menggembirakan! Kami mempunyai semua keping sekarang, kami hanya perlu memberitahu shader: setiap piksel sepatutnya mendapat beberapa warna dari jirannya, sambil kehilangan sebahagiannya sendiri.

Saya mengesyorkan agar anda sentiasa membiarkan enjin permainan anda melakukan skala pensel anda untuk anda.

Kami mempunyai faktor f kami seperti dahulu. Dalam kes ini kita mempunyai masa tamat (0.016 adalah 1/60, kerana kita berjalan pada 60 fps) dan saya terus mencuba nombor sehingga saya tiba pada 14, yang nampaknya kelihatan baik. Inilah hasilnya:

Klik untuk menambah asap. Anda boleh menanggalkan dan mengeditnya di CodePen.

Uh Oh, Ia Terjebak!

Ini adalah persamaan menyebar yang sama yang kami gunakan dalam demo CPU, namun simulasi kami terjebak! Apa yang memberi?

Ternyata tekstur (seperti semua nombor pada komputer) mempunyai ketepatan terhad. Pada satu ketika, faktor yang kita tolak dengan begitu kecil sehingga ia dapat dibulatkan ke 0, jadi simulasi itu terjebak. Untuk membetulkan ini, kita perlu menyemak bahawa ia tidak jatuh di bawah nilai minima:

Saya menggunakan komponen r dan bukan rgb untuk mendapatkan faktor itu, kerana lebih mudah untuk bekerja dengan nombor tunggal, dan kerana semua komponen adalah nombor yang sama pula (sejak asap kami berwarna putih).

Dengan percubaan dan kesilapan, saya dapati 0.003 menjadi ambang yang baik di mana ia tidak terjebak. Saya hanya bimbang tentang faktor apabila ia negatif, untuk memastikan ia sentiasa berkurangan. Sebaik sahaja kita menggunakan penetapan ini, inilah yang kita dapat:

Klik untuk menambah asap. Anda boleh menanggalkan dan mengeditnya di CodePen.

Langkah 4: Selaraskan Asap ke Atas

Ini tidak kelihatan seperti asap, walaupun. Jika kita mahu ia mengalir ke atas dan bukannya dalam setiap arah, kita perlu menambah beberapa berat. Jika piksel bawah selalu mempunyai pengaruh yang lebih besar daripada arah yang lain, maka piksel kita akan kelihatan naik.

Dengan bermain bersama dengan pekali, kita boleh sampai pada sesuatu yang kelihatan cantik dengan persamaan ini:

Dan inilah yang kelihatan seperti:

Klik untuk menambah asap. Anda boleh menanggalkan dan mengeditnya di CodePen.

Nota mengenai Persamaan Penyebaran

Pada dasarnya saya fiddled sekitar dengan pekali di sana untuk membuat ia kelihatan baik mengalir ke atas. Anda juga boleh mengalir ke arah yang lain.

Penting untuk diperhatikan bahawa sangat mudah untuk membuat simulasi ini "meletup". (Cuba ubah 6.0 di sana ke 5.0 dan lihat apa yang berlaku). Ini jelas kerana sel-sel lebih banyak daripada mereka kehilangan.

Persamaan ini sebenarnya adalah apa yang saya sebut kertas itu sebagai model yang "osak". Mereka mempersembahkan persamaan alternatif yang lebih stabil, tetapi tidak begitu mudah untuk kita, terutamanya kerana perlu menulis ke grid yang dibaca dari. Dengan kata lain, kita perlu membaca dan menulis dengan tekstur yang sama pada masa yang sama.

Apa yang kami ada cukup untuk tujuan kami, tetapi anda boleh melihat penjelasan di dalam kertas itu jika anda ingin tahu. Anda juga akan mencari persamaan alternatif yang dilaksanakan dalam demo CPU interaktif dalam fungsi diffuse_advanced ().

Betulkan Pantas

Satu perkara yang anda perasan, jika anda bermain-main dengan asap anda, ialah ia terjebak di bahagian bawah skrin jika anda menjana beberapa di sana.Ini kerana piksel di baris bawah cuba untuk mendapatkan nilai dari piksel di bawah mereka yang tidak wujud.

Untuk membetulkannya, kami hanya pastikan bahawa piksel di baris bawah mencari 0 di bawahnya:

Dalam demo CPU, saya berurusan dengan itu dengan hanya tidak membuat sel-sel dalam meresap sempadan. Anda alternatif boleh menetapkan hanya secara manual mana-mana di luar batas sel mempunyai nilai 0. (The grid dalam demo CPU meluas oleh satu baris dan lajur sel dalam setiap arah, jadi anda tidak benar-benar melihat sempadan)

A Grid Velocity

Tahniah! Anda kini mempunyai shader asap yang bekerja! Perkara terakhir yang saya ingin bincangkan secara ringkas adalah bidang halaju yang disebutkan oleh kertas itu.

Velocity field from Jon Stams paper

Asap anda tidak perlu meresap secara seragam atau ke arah tertentu; ia boleh mengikuti corak umum seperti yang digambarkan. Anda boleh melakukan ini dengan menghantar tekstur lain di mana nilai warna mewakili arah asap yang harus mengalir di lokasi tersebut, dengan cara yang sama yang kita menggunakan peta biasa untuk menentukan arah pada setiap piksel dalam tutorial pencahayaan kita.

Malah, tekstur halaju anda tidak perlu statik sama ada! Anda boleh menggunakan helah penimbal bingkai untuk juga mempunyai perubahan halaju dalam masa nyata. Saya tidak akan membincangkannya dalam tutorial ini, tetapi terdapat banyak potensi untuk diterokai.

Kesimpulan

Sekiranya ada apa-apa yang perlu diambil dari tutorial ini, ia dapat memberikan tekstur bukan hanya pada skrin yang merupakan teknik yang sangat berguna.

Apakah Buffer Buffers Baik Untuk?

Satu penggunaan umum untuk ini adalah pemprosesan pasca dalam permainan. Jika anda ingin memohon beberapa jenis penapis warna, dan bukannya memohon kepada setiap objek tunggal, anda boleh menyebabkan semua objek anda menjadi tekstur saiz skrin, kemudian gunakan shader anda untuk tekstur akhir dan lukiskannya ke skrin.

Satu lagi contohnya ialah apabila melaksanakan shaders yang memerlukan banyak pas, seperti kabur. Anda biasanya akan menjalankan imej anda melalui shader, mengaburkan arah x, kemudian luruskannya sekali lagi untuk mengaburkan arah-y.

Contoh akhir adalah penyajian tertunda, seperti yang dibahas dalam tutorial pencahayaan sebelumnya, yang merupakan cara mudah untuk menambah banyak sumber cahaya ke tempat kejadian dengan cekap. Perkara yang keren tentang ini ialah mengira pencahayaan tidak lagi bergantung pada jumlah sumber cahaya yang anda miliki.

Jangan Takut Kertas Teknikal

Terdapat pasti lebih terperinci yang diliputi dalam kertas yang saya sebutkan, dan ia menganggap anda mempunyai beberapa kebiasaan dengan aljabar linear, tetapi jangan biarkan itu menghalang anda daripada membedahnya dan cuba untuk melaksanakannya. Intipati itu cukup mudah dilaksanakan (selepas beberapa tinkering dengan pekali).

Mudah-mudahan anda telah mempelajari sedikit lebih lanjut tentang shaders di sini, dan jika anda mempunyai sebarang pertanyaan, cadangan, atau peningkatan, sila bagikan di bawah!

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.