Pipa Rendering Engine: Membuat shader generik

10

Saya mencoba membuat mesin game 2D menggunakan OpenGL ES 2.0 (iOS untuk saat ini). Saya telah menulis layer Aplikasi pada Objective C dan RendererGLES20 yang terpisah berisi C ++. Tidak ada panggilan khusus GL yang dilakukan di luar renderer. Ini bekerja dengan sempurna.

Tapi saya punya beberapa masalah desain saat menggunakan shader. Setiap shader memiliki atribut dan seragam uniknya sendiri yang perlu ditetapkan tepat sebelum panggilan undian utama (glDrawArrays dalam kasus ini). Misalnya, untuk menggambar beberapa geometri yang akan saya lakukan:

void RendererGLES20::render(Model * model)
{
    // Set a bunch of uniforms
    glUniformMatrix4fv(.......);
    // Enable specific attributes, can be many
    glEnableVertexAttribArray(......);
    // Set a bunch of vertex attribute pointers:
    glVertexAttribPointer(positionSlot, 2, GL_FLOAT, GL_FALSE, stride, m->pCoords);

    // Now actually Draw the geometry
    glDrawArrays(GL_TRIANGLES, 0, m->vertexCount);

    // After drawing, disable any vertex attributes:
    glDisableVertexAttribArray(.......);
}

Seperti yang Anda lihat kode ini sangat kaku. Jika saya menggunakan shader lain, katakan efek riak, saya akan perlu untuk lulus seragam tambahan, attribs vertex dll. Dengan kata lain saya harus mengubah kode sumber rendererGLES20 render hanya untuk memasukkan shader baru.

Apakah ada cara untuk membuat objek shader benar-benar generik? Seperti Bagaimana jika saya hanya ingin mengubah objek shader dan tidak khawatir tentang kompilasi ulang sumber game? Adakah cara untuk membuat agnostik penyaji seragam dan atribut dll ?. Meskipun kita perlu meneruskan data ke seragam, apa tempat terbaik untuk melakukan itu? Kelas model? Apakah kelas model mengetahui seragam dan atribut khusus shader?

Berikut ini menunjukkan kelas Aktor:

class Actor : public ISceneNode
{
    ModelController * model;
    AIController * AI;
};

Kelas pengendali model: kelas ModelController {class IShader * shader; int teksturId; warna vec4; float alpha; struct Vertex * vertexArray; };

Kelas Shader hanya berisi objek shader, kompilasi dan menautkan sub-rutinitas dll.

Di kelas Game Logic, saya sebenarnya merender objek:

void GameLogic::update(float dt)
{
    IRenderer * renderer = g_application->GetRenderer();

    Actor * a = GetActor(id);
    renderer->render(a->model);
}

Harap dicatat bahwa meskipun Actor memperluas ISceneNode, saya belum mulai mengimplementasikan SceneGraph. Saya akan melakukannya segera setelah saya menyelesaikan masalah ini.

Ada ide bagaimana cara memperbaikinya? Pola desain terkait dll?

Terima kasih sudah membaca pertanyaan.

fakhir
sumber
kemungkinan duplikat dari Desain Mesin Game - Ubershader - Desain manajemen Shader
Sean Middleditch
Tidak yakin apakah itu duplikat yang tepat, tetapi itu sesuai dengan apa yang Anda coba lakukan, saya pikir.
Sean Middleditch

Jawaban:

12

Dimungkinkan untuk membuat sistem shader Anda lebih digerakkan oleh data, sehingga Anda tidak memiliki begitu banyak kode khusus shader untuk format seragam dan verteks, melainkan mengaturnya secara terprogram berdasarkan metadata yang melekat pada shader.

Pertama, penafian: sistem yang digerakkan oleh data dapat membuatnya lebih mudah untuk menambahkan shader baru, tetapi di sisi lain muncul dengan biaya dalam hal meningkatnya kompleksitas sistem, yang membuatnya lebih sulit untuk dipertahankan dan didebug. Jadi itu ide yang baik untuk memikirkan dengan hati-hati tentang seberapa banyak data-drivenness akan baik untuk Anda (untuk proyek kecil, jawabannya mungkin "tidak ada"), dan jangan mencoba membangun sistem yang terlalu digeneralisasi.

Oke, mari kita bicara tentang format vertex (atribut) terlebih dahulu. Anda dapat membuat deskripsi data dengan membuat struct yang berisi data untuk diteruskan ke glVertexAttribPointer- indeks, jenis, ukuran, dll dari atribut tunggal - dan memiliki array struct tersebut untuk mewakili seluruh format vertex. Diberikan informasi ini, Anda dapat secara terprogram mengatur semua status GL terkait dengan atribut vertex.

Dari mana data untuk mengisi deskripsi ini berasal? Secara konseptual, saya pikir cara terbersih adalah memilikinya milik shader. Saat Anda membuat data vertex untuk sebuah mesh, Anda akan mencari shader mana yang digunakan pada mesh tersebut, menemukan format vertex yang diperlukan oleh shader itu, dan membangun buffer vertex yang sesuai. Anda hanya perlu beberapa cara untuk setiap shader untuk menentukan format vertex yang diharapkan.

Ada berbagai cara untuk melakukan ini; misalnya, Anda dapat memiliki sekumpulan nama standar untuk atribut di shader ("attrPosition", "attrNormal", dll.) ditambah beberapa aturan kode keras seperti "position is 3 floats". Kemudian Anda menggunakan glGetAttribLocationatau mirip dengan permintaan atribut mana yang digunakan shader, dan menerapkan aturan untuk membangun format vertex. Cara lain adalah dengan membuat potongan XML yang mendefinisikan format, tertanam dalam komentar di sumber shader dan diekstraksi oleh alat Anda, atau sesuatu di sepanjang baris itu.

Untuk seragam, jika Anda dapat menggunakan OpenGL 3.1 atau lebih baru itu ide yang baik untuk menggunakan objek buffer seragam (setara OpenGL dari buffer konstan D3D). Sayangnya, GL ES 2.0 tidak memilikinya, jadi seragam harus ditangani secara individual. Salah satu cara untuk melakukannya adalah dengan membuat struct yang berisi lokasi yang seragam untuk setiap parameter yang ingin Anda atur - matriks kamera, daya spekuler, matriks dunia, dll. Lokasi-lokasi sampler bisa juga ada di sini. Pendekatan ini bergantung pada adanya seperangkat parameter standar yang dibagikan di semua shader. Tidak setiap shader harus menggunakan setiap parameter tunggal, tetapi semua parameter harus ada di struct ini.

Setiap shader akan memiliki instance dari struct ini, dan ketika Anda memuat shader Anda akan menanyakannya untuk lokasi semua parameter, menggunakan glGetUniformLocationdan nama standar. Kemudian kapan pun Anda perlu mengatur seragam dari kode, Anda dapat memeriksa apakah itu ada di shader itu, dan hanya mencari lokasinya dan mengaturnya.

Nathan Reed
sumber