Pada bulan September tahun ini, game mobile Titan World dari Unstoppable, kantor Mlu mobile Glu, akan dirilis. Proyek itu dibatalkan tepat sebelum rilis dunia. Tetapi prestasi tetap, dan yang paling menarik dari mereka, dengan izin baik dari kepala studio Dennis Zdonov dan Alex Paley, saya ingin berbagi dengan publik.Pada bulan Maret 2018, pemimpin tim dan saya mengadakan pertemuan di mana kami membahas apa yang harus dilakukan selanjutnya: kode render selesai, dan tidak ada fitur baru dan efek khusus dalam rencana. Rasanya seperti pilihan logis untuk menulis ulang sistem partikel dari awal - menurut semua tes, ini memberikan penurunan terbesar dalam produktivitas, ditambah itu membuat desainer gila dengan antarmuka (file konfigurasi teks) dan kemampuan yang sangat sedikit.
Perlu dicatat bahwa sebagian besar waktu tim bekerja pada permainan dalam mode "besok rilis", jadi saya menulis semua subsistem, pertama, berusaha untuk tidak merusak apa yang sudah bekerja, dan kedua, dengan siklus pengembangan pendek. Secara khusus, sebagian besar efek yang tidak mampu dilakukan oleh sistem reguler dilakukan dalam fragmen shader tanpa mempengaruhi kode utama.
Pembatasan jumlah partikel (matriks transformasi untuk setiap partikel dibentuk pada cpu, kesimpulannya adalah melalui installer dari ios gl-extensible), misalnya, perlu untuk menulis shader yang "ditiru" array besar partikel berdasarkan pada representasi analitik dari bentuk benda dan ditambah dengan ruang. palm off data palsu ke dalam buffer kedalaman.
Koordinat-z dari fragmen dihitung untuk partikel bidang, seolah-olah kita sedang menggambar bola, dan jari-jari bola ini dimodulasi oleh sinus kebisingan Perlin dengan mempertimbangkan waktu:
r=.5+.5*sin(perlin(specialUV)+time)
Penjelasan lengkap tentang rekonstruksi kedalaman bola dapat ditemukan di
Íñigo Quílez , tetapi saya menggunakan kode yang disederhanakan dan lebih cepat. Tentu saja, ia adalah perkiraan kasar, tetapi pada bentuk geometris yang kompleks (asap, ledakan) ia memberikan gambaran yang cukup baik.
Tangkapan layar gameplay. "Rok" asap dibuat dalam satu bagian kecil, beberapa lagi tertinggal di bagian utama ledakan. Tentu saja, itu terlihat paling spektakuler "dari tanah", ketika asap dengan lembut menyelimuti bangunan dan unit, namun, proposal untuk mengubah posisi kamera selama ledakan tidak masuk ke produksi.Pernyataan masalah
Apa yang ingin Anda dapatkan di jalan keluar? Kami pergi, lebih tepatnya, dari batasan-batasan yang membuat kami tersiksa pada sistem partikel sebelumnya. Situasi ini diperburuk oleh fakta bahwa anggaran frame hampir habis, dan pada perangkat yang lemah (seperti ipad air), kedua pixel dan pipa vertex terisi penuh. Oleh karena itu, saya ingin mendapatkan sistem yang paling produktif sebagai hasilnya, walaupun saya sedikit membatasi fungsionalitas.
Desainer menyusun daftar fitur dan menggambar sketsa UI berdasarkan pengalaman dan praktik mereka sendiri dengan kesatuan, tidak nyata dan setelah efek.
Teknologi yang tersedia
Karena warisan dan pembatasan yang diberlakukan oleh kantor pusat, kami terbatas pada peluang 2. Oleh karena itu, teknologi seperti mengubah umpan balik yang digunakan dalam sistem partikel modern tidak tersedia.
Apa yang tersisa? Gunakan vertex tekstur untuk mengambil dan menyimpan posisi / akselerasi dalam tekstur? Pilihan yang berfungsi, tetapi ingatannya juga hampir berakhir, kinerja solusi semacam itu bukanlah yang paling optimal, dan hasilnya tidak berbeda dalam keindahan arsitektur.
Pada saat ini, saya telah membaca banyak artikel tentang penerapan sistem partikel pada GPU. Sebagian besar berisi judul yang cerah ("jutaan partikel di gpu seluler, dengan preferensi dan penyair"), namun, implementasi datang ke contoh sederhana, meskipun penghibur / penarik yang tampak lucu, dan secara umum hampir tidak berguna untuk penggunaan nyata dalam permainan.
Artikel ini membawa manfaat maksimal: penulis memecahkan masalah nyata, dan tidak melakukan "partikel bola dalam ruang hampa". Angka-angka patokan dari artikel ini dan hasil profiling menghemat banyak waktu pada tahap desain.
Cari pendekatan
Saya mulai dengan mengklasifikasikan masalah yang diselesaikan oleh sistem partikel dan mencari kasus-kasus tertentu. Ternyata kira-kira berikut ini (sepotong dermaga nyata dari konsep dari korespondensi dengan pemimpin tim):
“- Partikel / susunan jala dengan gerakan siklik. Tidak ada posisi pemrosesan, semua melalui persamaan gerak. Aplikasi - asap dari pipa, uap di atas air, salju / hujan, kabut volumetrik, pohon yang bergoyang, penggunaan parsial pada efek non-siklik dari ledakan alias dimungkinkan.
- Kaset. Pembentukan vb oleh acara, hanya memproses pada GPU (tembakan oleh sinar, penerbangan sepanjang lintasan tetap (?) Dengan jejak). Mungkin varian dengan transfer koordinat start-finish ke seragam dan konstruksi pita oleh vertexID akan lepas landas. dengan t.z. render silang dengan fresnel seperti pada directlights + uvscroll.
- Pembuatan partikel dan pemrosesan kecepatan. Opsi paling fleksibel dan paling sulit / paling lambat, lihat pemrosesan gerak teknologi. "
Singkatnya: ada efek partikel yang berbeda, dan beberapa di antaranya dapat diimplementasikan lebih mudah daripada yang lain.
Kami memutuskan untuk membagi tugas menjadi beberapa iterasi - dari yang sederhana ke yang kompleks. Prototyping dilakukan pada engine / editor saya di bawah windows / directx11 karena fakta bahwa kecepatan pengembangan tersebut beberapa kali lipat lebih tinggi. Proyek ini dikompilasi dalam beberapa detik, dan shader sepenuhnya diedit dengan cepat dan dikompilasi di latar belakang, menampilkan hasilnya secara real time dan tanpa memerlukan gerakan tambahan seperti menekan tombol. Siapa pun yang membangun proyek besar dengan banyak macbook / xcode, saya pikir, akan memahami alasan keputusan ini.
Semua contoh kode akan diambil dari prototipe windows.
Lingkungan pengembangan untuk windows.Implementasi
Tahap pertama adalah output statis dari array partikel. Tidak ada yang rumit: mulai vertex bufffer, isi dengan paha depan (tulis uv yang benar untuk setiap quad), dan jahit id simpul ke dalam uv "tambahan". Setelah itu, di shader, dengan id titik berdasarkan pengaturan emitor, kami membentuk posisi partikel, dan dengan uv kami mengembalikan koordinat layar.
Jika vertex_id tersedia secara native, Anda dapat sepenuhnya melakukannya tanpa buffer dan tanpa uv untuk mengembalikan koordinat layar (sebagai akibatnya dilakukan dalam versi windows).
Shader:
struct VS_INPUT { … uint v_id:SV_VertexID; … } //float index = input.uv2.x/6.0;// vertex_id index = floor(input.v_id/6.0);// vertex_id float2 map[6]={0,0,1,0,1,1,0,0,1,1,0,1}; float2 quaduv=map[frac(input.v_id/6.0)*6];
Setelah itu, Anda dapat menerapkan skenario sederhana dengan jumlah kode yang sangat kecil, misalnya, gerakan siklik dengan penyimpangan kecil cocok untuk efek salju. Namun, tujuan kami adalah untuk memberikan kontrol terhadap perilaku partikel ke sisi para seniman, dan mereka, seperti yang Anda tahu, jarang tahu bagaimana cara shader. Opsi dengan preset perilaku dan parameter pengeditan melalui slider juga tidak menarik - beralih shader atau bercabang di dalam, mengalikan opsi preset, kurangnya kontrol penuh.
Tugas selanjutnya adalah mengimplementasikan fade in / fade out untuk sistem seperti itu. Partikel seharusnya tidak muncul entah dari mana dan menghilang ke mana-mana. Dalam implementasi klasik dari sistem partikel, kami memproses buffer secara terprogram menggunakan cpu, membuat partikel baru dan menghilangkan yang lama. Bahkan, untuk mendapatkan kinerja yang baik, Anda perlu menulis manajer memori yang cerdas. Tapi apa yang terjadi jika Anda tidak menggambar partikel "mati"?
Misalkan (sebagai permulaan) bahwa interval waktu dari emisi partikel dan masa hidup suatu partikel adalah konstan dalam satu emitor tunggal.

Kemudian kita dapat secara spekulatif menyajikan buffer kita (yang hanya berisi id titik) sebagai lingkaran dan menentukan ukuran maksimumnya sebagai berikut:
pCount = round (prtPerSec * LifeTime / 60.0); pCountT = floor (prtPerSec * EmissionEndTime / 60.0); pCount=min (pCount, pCountT);
dan di shader, hitung waktu berdasarkan indeks dan waktu (waktu berlalu sejak awal efek)
pTime=time-index/prtPerSec;
Jika emitor berada dalam fase siklik (semua partikel dipancarkan dan sekarang mati dan dilahirkan secara serempak), kita membuat frac dari waktu partikel dan dengan demikian mendapatkan loop.
Kita tidak perlu menggambar partikel dengan pTime kurang dari nol - mereka belum dilahirkan. Hal yang sama berlaku untuk partikel di mana jumlah masa pakai dan waktu saat ini melebihi waktu akhir emisi. Dalam kedua kasus, kami tidak akan menggambar apa pun dengan membatalkan ukuran partikel dan / atau menjatuhkannya di belakang layar. Pendekatan ini akan memberikan overhead kecil dalam fase fadein / fadeout, sambil mempertahankan kinerja maksimum dalam fase berkelanjutan.
Algoritma dapat sedikit ditingkatkan dengan mengirimkan hanya bagian dari buffer verteks yang berisi partikel hidup untuk rendering. Karena kenyataan bahwa emisi terjadi secara berurutan, partikel-partikel hidup akan tersegmentasi paling banyak sekali, yaitu. diperlukan dua drawcall.
Sekarang, mengetahui waktu saat ini dari setiap partikel, Anda dapat mengatur kecepatan, akselerasi (dan, secara umum, parameter lainnya) untuk menulis persamaan gerak, menghasilkan koordinat di ruang dunia.
Menggunakan dipulihkan dari vertex_id uv, kita sudah akan mendapatkan empat poin (lebih tepatnya, kita akan memindahkan masing-masing titik quad ke arah yang kita butuhkan), di mana vertex shader, setelah menyelesaikan proyeksi, akan menyelesaikan pekerjaannya.
p.xy+=(quaduv-.5);
Dengan bonus gratis, kami mendapat kesempatan tidak hanya untuk menjeda emitor, tetapi juga untuk memundurkan waktu bolak-balik dengan akurasi ke frame. Fitur ini ternyata sangat berguna dalam desain efek kompleks.
Kami meningkatkan fungsionalitasnya
Iterasi berikutnya dalam pengembangan adalah solusi untuk masalah emitor bergerak. Sistem khusus kami tidak tahu apa-apa tentang posisinya, dan ketika emitor bergerak, seluruh efek bergerak secara serempak di belakangnya. Untuk asap dari pipa knalpot dan efek serupa, itu terlihat lebih dari aneh.
Idenya adalah untuk merekam posisi emitor dalam buffer verteks ketika partikel baru lahir. Karena jumlah partikel tersebut kecil, overhead seharusnya minimal.
Seorang kolega menyarankan bahwa ketika mengembangkan UI-nya sendiri, ia menggunakan map / unmap hanya bagian dari vertex buffer dan cukup senang dengan kinerja solusi ini. Saya melakukan tes, dan ternyata pendekatan ini benar-benar berfungsi dengan baik pada platform desktop dan mobile.
Kesulitan muncul dengan sinkronisasi waktu pada cpu dan gpu. Itu perlu untuk memastikan bahwa pembaruan buffer dibuat tepat ketika partikel "baru" yang dilingkarkan berada di posisi awal. Artinya, sehubungan dengan buffer cincin, perlu untuk menyinkronkan batas-batas wilayah pembaruan dengan waktu operasi emitor.
Saya mentransfer kode hlsl ke C ++, untuk pengujian saya menulis emitor bergerak di sekitar Lissajous, dan semua ini tiba-tiba berhasil. Namun, dari waktu ke waktu, sistem "meludah" satu partikel atau lebih, menembakkannya ke arah yang sewenang-wenang, tidak mengeluarkannya tepat waktu, atau membuat yang baru di tempat yang sewenang-wenang.
Masalahnya diselesaikan dengan mengaudit keakuratan menghitung waktu di mesin dan secara bersamaan memeriksa delta waktu saat merekam posisi emitor baru - sehingga seluruh bagian buffer yang tidak terpengaruh oleh iterasi sebelumnya diperbarui. Sistem ini juga diperlukan untuk bekerja dalam kondisi desync yang dipaksakan - penarikan tiba-tiba fps tidak boleh merusak efeknya, terutama karena untuk perangkat yang berbeda permainan kami mencatat fps yang berbeda sesuai dengan kinerja - 60/30/20.
Kode metode telah berkembang cukup banyak (buffer cincin sulit untuk diproses secara elegan), namun, setelah memperhitungkan semua kondisi, sistem bekerja dengan benar dan stabil.
Sekitar waktu ini, mitra sudah membuat "ikan" dari editor, cukup untuk menguji sistem, dan menulis templat / api untuk mengintegrasikan sistem ke mesin kami.
Saya porting semua kode ke ios / opengl, terintegrasi dan akhirnya membuat tes efek nyata pada perangkat nyata. Menjadi jelas bahwa sistem tidak hanya berfungsi, tetapi juga cocok untuk produksi. Tetap menyelesaikan editor UI dan memoles kode ke negara "itu tidak menakutkan untuk memberikannya untuk rilis besok".
Kami bahkan sudah siap untuk menulis manajer memori agar tidak mengalokasikan / menghancurkan buffer (yang akhirnya disimpan vertex_id, uv, posisi dan vektor partikel awal) untuk setiap efek baru dengan emitor dinamis, ketika ide lain datang ke kepala saya.
Fakta keberadaan buffer vertex dalam sistem ini menghantuiku. Dia jelas melihat dalam arkaismenya, "warisan zaman kegelapan dari conveyor tetap." Ketika membuat efek pengujian pada prototipe windows, saya berpikir bahwa gerakan emitor selalu mulus dan selalu jauh lebih lambat daripada gerakan partikel. Selain itu, dengan sejumlah besar partikel, memperbarui posisi mengarah pada fakta bahwa ratusan partikel merekam data yang sama. Solusinya ternyata sederhana: kami memperkenalkan array tetap ke mana "sejarah" posisi emitor, dinormalisasi oleh masa hidup partikel, akan jatuh. Dan pada GPU kami akan menginterpolasi data. Setelah itu, kebutuhan buffer dinamis menghilang dalam versi ios / gles2 (hanya statis umum yang tersisa untuk mengimplementasikan vertex_id), dan pada versi windows / dx11 buffer menghilang sama sekali karena vertex_id asli dan kemampuan d3d api untuk menerima null alih-alih menghubungkan ke buffer vertex.
Dengan demikian, versi-menang dari sistem, dengan standar modern, tidak mengkonsumsi memori sama sekali, tidak peduli berapa banyak partikel yang ingin kita tampilkan. Hanya penyangga konstan kecil dengan parameter, penyangga posisi / pangkalan (60 pasang vektor ternyata cukup, dengan margin, untuk setiap kasus), dan, jika perlu, tekstur. Pengukuran kinerja menunjukkan kecepatan yang dekat dengan tes sintetis.
Selain itu, "ekor" dalam efek seperti bunga api mulai terlihat jauh lebih alami, karena interpolasi memungkinkan untuk menghapus diskritisasi dengan bingkai dan dengan cara ini emitor mengubah posisinya dengan lancar, seolah-olah menggambar panggilan dilakukan dengan frekuensi ratusan hertz.
Fitur
Selain fungsi dasar dari penerbangan partikel (kecepatan, akselerasi, gravitasi, ketahanan medium), kami membutuhkan sejumlah "lemak" fungsional.
Akibatnya, gerakan kabur (peregangan partikel di sepanjang vektor gerakan), orientasi partikel melintasi vektor gerakan (ini memungkinkan, misalnya, untuk membuat bola partikel), mengubah ukuran partikel sesuai dengan waktu saat ini dalam hidupnya, dan lusinan hal kecil lainnya dilaksanakan.
Kompleksitas muncul dengan bidang vektor: karena sistem tidak menyimpan keadaannya (posisi, akselerasi, dll.) Untuk setiap partikel, tetapi menghitungnya setiap kali melalui persamaan gerak, sejumlah efek (seperti pergerakan busa saat mengaduk kopi) pada prinsipnya tidak mungkin. Namun, modulasi sederhana kecepatan dan akselerasi oleh suara perlin memberikan hasil yang terlihat cukup modern. Perhitungan noise real-time untuk begitu banyak partikel ternyata terlalu mahal (bahkan dengan batas lima oktaf), sehingga tekstur dihasilkan dari mana vertex shader kemudian akan sampel. Untuk meningkatkan efek bidang vektor palsu, pergeseran kecil koordinat sampel ditambahkan tergantung pada waktu saat emitor.
Tes asap rokok bekerja dengan mendistribusikan kecepatan awal dan akselerasi terhadap kebisingan perlin.Konveyor piksel
Awalnya, kami hanya berencana untuk mengubah warna / transparansi partikel tergantung pada waktunya. Saya menambahkan beberapa algoritma ke pixel shader.
Rotasi warna tekstur - disederhanakan, sin (warna + waktu). Memungkinkan sampai batas tertentu meniru efek permutasi dari AfterEffects.
Pencahayaan palsu - modulasi warna partikel oleh gradien dalam koordinat dunia, terlepas dari sudut rotasi partikel.
Evolusi perbatasan - ketika sebuah partikel bergerak di ruang angkasa, perbatasannya (saluran alpha) dimodulasi oleh kombinasi sorotan dan suara perlin, yang memberikan dinamika alirannya, sangat mirip dengan awan, asap, dan efek cairan lainnya.
Kode Pseudo Shader:
b=perlin(uv)
Dalam versi yang sedikit rumit, shader ini bisa menggambar perbatasan dengan kelembutan yang sewenang-wenang dan dengan highlight kontur, yang menambahkan efek "eksplosif" ke realisme.
Eksperimen pertama dengan evolusi batas.Apa selanjutnya
Meskipun editor, sudah siap untuk bekerja dan terintegrasi ke dalam mesin, para perancang tidak punya waktu untuk membuat efek tunggal di atasnya - proyek ditutup. Namun demikian, tidak ada hambatan untuk menggunakan praktik-praktik ini di tempat lain - misalnya, untuk melakukan pekerjaan pada Demo Revisi.
Dari sudut pandang teknologi, ada juga ruang untuk bergerak - sekarang, misalnya, beberapa efek penghancuran objek kerangka kawat sedang beroperasi:

Pertanyaan tentang penyortiran partikel untuk alpha-blending tetap terbuka sejauh ini: karena semuanya dianggap analitis dalam shader, sebenarnya tidak ada data input untuk disortir. Tetapi ada bidang besar untuk eksperimen!
Selama pengembangan Titan World, banyak trik diterapkan di bagian grafis permainan, tetapi lebih banyak tentang itu di waktu berikutnya.
NB Anda dapat menggali mesin sumber alpha di
sini . Contohnya ada di folder rilis / sampel, tombol kontrol utama adalah ruang, alt | control + mouse. Shader terletak langsung di file fxp, kode mereka tersedia melalui jendela editor.