Desain Mesin Game - Ubershader - Desain manajemen Shader [ditutup]

18

Saya ingin menerapkan sistem Ubershader yang fleksibel, dengan naungan yang ditangguhkan. Gagasan saya saat ini adalah untuk membuat shader dari modul, yang berhubungan dengan fitur-fitur tertentu, seperti FlatTexture, BumpTexture, Pemetaan Displacement, dll. Ada juga beberapa modul kecil yang menerjemahkan kode warna, melakukan pemetaan nada, dll. Ini memiliki keuntungan yang saya dapat ganti jenis modul tertentu jika GPU tidak mendukungnya, jadi saya dapat beradaptasi dengan kemampuan GPU saat ini. Saya tidak yakin apakah desain ini bagus. Saya khawatir saya bisa membuat pilihan desain yang buruk, sekarang, dan kemudian membayarnya.

Pertanyaan saya adalah di mana saya menemukan sumber daya, contoh, artikel tentang bagaimana menerapkan sistem manajemen shader secara efektif? Adakah yang tahu bagaimana mesin permainan besar melakukan ini?

Michael Staud
sumber
3
Tidak cukup lama untuk jawaban nyata: Anda akan baik-baik saja dengan pendekatan ini jika Anda memulai dari yang kecil dan membiarkannya tumbuh secara organik didorong oleh kebutuhan Anda alih-alih mencoba membangun MegaCity-One dari shader di depan. Pertama, Anda mengurangi kekhawatiran terbesar Anda untuk melakukan desain terlalu banyak di muka dan membayarnya nanti jika tidak berhasil, kedua Anda menghindari melakukan pekerjaan ekstra yang tidak pernah digunakan.
Patrick Hughes
Sayangnya, kami tidak lagi menerima pertanyaan "permintaan sumber daya".
Gnemlock

Jawaban:

23

Pendekatan semi-umum adalah membuat apa yang saya sebut komponen shader , mirip dengan apa yang saya pikir Anda panggil modul.

Idenya mirip dengan grafik pasca pemrosesan. Anda menulis potongan kode shader yang mencakup input yang diperlukan, output yang dihasilkan, dan kemudian kode untuk benar-benar bekerja padanya. Anda memiliki daftar yang menunjukkan shader mana yang harus diterapkan dalam situasi apa pun (apakah bahan ini membutuhkan komponen pemetaan bump, apakah komponen yang ditunda atau maju diaktifkan, dll.).

Anda sekarang dapat mengambil grafik ini dan menghasilkan kode shader darinya. Ini sebagian besar berarti "menempelkan" kode chunks 'ke tempatnya, dengan grafik telah memastikan mereka sudah dalam urutan yang diperlukan, dan kemudian menempelkan input / output shader yang sesuai (dalam GLSL, ini berarti mendefinisikan "global" Anda di , variabel keluar, dan seragam).

Ini tidak sama dengan pendekatan ubershader. Ubershaders adalah tempat Anda meletakkan semua kode yang diperlukan untuk semuanya menjadi satu set shader, mungkin menggunakan #ifdefs dan seragam dan sejenisnya untuk menghidupkan dan mematikan fitur ketika menyusun atau menjalankannya. Saya pribadi membenci pendekatan ubershader, tetapi beberapa mesin AAA yang agak mengesankan menggunakannya (Crytek khususnya datang ke pikiran).

Anda dapat menangani potongan shader dengan beberapa cara. Cara paling maju - dan berguna jika Anda berencana untuk mendukung GLSL, HLSL, dan konsol - adalah dengan menulis parser untuk bahasa shader (mungkin sedekat mungkin dengan HLSL / Cg atau GLSL yang Anda bisa untuk "dimengerti" oleh para pengembang Anda. ) yang kemudian dapat digunakan untuk terjemahan sumber-ke-sumber. Pendekatan lain adalah dengan hanya membungkus potongan shader dalam file XML atau sejenisnya, misalnya

<shader name="example" type="pixel">
  <input name="color" type="float4" source="vertex" />
  <output name="color" type="float4" target="output" index="0" />
  <glsl><![CDATA[
     output.color = vec4(input.color.r, 0, 0, 1);
  ]]></glsl>
</shader>

Catatan dengan pendekatan itu Anda bisa membuat beberapa bagian kode untuk API yang berbeda atau bahkan versi bagian kode (sehingga Anda dapat memiliki versi GLSL 1.20 dan versi GLSL 3.20). Grafik Anda bahkan dapat secara otomatis mengecualikan potongan shader yang tidak memiliki bagian kode yang kompatibel sehingga Anda bisa mendapatkan degradasi semi-anggun pada perangkat keras yang lebih lama (jadi sesuatu seperti pemetaan normal atau apa pun yang hanya dikecualikan pada perangkat keras yang lebih tua yang tidak dapat mendukungnya tanpa programmer perlu lakukan banyak pemeriksaan eksplisit).

Sampel XMl kemudian dapat menghasilkan sesuatu yang mirip dengan (permintaan maaf jika ini adalah GLSL tidak valid, sudah lama sejak saya mengalami sendiri API itu):

layout (location=0) in vec4 input_color;
layout (location=0) out vec4 output_color;

struct Input {
  vec4 color;
};
struct Output {
  vec4 color;
}

void main() {
  Input input;
  input.color = input_color;
  Output output;

  // Source: example.shader
#line 5
  output.color = vec4(input.color.r, 0, 0, 1);

  output_color = output.color;
}

Anda bisa menjadi sedikit lebih pintar dan menghasilkan lebih banyak kode "efisien", tetapi jujur ​​setiap kompiler shader yang bukan total omong kosong akan menghapus redundansi dari kode yang dihasilkan untuk Anda. Mungkin GLSL yang lebih baru memungkinkan Anda memasukkan nama file ke #lineperintah sekarang juga, tetapi saya tahu versi yang lebih lama sangat kurang dan tidak mendukungnya.

Jika Anda memiliki beberapa chunk, input mereka (yang tidak disediakan sebagai output oleh leluhur chunk di pohon) disatukan ke dalam blok input, seperti output, dan kode baru saja digabungkan. Sedikit kerja ekstra dilakukan untuk memastikan tahap cocok (vertex vs fragmen) dan bahwa tata letak input atribut vertex "hanya bekerja". Manfaat lain yang bagus dengan pendekatan ini adalah bahwa Anda dapat menulis indeks pengikat atribut seragam dan input eksplisit yang tidak didukung dalam versi GLSL yang lebih lama dan menangani ini di pustaka shader generation / binding Anda. Anda juga dapat menggunakan metadata dalam mengatur VBO dan glVertexAttribPointerpanggilan Anda untuk memastikan kompatibilitas dan bahwa semuanya "berfungsi".

Sayangnya tidak ada perpustakaan lintas-API yang baik seperti ini. Cg agak dekat, tetapi ia memiliki dukungan omong kosong untuk OpenGL pada kartu AMD dan bisa sangat lambat jika Anda menggunakan salah satu kecuali fitur pembuatan kode paling dasar. Kerangka kerja efek DirectX juga berfungsi, tetapi tentu saja tidak memiliki dukungan untuk bahasa apa pun selain HLSL. Ada beberapa pustaka yang tidak lengkap / bermasalah untuk GLSL yang meniru pustaka DirectX tetapi memberi status mereka terakhir kali saya memeriksa saya hanya akan menulis sendiri.

Pendekatan ubershader hanya berarti mendefinisikan arahan preprocessor "terkenal" untuk fitur-fitur tertentu dan kemudian mengkompilasi ulang untuk bahan yang berbeda dengan konfigurasi yang berbeda. mis., untuk materi apa pun dengan peta normal, Anda dapat menentukan USE_NORMAL_MAPPING=1dan kemudian di ubershader tahap-pixel Anda cukup:

#if USE_NORMAL_MAPPING
  vec4 normal;
  // all your normal mapping code
#else
  vec4 normal = normalize(in_normal);
#endif

Masalah besar di sini adalah menangani ini untuk HLSL yang telah dikompilasi, di mana Anda perlu mengkompilasi semua kombinasi yang digunakan. Bahkan dengan GLSL Anda harus dapat menghasilkan kunci dari semua arahan preprosesor dengan benar untuk menghindari kompilasi / caching shader identik. Menggunakan seragam dapat mengurangi kerumitan tetapi tidak seperti seragam preprosesor tidak mengurangi jumlah instruksi dan masih dapat berdampak kecil pada kinerja.

Untuk lebih jelasnya, kedua pendekatan (dan juga hanya menulis satu ton variasi shader secara manual) semuanya digunakan dalam ruang AAA. Gunakan mana yang paling cocok untuk Anda.

Sean Middleditch
sumber