Yarı yaygın bir yaklaşım, gölgelendirici bileşenleri dediğim şeyi, modül dediğini düşündüğüm gibi yapmaktır .
Fikir, işlem sonrası bir grafiğe benzer. Hem gerekli girişleri, üretilen çıktıları hem de gerçekte üzerinde çalışacak kodu içeren gölgelendirici kod parçaları yazarsınız. Herhangi bir durumda hangi gölgelendiricilerin uygulanacağını gösteren bir listeniz var (bu malzemenin bir yumru haritalama bileşenine ihtiyacı olup olmadığı, ertelenmiş veya ileri bileşen etkin olup olmadığı vb.).
Şimdi bu grafiği alıp gölgelendirici kodu oluşturabilirsiniz. Bu çoğunlukla, parçaların kodunu "yapıştırmak" anlamına gelir; grafik zaten gerekli sırada olduklarından emin olmak ve daha sonra gölgelendirici girişlerini / çıkışlarını uygun şekilde yapıştırmak anlamına gelir (GLSL'de, bu "global" , out ve uniform değişkenler).
Bu bir ubershader yaklaşımı ile aynı şey değildir. Ubershaders, her şey için gereken tüm kodları tek bir gölgelendiriciye koyduğunuz yerdir, belki de #ifdefs ve üniformaları ve benzerlerini kullanarak bunları derlerken veya çalıştırırken açmak ve kapatmak için. Şahsen ubershader yaklaşımını hor görüyorum, ancak bazı oldukça etkileyici AAA motorları bunları kullanıyor (özellikle Crytek akla geliyor).
Gölgelendirici parçalarını çeşitli şekillerde kullanabilirsiniz. En gelişmiş yol - ve GLSL, HLSL ve konsolları desteklemeyi planlıyorsanız, yararlı - bir gölgelendirici dili için bir ayrıştırıcı yazmaktır (muhtemelen geliştiricileriniz tarafından maksimum "anlaşılabilirlik" için olabildiğince HLSL / Cg veya GLSL'ye yakın) ) daha sonra kaynaktan kaynağa çeviriler için kullanılabilir. Başka bir yaklaşım sadece gölgelendirici parçalarını XML dosyalarına veya benzerlerine sarmaktır, ör.
<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>
Bu yaklaşımla farklı API'ler için birden fazla kod bölümü oluşturabileceğinizi ve hatta kod bölümünü güncelleyebileceğinizi unutmayın (böylece bir GLSL 1.20 sürümüne ve bir GLSL 3.20 sürümüne sahip olabilirsiniz). Grafiğiniz, uyumlu kod bölümü olmayan gölgelendirici parçalarını otomatik olarak hariç tutabilir, böylece eski donanımda yarı zarif bir bozulma elde edebilirsiniz (bu nedenle normal haritalama veya programlayıcının ihtiyacı olmadan destekleyemeyen eski donanımlarda hariç tutulan her şey bir sürü açık kontrol yapın).
Daha sonra XMl örneği benzer bir şey üretebilir (bu geçersiz GLSL ise özür dilerim, kendimi bu API'ya maruz bıraktığımdan beri bir süre geçti):
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;
}
Biraz daha akıllı olabilir ve daha "verimli" kod üretebilirsiniz, ancak dürüst olmak gerekirse, toplam bok olmayan herhangi bir gölgelendirici derleyicisi sizin için üretilen koddan fazlalıkları kaldıracaktır. Belki daha yeni GLSL, dosya adını #line
şimdi de komutlara koymanızı sağlar , ancak eski sürümlerin çok eksik olduğunu ve bunu desteklemediğini biliyorum.
Birden fazla parçaya sahipseniz, girişleri (ağaçtaki bir ata parçası yığını tarafından çıkış olarak sağlanmaz), çıktılar gibi giriş bloğuna birleştirilir ve kod da birleştirilir. Aşamaların eşleşmesini (tepe noktasıyla parça arası) ve köşe özelliği giriş düzenlerinin "sadece işe yarar" olmasını sağlamak için biraz fazladan çalışma yapılır. Bu yaklaşımın bir başka güzel yararı, GLSL'nin eski sürümlerinde desteklenmeyen açık üniforma ve giriş özniteliği bağlanma indeksleri yazabilmeniz ve bunları gölgelendirici oluşturma / bağlama kütüphanenizde işlemenizdir. Benzer şekilde, glVertexAttribPointer
uyumluluk sağlamak ve her şeyin "sadece işe yaraması" için VBO'larınızı ve çağrılarınızı ayarlarken meta verileri kullanabilirsiniz .
Maalesef bunun gibi iyi bir çapraz API kütüphanesi yok. Cg biraz yaklaşıyor, ancak AMD kartlarında OpenGL için bok desteği var ve en temel kod oluşturma özelliklerinden herhangi birini kullanırsanız son derece yavaş olabilir. DirectX efektleri çerçevesi de çalışır, ancak elbette HLSL dışında herhangi bir dil için sıfır desteğe sahiptir. GLXL için DirectX kütüphanelerini taklit eden ancak son kontrol ettiğimde durumlarına bakıldığında sadece kendim yazacağım bazı eksik / buggy kütüphaneleri var.
Ubershader yaklaşımı sadece belirli özellikler için "iyi bilinen" önişlemci direktiflerini tanımlamak ve daha sonra farklı konfigürasyona sahip farklı malzemeler için yeniden derlemek anlamına gelir. Örneğin, normal bir haritaya sahip herhangi bir malzeme için tanımlayabilir USE_NORMAL_MAPPING=1
ve daha sonra piksel aşamalı ubershader'ınızda şunları yapabilirsiniz:
#if USE_NORMAL_MAPPING
vec4 normal;
// all your normal mapping code
#else
vec4 normal = normalize(in_normal);
#endif
Burada büyük bir sorun, kullanımda olan tüm kombinasyonları önceden derlemeniz gereken önceden derlenmiş HLSL için bu sorunla başa çıkmaktır. GLSL ile bile, aynı gölgelendiricilerin yeniden derlenmesini / önbelleğe alınmasını önlemek için kullanılan tüm önişlemci yönergelerinin bir anahtarını düzgün bir şekilde oluşturabilmeniz gerekir. Üniforma kullanmak karmaşıklığı azaltabilir, ancak önişlemci üniformalarının aksine talimat sayısını azaltmaz ve yine de performans üzerinde küçük bir etkisi olabilir.
Açık olmak gerekirse, her iki yaklaşım da (sadece bir ton gölgelendiricinin elle yazılmasının yanı sıra) AAA alanında kullanılır. Sizin için en uygun olanı kullanın.