Algoritma interaksi ratusan ribu partikel unik pada GPU, dalam GLES3 dan WebGL2

Deskripsi algoritma logika, dan analisis contoh kerja dalam bentuk game techno-demo


Versi webGL2 dari demo ini https://danilw.itch.io/flat-maze-web untuk tautan lain, lihat artikelnya.



Artikel ini dibagi menjadi dua bagian, pertama tentang logika, dan bagian kedua tentang aplikasi dalam permainan, bagian pertama :


  • Fitur Utama
  • Tautan dan deskripsi singkat.
  • Algoritma logika.
  • Keterbatasan logika. Bug / fitur, dan Bug sudut.
  • Akses ke data indeks.

Penjelasan lebih lanjut tentang demo game, bagian kedua :


  • Fitur yang digunakan dari logika ini. Dan rendering cepat satu juta partikel piksel.
  • Implementasi, beberapa komentar pada kode, deskripsi tabrakan dalam dua arah. Dan interaksi dengan pemain.
  • Tautan ke grafik yang digunakan dengan opengameart, dan shader untuk bayangan. Dan tautan artikel ke cyberleninka.ru

Bagian 1


1. Fitur Utama


Idenya adalah tabrakan / fisika dari ratusan ribu partikel di antara mereka, secara real time, di mana setiap partikel memiliki ID pengidentifikasi unik.


Ketika setiap partikel diindeks, dimungkinkan untuk mengontrol parameter apa pun dari partikel apa pun , misalnya massa, kesehatannya (hp) atau kerusakan, akselerasi, perlambatan, yang menjadi objek yang ditemui dan reaksi terhadap peristiwa tersebut tergantung pada jenis / indeks partikel, juga pengatur waktu unik untuk setiap partikel. , dan seterusnya sesuai kebutuhan.


Semua logika pada GLSL sepenuhnya portabel untuk mesin game dan OS apa pun yang memiliki dukungan GLES3.


Jumlah maksimum partikel sama dengan ukuran framebuffer (fbo, semua piksel).


Sejumlah partikel yang nyaman (ketika ada ruang bagi partikel untuk berinteraksi) adalah (Resolution.x*Resolution.y/2)/2 adalah setiap piksel kedua dalam x dan setiap piksel kedua dalam y , itulah sebabnya deskripsi logika mengatakan demikian.


Di bagian pertama artikel, logika minimal ditampilkan, di bagian kedua pada contoh permainan, logika dengan sejumlah besar kondisi interaksi.


2. Tautan dan deskripsi singkat


Saya membuat tiga demo pada logika ini:


1. Pada GLSL fragmen-shader , pada shadertoy https://www.shadertoy.com/view/tstSz7 , lihat kode BufferC di dalamnya semua logika. Kode ini juga memungkinkan Anda untuk menampilkan ratusan ribu partikel dengan UV-nya, dalam posisi sewenang-wenang, pada fragmen-shader tanpa menggunakan partikel yang dipasang.



2. Porting logic ke instance-partikel (digunakan oleh Godot sebagai mesin)



Tautan versi Web , exe (win) , sumber proyek partikel_2D_self_collision .


Deskripsi singkat: Ini adalah demonstrasi buruk pada partikel yang dipasang, karena fakta bahwa saya membuat peningkatan maksimum di mana seluruh peta terlihat, 640x360 partikel (230k) selalu diproses, ini banyak. Lihat di bawah dalam deskripsi permainan, di sana saya melakukannya dengan benar, tanpa partikel tambahan. (ada kesalahan indeks partikel dalam video, ini diperbaiki dalam kode)


3. Gim, tentangnya di bawah ini dalam deskripsi gim. Tautan versi Web , exe (win) , sumber


3. Algoritma logika


Secara singkat:


Logikanya mirip dengan pasir jatuh, setiap piksel mempertahankan nilai fraksional dari posisi (bergeser dalam pikselnya) dan akselerasi saat ini.


Logika memeriksa piksel dalam jari-jari 1, bahwa posisi mereka berikutnya ingin pergi ke piksel ini (karena pembatasan ini, lihat batasan di bawah) , juga piksel dalam jari-jari 2 untuk tolakan (benturan).


Indeks unik disimpan dengan menerjemahkan logika ke int-float, dan mengurangi ukuran pos posisi dan kecepatan pos .


Data disimpan dengan cara ini: (karena bug ini, lihat batasan)


 pixel.rgba r=[0xfffff-posx, 0xf-data] g=[0xfffff-posy, 0xf-data] b=[0xffff-velx, 0xff-data] a=[0xffff-vely, 0xff-data] 


Dalam kode , nomor baris untuk BufC https://www.shadertoy.com/view/tstSz7 , 115 transisi-cek, 139 cek-tabrakan.


Ini adalah loop sederhana untuk mengambil nilai yang berdekatan. Dan kondisinya adalah, jika posisi diambil sama dengan posisi piksel saat ini, maka kami memindahkan data tersebut ke piksel ini (karena batasan ini) , dan nilai perubahan berubah tergantung pada piksel tetangga, jika ada.


Ini semua logika partikel.


Cara terbaik adalah menempatkan partikel pada jarak 1 pixel dari satu sama lain jika mereka lebih dekat dari 1 pixel, maka akan ada tolakan, sebagai contoh peta dengan labirin dalam permainan, partikel berdiri di tempat mereka tanpa bergerak karena jarak 1 pixel di antara mereka.


Selanjutnya adalah rendering (rendering), dalam kasus fragmen-shader, piksel diambil dalam radius 1 untuk menampilkan area berpotongan. Dalam kasus partikel yang di-instansasikan, sebuah piksel diambil pada alamat INSTANCE_ID diterjemahkan dari tampilan linear menjadi array dua dimensi.


4. Keterbatasan logika. Bug / fitur, dan bug ANGLE


  1. Ukuran piksel , BALL_SIZE dalam kode, harus dalam batas perhitungan, lebih besar dari sqrt(2)/2 dan kurang dari 1 . Semakin dekat ke 1, semakin sedikit ruang untuk berjalan di dalam piksel (piksel itu sendiri), semakin sedikit ruang. Ukuran seperti itu diperlukan agar piksel tidak saling jatuh, kurang dari 1 dapat diatur ketika Anda memiliki objek kecil, ilusi objek kurang dari 1 piksel (dihitung) dibuat.
  2. Kecepatan tidak boleh lebih dari 1 piksel jika tidak piksel akan hilang. Tetapi untuk memiliki kecepatan lebih dari 1 per frame, Anda bisa, jika Anda membuat beberapa framebuffer (fbo / viewport) dan memproses beberapa langkah logika per frame-speed, itu akan meningkatkan jumlah kali sama dengan jumlah fbo tambahan. Inilah yang saya lakukan di demo buah, dan menggunakan tautan ke shadertoy (bufC disalin ke bufD).
  3. Batasan tekanan (seperti gravitasi, atau peta gaya-normal lainnya). Jika beberapa piksel tetangga mengambil posisi ini (lihat gambar di atas), maka hanya satu yang disimpan, piksel yang tersisa hilang. Ini mudah dilihat dalam demo di shadertoy, atur tetikus ke Force, ubah nilai MOUSE_F pada Common menjadi 10 , dan arahkan partikel ke sudut layar, mereka akan menghilang satu sama lain. Atau sama dengan nilai gravitasi maxG di Common .
  4. Bug di Sudut. Agar logika ini bekerja di partikel-GPU (yang dipasang), yang terbaik (lebih murah, lebih cepat) untuk menghitung posisi, dan semua parameter partikel lainnya untuk tampilan, dalam contoh-shader . Tetapi Angle tidak mengizinkan penggunaan lebih dari satu fbo-tekstur untuk shader, sehingga perhitungan bagian dari logika harus ditransfer ke Vertex-shader di mana untuk mentransfer nomor indeks dari instance shader. Inilah yang saya lakukan di kedua demo dengan partikel GPU.
  5. Bug serius di kedua demo (kecuali untuk game) nilai posisi akan hilang jika bukan kelipatan 1/0xfffff tes bug ada di sini https://www.shadertoy.com/view/WdtSWS
    Lebih tepatnya, ini bukan bug, dan memang seharusnya demikian, untuk kesederhanaan, sebagai bagian dari algoritma ini, saya menyebutnya bug.

Perbaiki bug:
Jangan mengonversi nilai posisi menjadi int-float , karena 0xff ini 0xff hilang, 8 bit tersedia untuk data, tetapi nilai 0xffff untuk data akan tetap, yang mungkin cukup untuk banyak hal.
Saya melakukan hal itu dalam demo game , saya hanya menggunakan 0xffff untuk data di mana jenis partikel, timer animasi, kesehatan disimpan, dan masih ada ruang kosong.


5. Akses ke data indeks


instanced-partikel memiliki INSTANCE_ID sendiri, dibutuhkan piksel dari tekstur framebuffer dengan logika partikel (bufC, contoh untuk shader), jika di sana kita membongkar partikel (lihat penyimpanan data) ID partikel ini , dengan ID ini kita membaca tekstur dengan data untuk partikel (bufB , contoh pada shader).


Dalam contoh shadertoy, bufB hanya menyimpan warna untuk setiap partikel, tetapi jelas bahwa ada data apa pun, seperti massa, akselerasi, deselerasi yang ditulis sebelumnya, serta tindakan logis apa pun (misalnya, Anda dapat memindahkan partikel apa pun ke posisi apa pun (teleport) jika dilakukan tindakan logis yang sesuai dalam kode), Anda juga dapat mengontrol pergerakan partikel atau grup dari keyboard ...


Maksud saya, Anda dapat melakukan apa saja dengan setiap partikel seolah-olah mereka adalah partikel biasa dalam sebuah array pada prosesor, akses dua arah dari partikel GPU dapat mengubah keadaannya, tetapi juga dari CPU Anda dapat mengubah keadaan partikel dengan indeks (menggunakan tindakan dan tekstur logis) buffer data).


Bagian 2


1. Fitur yang digunakan dari logika ini. Dan rendering cepat satu juta partikel piksel


Ukuran framebuffer (fbo / viewport) untuk partikel adalah 1280x720, bagian-bagiannya terletak setelah 1, ini adalah 230 ribu partikel aktif (elemen aktif dalam labirin).
Selalu ada tidak lebih dari 12 ribu partikel GPU di layar.


Logika menggunakan:


  • Logika pemain terpisah dari partikel logika, dan hanya membaca data dari buffer partikel.
  • Pemain melambat saat bertabrakan dengan benda.
  • Objek tipe monster memberikan damage kepada pemain.
  • Pemain memiliki 2 serangan, satu mengusir semua yang ada di sekitar, yang kedua menciptakan partikel seperti bola api (gambarnya seperti ini)
  • Jenis bola api memiliki massa sendiri, dan pelacakan tabrakan bilateral dengan partikel lainnya bekerja.
  • partikel lain seperti gips dan zombie (satu jenis gips kebal) dihancurkan dalam tabrakan dengan bola api
  • bola api padam setelah satu tabrakan
  • Tingkat fisika - pohon dan kotak ditolak oleh pemain, partikel lain tidak berinteraksi, tidak ada akselerasi yang bekerja pada bola api
  • timer animasi unik untuk setiap partikel

Dibandingkan dengan demo buah, di mana ada overhead, dalam game ini jumlah partikel yang dipasang GPU hanya 12 ribu.


Ini terlihat seperti ini:



Jumlahnya bergantung pada zoom saat ini ( zoom ) peta, dan kenaikannya terbatas pada nilai tertentu, jadi hanya yang terlihat di layar yang dipertimbangkan.
Layar bergeser dengan pemain, logika untuk menghitung pergeseran agak rumit, dan sangat situasional, saya ragu bahwa dia akan menemukan aplikasi di proyek lain.


2. Implementasi, beberapa komentar pada kode.


Semua kode game ada di GPU.


Logika untuk menghitung perpindahan partikel di layar dengan peningkatan fungsi simpul di file / shaders/scene2/particle_logic2.shader adalah file shader partikel (vertex dan fragmen), bukan shader yang di- instal, shader yang di- instal tidak melakukan apa-apa, hanya melewati indeksnya karena bug yang dijelaskan di atas.


partikel berdasarkan jenis dan seluruh logika interaksi partikel dalam file, ini adalah file frame shader shader file shader / scene2 / Particles_fbo_logic.shader


 // 1-2 ghost // 3-zombi // 4-18 blocks // +20 is on fire // 40 is bullet(right) 41 left 42 top 43 down 

pixel penyimpanan data [pos.x, pos.y, [0xffff-vel.x, 0xff-data1],[0xffff-vel.y, 0xff-data2]]
data1 adalah tipe, data2 adalah HP atau timer.


Timer berjalan dalam bingkai di setiap partikel , nilai maksimum timer adalah 255, saya tidak perlu begitu banyak, saya hanya menggunakan maksimum 1-16 ( 0xf ), dan 0xf tetap tidak digunakan di mana misalnya Anda dapat menyimpan nilai HP nyata, itu tidak digunakan untuk saya. ( 0xf , saya menggunakan 0xff untuk timer , tetapi sebenarnya saya hanya memiliki kurang dari 16 frame animasi, dan 0xf cukup, tetapi saya tidak memerlukan data tambahan)
Sebenarnya 0xff digunakan hanya pada penghitung waktu pembakaran pohon, mereka berubah menjadi zombie setelah 255 frame. Logika pengatur waktu sebagian dalam type_hp_logic dalam shader framebuffer partikel (tautan di atas).


Contoh operasi tabrakan dua arah ketika bola api padam pada serangan pertama, dan objek yang terkena juga melakukan aksinya.


File shaders / scene2 /icles_fbo_logic.shader baris 438:


 if (((real_index == 40) || (real_index == 41) || (real_index == 42) || (real_index == 43)) && (type_hp.y > 22)) { int h_id = get_id(fragCoord + vec2(float(x), float(y))); ivec2 htype_hp = unpack_type_hp(h_id); int hreal_index = htype_hp.x; if ((hreal_index != 40) && (hreal_index != 41) && (hreal_index != 42) && (hreal_index != 43)) type_hp.y = 22; } else { if (!need_upd) { int h_id = get_id(fragCoord + vec2(float(x), float(y))); ivec2 htype_hp = unpack_type_hp(h_id); int hreal_index = htype_hp.x; if (((hreal_index == 40) || (hreal_index == 41) || (hreal_index == 42) || (hreal_index == 43)) && (htype_hp.y > 22)) { need_upd = true; } } } 

real_index adalah tipe, tipe tercantum di atas, 40-43 adalah bola api .
lanjut type_hp.y > 22 adalah nilai dari timer, jika lebih besar dari 22 maka bola api tidak menemukan apa pun.
h_id = get_id(... ambil nilai tipe dan HP (timer) dari partikel yang ditemukan
hreal_index != 40... jenis yang diabaikan ( bola api lainnya)
type_hp.y = 22 timer diatur ke 22, ini merupakan indikator bahwa bola api ini bertabrakan dengan satu objek.
else { if (!need_upd) variabel need_upd memeriksa bahwa tidak ada tabrakan berulang, karena fungsinya dalam satu lingkaran, kita menemukan satu bola api .
h_id = get_id(... jika belum ada tabrakan, kami mengambil objek bertipe dan timer.
hreal_index == 40...htype_hp.y > 22 bahwa objek tabrakan adalah bola api dan tidak padam.
need_upd = true flag bahwa perlu memperbarui jenis karena menemukan bola api .


baris selanjutnya 481
if((need_upd)&&(real_index<24)){ real_index <24 berdasarkan jenis kurang dari 24 ada pohon zombie dan hantu yang tidak terbakar, dan kemudian dalam kondisi ini kami memperbarui jenisnya tergantung pada jenis saat ini.


Dengan demikian, hampir semua interaksi objek dapat dilakukan.


Interaksi dengan pemain:


File shaders / scene2 / logic.shader baris 143 berfungsi player_collision


Logika ini membaca piksel di sekitar pemain dalam radius 4x4 piksel, mengambil posisi masing-masing piksel dan membandingkannya dengan posisi pemain, jika elemen ditemukan kemudian ketik pengecekan berikutnya, jika ini monster maka kita ambil HP dari pemain.


Ini berfungsi sedikit tidak akurat dan saya tidak ingin memperbaikinya , fungsi ini dapat dibuat lebih akurat.


Partikel mendorong menjauh dari pemain dan efek tolakan selama serangan:


Framebuffer (viewport) digunakan untuk menulis normal dari aksi saat ini, dan partikel ( Partikel_fbo_logic.shader ) mengambil tekstur ini (dari normal) di posisinya dan menerapkan nilai pada kecepatan dan posisinya. Seluruh kode logika ini secara harfiah hanya beberapa baris, file force_collision.shader


Pada klik tombol kiri mouse, peluru fireball terbang, penampilan mereka tidak terlalu alami , mereka tidak memperbaiki dan pergi dalam bentuk ini.


Anda dapat membuat zona normal (bentuk) untuk memijah partikel dengan pergeseran yang muncul relatif terhadap pemain (ini tidak dilakukan).
Atau Anda dapat menjadikan bola api sebagai objek terpisah sebagai pemain dan menggambar normal menjadi penyangga untuk mendorong partikel menjauh dari bola api , yaitu, dengan analogi dengan pemain ...
Siapa yang perlu berpikir mereka akan mencari tahu sendiri.


3. Tautan ke grafik yang digunakan dengan opengameart, dan shadow shader


Saya diberi tautan ke artikel di cyberleninka.ru
Di mana deskripsi algoritma yang saya gunakan, mungkin ada deskripsi yang lebih rinci dan benar daripada di artikel saya ini.


Shadow shader bekerja sangat sederhana, berdasarkan pada shader ini https://www.shadertoy.com/view/XsK3RR (Saya memiliki kode yang dimodifikasi)
Shader Membangun 1D Radial Lightmap



dan shading di lantai lukisan kode shader / scene2 / mainImage.shader


Tautan ke grafik yang digunakan , semua grafik dalam game dari situs https://opengameart.org
fireball https://opengameart.org/content/animated-traps-and-obstacles
karakter https://opengameart.org/content/legend-of-faune
pohon dan blok https://opengameart.org/content/lolly-set-01
(dan beberapa foto lagi dengan opengameart)


Grafik dalam menu diperoleh oleh 2D_GI shader, sebuah utilitas untuk membuat menu seperti itu:



Siapa yang membaca sampai akhir - dilakukan dengan baik :)
Jika Anda memiliki pertanyaan, tanyakan, saya dapat melengkapi deskripsi berdasarkan permintaan.

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


All Articles