Terikat GPU. Bagian Dua Hutan tanpa akhir



Di hampir setiap gim, perlu mengisi level gim dengan benda-benda yang menciptakan kekayaan visual, keindahan, dan variabilitas dunia virtual. Ambil game dunia terbuka apa saja. Di sana pohon, rumput, tanah, dan air adalah "tempat penampung" utama dari gambar. Hari ini akan ada sangat sedikit GPGPU, tetapi saya akan mencoba memberi tahu Anda cara menggambar banyak pohon dan batu di bingkai ketika Anda tidak bisa, tetapi benar-benar ingin.

Harus segera dicatat bahwa kita memiliki studio indie kecil, dan kita sering tidak memiliki sumber daya untuk menggambar dan memodelkan setiap hal kecil. Karenanya persyaratan untuk berbagai subsistem menjadi "superstruktur" di atas fungsionalitas mesin siap pakai. Jadi itu di artikel pertama dari siklus tentang animasi (di sana kami menggunakan dan mempercepat sistem animasi Persatuan selesai), jadi itu akan ada di sini. Ini sangat menyederhanakan pengenalan fungsionalitas baru ke dalam game (lebih sedikit belajar, lebih sedikit bug, dll).

Jadi, tugasnya: Anda harus menggambar banyak hutan. Gim kami memiliki strategi waktu-nyata (RTS) dengan tingkat besar (30x30 km), dan ini menetapkan persyaratan dasar untuk sistem render:

  • Dengan bantuan minimap, kami dapat langsung mentransfer ke titik mana pun di level. Dan data tentang objek untuk posisi baru harus siap. Kami tidak dapat mengandalkan pemuatan sumber daya setelah beberapa waktu di game FPS atau TPS.
  • Objek pada tingkat besar seperti itu membutuhkan jumlah yang sangat besar. Ratusan ribu, kalau tidak jutaan.
  • Sekali lagi, level yang besar membuatnya sangat panjang dan sulit untuk secara manual mengatur "hutan". Generasi prosedural hutan, batu dan semak-semak diperlukan, tetapi dengan kemungkinan penyesuaian manual dan pengaturan di tempat-tempat utama dari tingkat permainan.

Bagaimana mengatasi masalah ini? Sejumlah objek yang diatur biasa dari unit masih tidak akan menarik. Kami akan mati dalam pemusnahan dan batching. Rendering dimungkinkan menggunakan instancing. Penting untuk menulis sistem kontrol. Pohon harus dimodelkan. Sistem animasi pohon perlu dilakukan. Ooh Saya menginginkannya dengan indah dan segera. Ada SpeedTree, tetapi tidak ada api untuk animasi, tampilan papan reklame mengerikan, karena tidak ada "papan reklame horisontal" dan dokumentasinya buruk. Tetapi kapan ini menghentikan kita? Kami akan mengoptimalkan rendering SpeedTree.

Rendering


Mari kita lihat dari awal jika semuanya sangat buruk dengan objek speedtree biasa:



Berikut adalah sekitar 2.000 pohon di atas panggung. Semuanya sesuai dengan render, di sana instancing menggabungkan pohon-pohon ke dalam batch, tetapi dengan CPU semuanya buruk. Separuh dari waktu rendering kamera adalah pendinginan. Dan kita membutuhkan ratusan ribu. Kami pasti menolak GameObjects, tetapi sekarang kami perlu mengungkap struktur model SpeedTree, mekanisme untuk beralih LOD dan melakukan semuanya dengan pegangan.

Pohon SpeedTree terdiri dari beberapa LOD (biasanya 4), yang terakhir adalah papan iklan, dan yang lainnya adalah geometri dengan berbagai tingkat detail. Masing-masing terdiri dari beberapa sabmesh, dengan bahannya sendiri:


Ini bukan kekhususan dari SpeedTree. Setiap struktur dapat memiliki struktur seperti itu. LOD switching diimplementasikan dalam dua mode yang tersedia:

  1. Cross Fade:

  2. Pohon kecepatan:


CrossFade (dalam hal Unity shaders didefinisikan oleh LOD_FADE_CROSSFADE yang didefinisikan oleh preprocessor) adalah metode switching LOD utama untuk objek pemandangan mana pun dengan beberapa tingkat detail. Ini terdiri dari fakta bahwa ketika LOD diubah, mesh yang seharusnya menghilang tidak hanya menghilang (lompatan kualitas model akan terlihat jelas), tetapi "larut" pada layar menggunakan dithering . Efek sederhana, dan menghindari penggunaan transparansi sejati (alpha blending). Model yang akan muncul dengan cara yang persis sama "muncul" di layar.

SpeedTree (LOD_FADE_PERCENTAGE) dibuat khusus untuk pohon. Selain koordinat utama, koordinat tambahan dari posisi simpul junior sehubungan dengan tingkat LOD saat ini dicatat dalam geometri daun, cabang dan batang. Tingkat transisi dari satu level ke level lainnya adalah nilai bobot untuk interpolasi linier dari kedua posisi ini. Pindah ke / dari papan iklan dilakukan menggunakan metode CrossFade.

Pada prinsipnya, ini yang perlu Anda ketahui untuk menerapkan sistem switching LOD Anda sendiri. Rendernya sendiri sederhana. Kami melewati semua jenis pohon, melintasi semua LOD, dan melintasi semua sabmesh dari masing-masing LOD. Kami memasang bahan yang sesuai, dan menggambar semua instance dari objek ini dalam satu gerakan menggunakan instancing. Dengan demikian, jumlah DrawCalls sama dengan jumlah objek unik dalam adegan. Bagaimana kita tahu cara menggambar? Ini akan membantu kita

Generator Hutan


Pendaratan itu sendiri sederhana dan bersahaja. Untuk setiap jenis pohon, kami membagi dunia menjadi paha depan sehingga setiap pohon cocok dengan satu pohon. Kami memeriksa semua paha depan dan memeriksa topeng formulir:



pada tingkat ini, apakah mungkin menanam pohon di sini? Topeng, dengan tempat "berhutan", ditarik oleh desainer tingkat. Awalnya semuanya ada di CPU dan C #. Generator bekerja lambat, dan ukuran levelnya meningkat sehingga menunggu regenerasi selama beberapa puluh menit menjadi stres. Diputuskan untuk mentransfer generator ke GPU dan Compute shader. Di sini juga, semuanya sederhana. Kita membutuhkan peta ketinggian tanah, topeng penanaman pohon dan AppendStructuredBuffer, tempat kita menambahkan pohon yang dihasilkan (posisi dan ID, itu saja datanya).

Diatur oleh pohon tangan pada titik-titik utama, skrip khusus menarik ke array umum dan menghapus objek asli dari tempat kejadian.

Pengalihan Culling & LOD


Mengetahui posisi dan jenis pohon tidak cukup untuk membuat rendering yang efektif. Adalah perlu untuk menentukan setiap frame objek mana yang terlihat dan LOD mana (mempertimbangkan logika transisi) untuk dikirim ke render.

Shader komputasi khusus juga akan melakukan ini. Untuk setiap objek, Frustum Culling pertama kali dilakukan:


Jika objek terlihat, maka logika switching LOD dijalankan. Menurut ukuran pada layar, kami menentukan tingkat LOD yang diinginkan. Jika mode CrossFade diatur untuk LOD grup, maka kami menambah waktu transisi untuk dithering. Jika SpeedTree Persentase, maka kami mempertimbangkan nilai transisi yang dinormalisasi antara LOD.

API grafis modern memiliki fungsi luar biasa yang memungkinkan informasi pengiriman draw untuk diteruskan ke draw draw di compute buffer (misalnya, ID3D11DeviceContext :: DrawIndexedInstancedIndirect for D3D11). Ini berarti Anda dapat mengisi buffer komputasi ini di GPU juga. Jadi ternyata membuat sistem sepenuhnya CPU independen (baik, sebut saja Graphics.DrawMeshInstancedIndirect). Dalam kasus kami, hanya perlu mencatat jumlah instance dari setiap sabmesh. Sisa informasi (jumlah indeks dalam mesh dan offset) statis.

Compute buffer, dengan argumen untuk draw draw, dibagi menjadi beberapa bagian, yang masing-masing bertanggung jawab untuk memanggil rendering dari submesh-nya. Dalam penghitung shader untuk jala yang akan ditarik dalam bingkai saat ini, tambahkan nilai InstanceCount yang sesuai.

Begini tampilannya di render:


Pemusnahan oklusi GPU adalah langkah berikutnya yang jelas, tetapi untuk RTS dengan kamera seperti itu, dan bukit yang tidak terlalu besar, kemenangannya tidak begitu jelas (dan ini untuk mereka yang tertarik). Saya belum melakukannya.

Agar semuanya dapat digambar dengan benar, Anda perlu mengubah sedikit SpeedTree shaders untuk mengambil posisi dan nilai untuk transisi antara LOD dari buffer komputasi yang sesuai.

Sekarang kita menggambar pohon yang indah tapi statis. Dan pohon-pohon SpeedTree secara realistis dipengaruhi oleh angin, menghidupkannya. Seluruh logika animasi tersebut ada di file SpeedTreeWind.cginc, tetapi tidak ada dokumentasi atau akses ke parameter internal dari Unity.

CBUFFER_START(SpeedTreeWind) float4 _ST_WindVector; float4 _ST_WindGlobal; float4 _ST_WindBranch; float4 _ST_WindBranchTwitch; float4 _ST_WindBranchWhip; float4 _ST_WindBranchAnchor; float4 _ST_WindBranchAdherences; float4 _ST_WindTurbulences; float4 _ST_WindLeaf1Ripple; float4 _ST_WindLeaf1Tumble; float4 _ST_WindLeaf1Twitch; float4 _ST_WindLeaf2Ripple; float4 _ST_WindLeaf2Tumble; float4 _ST_WindLeaf2Twitch; float4 _ST_WindFrondRipple; float4 _ST_WindAnimation; CBUFFER_END 

Bagaimana kita memilihnya? Untuk melakukan ini, untuk setiap jenis pohon, kami akan membuat objek SpeedTree asli di suatu tempat di tempat yang tak terlihat (atau lebih tepatnya, terlihat di Unity, tetapi tidak terlihat di kamera, jika tidak parameter tidak akan diperbarui). Ini dapat dicapai dengan meningkatkan kotak pembatas, dan menempatkan objek di belakang kamera). Setiap frame dihapus set nilai yang diinginkan menggunakan material. GetVector (...).

Jadi, pohon-pohon bergetar tertiup angin, tetapi tampilan papan reklame yang tertekan:


Dengan opsi shader, BILLBOARD_FACE_CAMERA_POS lebih buruk lagi:


Kami membutuhkan papan iklan horisontal (atas-bawah). Ini adalah fitur SpeedTree standar sejak zaman King Pea, tetapi dilihat dari forum, masih belum diterapkan di Unity. Posting dari forum resmi SpeedTree: "Integrasi Unity tidak pernah menggunakan papan iklan horisontal." Kami akan mengencangkan tangan kami. Geometri itu sendiri mudah dibuat. Bagaimana cara mengetahui koordinat UV dari sprite di atlas untuknya?


Kami mendapatkan SpeedTreeRT SDK lama, dan kami menemukan struktur dalam dokumentasi:

 struct SBillboard { bool m_bIsActive; const float* m_pTexCoords; const float* m_pCoords; float m_fAlphaTestValue; }; 

"M_pTexCoords menunjuk ke set 4 (s, t) koordinat tekstur yang menentukan gambar yang digunakan pada papan iklan. m_pTexCoords berisi 8 entri. ”, katanya dalam bahasa asing. Nah, kita akan mencari urutan 4 nilai floating point dalam file biner spm, yang masing-masing terletak pada kisaran [0..1]. Dengan metode poking ilmiah, kami menemukan bahwa urutan yang diinginkan ada di depan blok 12 float dengan tanda-tanda yang sesuai dengan pola:

 float signs[] = { -1, 1, -1, 1, 1, -1, 1, 1, 1, -1, 1, 1 }; 

Kami menulis utilitas konsol kecil pada pro, yang beralih ke semua file spm, dan mencari koordinat uv untuk papan reklame horisontal di dalamnya. Outputnya seperti label CSV:

 Azalea_Desktop.spm: 0, 1, 0.333333, 1, 0.333333, 0.666667, 0, 0.666667, Azalea_Desktop_Flowers_1.spm: 0, 1, 0.333333, 1, 0.333333, 0.666667, 0, 0.666667, Azalea_Desktop_Flowers_2.spm: 0, 1, 0.333333, 1, 0.333333, 0.666667, 0, 0.666667, Leaf_Map_Maker_Desktop_1_Modeler_Use_Only.spm: Pattern not found! Leaf_Map_Maker_Desktop_2_Modeler_Use_Only.spm: Pattern not found! BarrelCactus_Cluster_Desktop_1.spm: 0, 0.592376, 0.407624, 0.592376, 0.407624, 0.184752, 0, 0.184752, BarrelCactus_Cluster_Desktop_2.spm: 0, 1, 0.499988, 1, 0.499988, 0.500012, 0, 0.500012, BarrelCactus_Desktop_1.spm: 0, 0.2208, 0.220748, 0.2208, 0.220748, 5.29885e-05, 0, 5.29885e-05, BarrelCactus_Desktop_2.spm: 0, 1, 0.301392, 1, 0.301392, 0.698608, 0, 0.698608, 

Untuk menetapkan koordinat tekstur ke geometri papan iklan horizontal, kami menemukan catatan yang diinginkan dan menguraikannya.

Sekarang seperti ini:


Masih tidak terlalu. Menggunakan ambang uji alfa, kita akan memudar papan reklame vertikal, dalam rekaman dari sudut ke kamera:



Ringkasan

Profiler yang menampilkan statistik dinamis (berapa banyak yang dirender) dan statis (berapa banyak objek dan parameternya ada di lokasi):


Nah, video indah terakhir (babak kedua menunjukkan pergantian tingkat kualitas):


Apa yang kita miliki pada akhirnya:

  • Sistem ini sepenuhnya independen CPU.
  • Ini bekerja dengan cepat.
  • Ini menggunakan aset SpeedTree yang sudah jadi, yang dapat Anda beli di Internet.
  • Tentu saja, saya berteman dengan LODGroup mana pun, bukan hanya SpeedTree. Begitu banyak kerikil sekarang juga dimungkinkan.

Di antara kekurangan dapat dicatat kurangnya penyisihan penyumbatan dan billboard masih sangat tidak ekspresif.

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


All Articles