Kode rendering saya selalu OpenGL. Saya sekarang perlu mendukung platform yang tidak memiliki OpenGL, jadi saya harus menambahkan lapisan abstraksi yang membungkus OpenGL dan Direct3D 9. Saya akan mendukung Direct3D 11 nanti.
TL; DR: perbedaan antara OpenGL dan Direct3D menyebabkan redundansi untuk programmer, dan tata letak data terasa serpihan.
Untuk saat ini, API saya bekerja sedikit seperti ini. Beginilah cara shader dibuat:
Shader *shader = Shader::Create(
" ... GLSL vertex shader ... ", " ... GLSL pixel shader ... ",
" ... HLSL vertex shader ... ", " ... HLSL pixel shader ... ");
ShaderAttrib a1 = shader->GetAttribLocation("Point", VertexUsage::Position, 0);
ShaderAttrib a2 = shader->GetAttribLocation("TexCoord", VertexUsage::TexCoord, 0);
ShaderAttrib a3 = shader->GetAttribLocation("Data", VertexUsage::TexCoord, 1);
ShaderUniform u1 = shader->GetUniformLocation("WorldMatrix");
ShaderUniform u2 = shader->GetUniformLocation("Zoom");
Sudah ada masalah di sini: begitu Direct3D shader dikompilasi, tidak ada cara untuk menanyakan atribut input dengan namanya; rupanya hanya semantik yang tetap bermakna. Inilah sebabnya mengapa GetAttribLocation
ada argumen ekstra ini, yang disembunyikan di ShaderAttrib
.
Sekarang ini adalah bagaimana saya membuat deklarasi vertex dan dua buffer vertex:
VertexDeclaration *decl = VertexDeclaration::Create(
VertexStream<vec3,vec2>(VertexUsage::Position, 0,
VertexUsage::TexCoord, 0),
VertexStream<vec4>(VertexUsage::TexCoord, 1));
VertexBuffer *vb1 = new VertexBuffer(NUM * (sizeof(vec3) + sizeof(vec2));
VertexBuffer *vb2 = new VertexBuffer(NUM * sizeof(vec4));
Masalah lain: informasi VertexUsage::Position, 0
tersebut sama sekali tidak berguna untuk backend OpenGL / GLSL karena tidak peduli dengan semantik.
Setelah buffer vertex diisi atau diarahkan ke data, ini adalah kode rendering:
shader->Bind();
shader->SetUniform(u1, GetWorldMatrix());
shader->SetUniform(u2, blah);
decl->Bind();
decl->SetStream(vb1, a1, a2);
decl->SetStream(vb2, a3);
decl->DrawPrimitives(VertexPrimitive::Triangle, NUM / 3);
decl->Unbind();
shader->Unbind();
Anda melihat bahwa decl
itu sedikit lebih dari sekedar deklarasi vertex D3D-like, itu agak mengurus rendering juga. Apakah ini masuk akal? Apa yang akan menjadi desain yang lebih bersih? Atau sumber inspirasi yang bagus?
sumber
Jawaban:
Pada dasarnya Anda mengalami situasi yang membuat NVIDIA Cg menjadi perangkat lunak yang sangat menarik (selain fakta bahwa NVIDIA Cg tidak mendukung GL | ES, yang Anda katakan sedang Anda gunakan).
Perhatikan juga bahwa Anda seharusnya tidak menggunakan glGetAttribLocation. Fungsi itu adalah juju buruk dari hari-hari awal GLSL sebelum orang-orang yang bertanggung jawab atas GL benar-benar mulai memahami bagaimana bahasa shading yang baik harus bekerja. Ini tidak ditinggalkan karena memiliki penggunaan sesekali, tetapi secara umum, lebih suka glBindAttibLocation atau ekstensi lokasi atribut eksplisit (inti dalam GL 3.3+).
Menangani perbedaan dalam bahasa shader sejauh ini merupakan bagian tersulit dari perangkat lunak porting antara GL dan D3D. Masalah API yang Anda hadapi mengenai definisi tata letak simpul juga dapat dilihat sebagai masalah bahasa shader, karena versi GLSL sebelum 3.30 tidak mendukung lokasi atribut eksplisit (serupa dengan atribut semantik di HLSL) dan versi GLSL sebelum 4.10 iirc tidak mendukung binding seragam eksplisit.
Pendekatan "terbaik" adalah memiliki pustaka bahasa tingkat tinggi dan format data shading yang merangkum paket shader Anda. JANGAN hanya memberi makan sekelompok GLSL / HLSL mentah ke kelas Shader yang tipis dan berharap untuk dapat menghasilkan segala jenis API yang waras.
Sebaliknya, letakkan shader Anda ke dalam file. Bungkus mereka dalam sedikit meta-data. Anda bisa menggunakan XML, dan menulis paket shader seperti:
Menulis parser minimal untuk itu sepele (cukup gunakan TinyXML misalnya). Biarkan pustaka shader Anda memuat paket itu, pilih profil yang sesuai untuk target renderer Anda saat ini, dan kompilasi shader.
Perhatikan juga bahwa jika Anda mau, Anda dapat menyimpan sumber di luar definisi shader, tetapi masih memiliki file. Masukkan saja nama file alih-alih sumber ke elemen sumber. Ini mungkin bermanfaat jika Anda berencana mengkompilasi shader, misalnya.
Bagian yang sulit sekarang tentu saja berurusan dengan GLSL dan kekurangannya. Masalahnya adalah Anda harus mengikat lokasi atribut ke sesuatu yang mirip dengan semantik HLSL. Ini dapat dilakukan dengan mendefinisikan semantik tersebut di API Anda dan kemudian menggunakan glBindAttribLocation sebelum menautkan profil GLSL. Kerangka paket shader Anda dapat menangani hal ini secara eksplisit, sama sekali tidak perlu API grafik Anda untuk mengekspos detailnya.
Anda dapat melakukannya dengan memperluas format XML di atas dengan beberapa elemen baru di profil GLSL untuk secara eksplisit menentukan lokasi atribut, misalnya
Kode paket shader Anda akan membaca semua elemen attrib di XML, ambil nama dan semantik darinya, cari indeks atribut yang telah ditentukan untuk setiap semantik, dan kemudian secara otomatis panggil glBindAttribLocation untuk Anda saat menautkan shader.
Hasil akhirnya adalah bahwa API Anda sekarang dapat terasa jauh lebih baik daripada kode GL lama Anda yang mungkin pernah terlihat, dan bahkan sedikit lebih bersih daripada D3D11 akan memungkinkan:
Perhatikan juga bahwa Anda tidak benar - benar membutuhkan format paket shader. Jika Anda ingin menjaga hal-hal sederhana, Anda bebas hanya memiliki jenis fungsi loadShader (const char * name) yang secara otomatis mengambil nama.vs dan name.fs file GLSL dalam mode GL dan mengkompilasi dan menautkannya. Namun, Anda benar-benar menginginkan metadata atribut itu. Dalam kasus sederhana, Anda dapat menambah kode GLSL Anda dengan komentar khusus yang mudah diurai, seperti:
Anda bisa menjadi semewah yang Anda inginkan dengan menguraikan komentar. Lebih dari beberapa mesin profesional akan melangkah lebih jauh untuk membuat ekstensi bahasa kecil yang mereka parsing dan modifikasi, bahkan, seperti hanya menambahkan deklarasi semantik gaya HLSL secara langsung. Jika pengetahuan Anda tentang parsing kuat, Anda harus dapat menemukan deklarasi yang diperluas itu, mengekstrak informasi tambahan, dan kemudian mengganti teks dengan kode yang kompatibel dengan GLSL.
Tidak peduli bagaimana Anda melakukannya, versi singkatnya adalah untuk menambah GLSL Anda dengan informasi semantik atribut yang hilang dan meminta abstraksi loader shader Anda dengan memanggil glBindAttribLocation untuk memperbaiki keadaan dan menjadikannya lebih seperti versi GLSL modern dan HLSL yang mudah dan efisien.
sumber
Pertama, saya sarankan menggunakan
VertexBuffer<T>
untuk meningkatkan keamanan tipe, tetapi kedua, saya pikir perbedaan antara kedua API terlalu banyak pada level ini. Saya pribadi akan sepenuhnya merangkum penyaji di belakang antarmuka yang tidak berurusan dengan hal-hal seperti deklarasi vertex atau pengaturan atribut shader.sumber
Secara pribadi, saya akan membuat (dan menegakkan) konvensi standar untuk indeks atribut. Indeks GL 0 adalah posisi. Indeks GL 1 adalah warnanya. Indeks 2 adalah normal, dengan 3 dan 4 untuk garis singgung dan binormal (jika perlu). Indeks 5-7 adalah koordinat tekstur. Mungkin 8 dan 9 untuk bobot tulang. 10 dapat menjadi warna kedua jika perlu. Jika Anda tidak dapat menggunakan
GL_ARB_explicit_attrib_location
atau GL 3.3+, maka Anda juga harus menetapkan penamaan atribut standar konvensi .Dengan begitu, D3D memiliki konvensi dan OpenGL memiliki konvensi. Jadi pengguna bahkan tidak perlu bertanya apa indeks "posisi" itu; mereka tahu itu 0. Dan abstraksi Anda tahu bahwa 0 berarti, di tanah D3D
VertexUsage::Position
,.sumber