Memperbarui tekstur sendiri
Ketika dimungkinkan untuk memparalelkan simulasi atau memberikan tugas, biasanya yang terbaik adalah menjalankannya dalam GPU. Pada artikel ini, saya akan menjelaskan teknik yang menggunakan fakta ini untuk membuat trik visual yang mengesankan dengan overhead kinerja rendah. Semua efek yang akan saya tunjukkan diimplementasikan menggunakan tekstur yang, ketika diperbarui, "
render yourself "; tekstur diperbarui ketika bingkai baru diberikan, dan keadaan tekstur berikutnya sepenuhnya tergantung pada keadaan sebelumnya. Pada tekstur ini, Anda dapat menggambar, menyebabkan perubahan tertentu, dan tekstur itu sendiri, secara langsung atau tidak langsung, dapat digunakan untuk membuat animasi yang menarik. Saya menyebutnya
tekstur konvolusional .
Gambar 1: konvolusi buffering gandaSebelum melanjutkan, kita perlu menyelesaikan satu masalah: tekstur tidak dapat dibaca dan ditulis pada saat yang sama, API grafik seperti OpenGL dan DirectX tidak mengizinkan ini. Karena keadaan tekstur selanjutnya tergantung pada yang sebelumnya, kita perlu mengatasi keterbatasan ini. Saya perlu membaca dari tekstur yang berbeda, bukan dari yang saya tulis.
Solusinya adalah
buffering ganda . Gambar 1 menunjukkan cara kerjanya: pada kenyataannya, alih-alih satu tekstur, ada dua, tetapi satu ditulis ke dan satu dibaca dari yang lain. Tekstur yang sedang ditulis disebut
buffer belakang , dan tekstur yang diberikan disebut
buffer depan . Karena tes konvolusional adalah "ditulis untuk dirinya sendiri," buffer sekunder di setiap frame menulis ke buffer primer, dan kemudian primer diberikan atau digunakan untuk rendering. Dalam bingkai berikutnya, peran berubah dan buffer primer sebelumnya digunakan sebagai sumber untuk buffer utama berikutnya.
Dengan merender keadaan sebelumnya ke tekstur lilitan baru menggunakan fragmen shader (atau
pixel shader ) memberikan efek dan animasi yang menarik. Shader menentukan bagaimana kondisi berubah. Kode sumber untuk semua contoh dari artikel (dan juga yang lain) dapat ditemukan di
repositori di GitHub .
Contoh aplikasi sederhana
Untuk mendemonstrasikan teknik ini, saya memilih simulasi yang terkenal di mana, ketika memperbarui, keadaan sepenuhnya tergantung pada keadaan sebelumnya:
permainan Conway "Life" . Simulasi ini dilakukan dalam kotak kotak, masing-masing sel yang hidup atau mati. Aturan untuk status sel berikut sederhana:
- Jika sel hidup memiliki kurang dari dua tetangga, tetapi menjadi mati.
- Jika sel hidup memiliki dua atau tiga tetangga yang hidup, ia tetap hidup.
- Jika sel hidup memiliki lebih dari tiga tetangga yang hidup, maka ia menjadi mati.
- Jika sel mati memiliki tiga tetangga yang hidup, itu menjadi hidup.
Untuk mengimplementasikan permainan ini sebagai tekstur konvolusional, saya menafsirkan tekstur sebagai kisi-kisi permainan, dan shader merender berdasarkan aturan di atas. Pixel transparan adalah sel mati, dan piksel putih buram adalah sel hidup. Implementasi interaktif ditunjukkan di bawah ini. Untuk mengakses GPU, saya menggunakan
myr.js , yang membutuhkan
WebGL 2 . Sebagian besar browser modern (misalnya, Chrome dan Firefox) dapat bekerja dengannya, tetapi jika demo tidak berfungsi, maka kemungkinan besar browser tidak mendukungnya. Gunakan mouse (atau layar sentuh) [dalam artikel asli] untuk menggambar sel-sel hidup pada tekstur.
Kode shader fragmen (dalam GLSL, karena saya menggunakan WebGL untuk rendering) ditunjukkan di bawah ini. Pertama, saya menerapkan fungsi
get
, yang memungkinkan saya membaca piksel dari offset tertentu dari yang sekarang. Variabel
pixelSize
adalah vektor 2D standar yang mengandung offset UV dari setiap piksel, dan fungsi
get
menggunakannya untuk membaca sel tetangga. Kemudian fungsi
main
menentukan warna sel baru berdasarkan keadaan saat ini (
live
) dan jumlah tetangga yang hidup.
uniform sampler2D source; uniform lowp vec2 pixelSize; in mediump vec2 uv; layout (location = 0) out lowp vec4 color; int get(int dx, int dy) { return int(texture(source, uv + pixelSize * vec2(dx, dy)).r); } void main() { int live = get(0, 0); int neighbors = get(-1, -1) + get(0, -1) + get(1, -1) + get(-1, 0) + get(1, 0) + get(-1, 1) + get(0, 1) + get(1, 1); if (live == 1 && neighbors < 2) color = vec4(0); else if (live == 1 && (neighbors == 2 || neighbors == 3)) color = vec4(1); else if (live == 1 && neighbors == 3) color = vec4(0); else if (live == 0 && neighbors == 3) color = vec4(1); else color = vec4(0); }
Tekstur konvolusional sederhana lainnya adalah
permainan dengan pasir yang jatuh , di mana pengguna dapat melemparkan pasir berwarna-warni ke TKP, yang jatuh dan membentuk gunung. Meskipun implementasinya sedikit lebih rumit, aturannya lebih sederhana:
- Jika tidak ada pasir di bawah butiran pasir, maka ia jatuh satu piksel ke bawah.
- Jika ada pasir di bawah butiran pasir, tetapi bisa meluncur ke bawah 45 derajat ke kiri atau ke kanan, maka ia akan melakukannya.
Manajemen dalam contoh ini sama dengan di game "Life". Karena di bawah aturan seperti itu, pasir dapat jatuh pada kecepatan hanya satu piksel per bingkai untuk sedikit mempercepat proses, tekstur per bingkai diperbarui tiga kali. Kode sumber aplikasi ada di
sini .
Satu langkah ke depan
Gambar 2: Gelombang Pixel.Contoh di atas menggunakan tekstur konvolusional secara langsung; isinya dirender ke layar sebagaimana adanya. Jika Anda mengartikan gambar hanya sebagai piksel, maka batas penggunaan teknik ini sangat terbatas, tetapi berkat peralatan modern mereka dapat diperluas. Alih-alih menghitung piksel sebagai warna, saya akan menafsirkannya sedikit berbeda, yang dapat digunakan untuk membuat animasi dari tekstur lain atau model 3D.
Pertama, saya akan menafsirkan tekstur convolutional sebagai peta ketinggian. Tekstur akan mensimulasikan
gelombang dan
getaran pada bidang air, dan hasilnya akan digunakan untuk membuat refleksi dan gelombang teduh. Kami tidak lagi diharuskan membaca tekstur sebagai gambar, sehingga kami dapat menggunakan pikselnya untuk menyimpan informasi apa pun. Dalam kasus water shader, saya akan menyimpan tinggi gelombang di saluran merah, dan pulsa gelombang di saluran hijau, seperti yang ditunjukkan pada Gambar 2. Saluran biru dan alfa belum digunakan. Gelombang dibuat dengan menggambar bintik-bintik merah pada tekstur konvolusional.
Saya tidak akan mempertimbangkan metodologi untuk memperbarui peta ketinggian, yang saya pinjam dari situs web
Hugo Elias , yang tampaknya telah menghilang dari Internet. Dia juga belajar tentang algoritma ini dari penulis yang tidak dikenal dan mengimplementasikannya dalam C untuk dieksekusi di CPU. Kode sumber untuk aplikasi di bawah ini ada di
sini .
Di sini saya menggunakan peta ketinggian hanya untuk mengimbangi tekstur dan menambahkan bayangan, tetapi dalam dimensi ketiga, aplikasi yang jauh lebih menarik dapat diimplementasikan. Ketika tekstur konvolusional ditafsirkan oleh shader verteks, bidang datar dibagi dapat didistorsi untuk membuat gelombang tiga dimensi. Anda dapat menerapkan naungan dan pencahayaan biasa ke bentuk yang dihasilkan.
Perlu dicatat bahwa piksel dalam tekstur konvolusional dari contoh yang ditunjukkan di atas terkadang menyimpan nilai yang sangat kecil yang tidak boleh hilang karena kesalahan pembulatan. Oleh karena itu, saluran warna tekstur ini harus memiliki resolusi yang lebih tinggi, dan bukan standar 8 bit. Dalam contoh ini, saya meningkatkan ukuran setiap saluran warna menjadi 16 bit, yang memberikan hasil yang cukup akurat. Jika Anda tidak menyimpan piksel, Anda sering perlu meningkatkan akurasi tekstur. Untungnya, API grafik modern mendukung fitur ini.
Kami menggunakan semua saluran
Gambar 3: Rumput pixel.Dalam contoh air, hanya saluran merah dan hijau yang digunakan, tetapi dalam contoh berikutnya, kami akan menerapkan keempatnya. Bidang dengan rumput (atau pohon) disimulasikan, yang dapat dipindahkan menggunakan kursor. Gambar 3 menunjukkan data apa yang disimpan dalam piksel. Offset disimpan di saluran merah dan hijau, dan kecepatan disimpan di saluran biru dan alfa. Kecepatan ini diperbarui untuk bergeser ke posisi istirahat dengan gerakan gelombang yang secara bertahap memudar.
Dalam contoh dengan air, membuat gelombang cukup sederhana: bintik-bintik dapat digambarkan pada tekstur, dan alpha blending memberikan bentuk yang halus. Anda dapat dengan mudah membuat beberapa tempat yang tumpang tindih. Dalam contoh ini, semuanya lebih rumit karena saluran alfa sudah digunakan. Kami tidak dapat menggambar tempat dengan nilai alfa 1 di tengah dan 0 dari tepi, karena ini akan memberi rumput impuls yang tidak perlu (karena impuls vertikal disimpan di saluran alpha). Dalam hal ini, shader terpisah ditulis untuk menggambarkan efek pada tekstur konvolusional. Shader ini memastikan bahwa alpha blending tidak menghasilkan efek yang tidak terduga.
Kode sumber aplikasi dapat ditemukan di
sini .
Rumput dibuat dalam 2D, tetapi efeknya akan bekerja di lingkungan 3D. Alih-alih perpindahan piksel, simpul digeser, yang juga lebih cepat. Juga, dengan bantuan puncak, efek lain dapat direalisasikan: kekuatan cabang yang berbeda - rumput menekuk dengan mudah dengan sedikit angin, dan pohon yang kuat berfluktuasi hanya selama badai.
Meskipun ada banyak algoritma dan bayangan untuk menciptakan efek angin dan perpindahan vegetasi, pendekatan ini memiliki keuntungan serius: menggambar efek pada tekstur konvolusional adalah proses yang sangat murah. Jika efeknya diterapkan dalam permainan, maka pergerakan vegetasi dapat ditentukan oleh ratusan pengaruh berbeda. Tidak hanya karakter utama, tetapi juga semua benda, hewan, dan gerakan dapat mempengaruhi dunia dengan mengorbankan biaya yang tidak signifikan.
Kasing dan kekurangan penggunaan lainnya
Anda dapat membuat banyak aplikasi teknologi lainnya, misalnya:
- Menggunakan tekstur konvolusional, Anda dapat mensimulasikan kecepatan angin. Pada teksturnya, Anda bisa menggambar rintangan yang membuat udara mengelilinginya. Partikel (hujan, salju, dan daun) dapat menggunakan tekstur ini untuk terbang di sekitar rintangan.
- Anda dapat mensimulasikan penyebaran asap atau api.
- Teksturnya bisa menyandikan ketebalan lapisan salju atau pasir. Jejak dan interaksi lainnya dengan layer dapat membuat penyok dan cetakan pada layer.
Saat menggunakan metode ini, ada kesulitan dan keterbatasan:
- Sulit untuk menyesuaikan animasi dengan mengubah frame rate. Misalnya, dalam aplikasi dengan pasir jatuh, butiran pasir jatuh pada kecepatan konstan - satu piksel per pembaruan. Solusi yang mungkin adalah memperbarui tekstur konvolusional dengan frekuensi konstan, mirip dengan cara kerja sebagian besar mesin fisik; mesin fisika berjalan pada frekuensi konstan, dan hasilnya diinterpolasi.
- Mentransfer data ke GPU adalah proses yang cepat dan mudah, namun mendapatkan kembali data tidak mudah. Ini berarti bahwa sebagian besar efek yang dihasilkan oleh teknik ini searah; mereka ditransfer ke GPU, dan GPU melakukan tugasnya tanpa intervensi dan umpan balik lebih lanjut. Jika saya ingin menanamkan panjang gelombang dari contoh air dalam perhitungan fisik (misalnya, sehingga kapal akan terombang-ambing bersama dengan ombak), maka saya akan membutuhkan nilai dari tekstur konvolusional. Mengambil data tekstur dari GPU adalah proses yang sangat lambat yang tidak perlu dilakukan secara real time. Solusi untuk masalah ini dapat berupa penerapan dua simulasi: satu dengan resolusi tinggi untuk grafik air sebagai tekstur convolutional, yang lain dengan resolusi rendah dalam CPU untuk fisika air. Jika algoritmenya sama, maka perbedaan mungkin cukup dapat diterima.
Demo dalam artikel ini dapat lebih dioptimalkan. Dalam contoh rumput, Anda dapat menggunakan tekstur dengan resolusi jauh lebih rendah tanpa cacat nyata; ini akan banyak membantu dalam adegan besar. Pengoptimalan lain: Anda dapat menggunakan kecepatan refresh yang lebih rendah, misalnya, di setiap frame keempat, atau seperempat per frame (karena teknik ini tidak menyebabkan masalah dengan pembaruan tersegmentasi). Untuk mempertahankan frame rate yang halus, kondisi konvolusional sebelumnya dan saat ini dapat diinterpolasi.
Karena tekstur konvolusional menggunakan buffer ganda internal, Anda dapat menggunakan kedua tekstur pada saat yang sama untuk rendering. Buffer primer adalah keadaan saat ini, dan yang kedua adalah yang sebelumnya. Ini dapat berguna untuk menginterpolasi tekstur dari waktu ke waktu atau untuk menghitung turunan untuk nilai tekstur.
Kesimpulan
GPU, terutama dalam program 2D, sering menganggur. Meskipun tampaknya hanya dapat digunakan dalam rendering adegan 3D yang kompleks, teknik yang ditunjukkan dalam artikel ini menunjukkan setidaknya satu cara lain untuk menggunakan kekuatan GPU. Dengan menggunakan kapabilitas GPU yang dikembangkan, Anda dapat menerapkan efek dan animasi yang menarik yang biasanya terlalu mahal untuk CPU.