Sie befinden sich im Grunde genommen in einer Situation, die NVIDIA Cg zu einer so attraktiven Software macht (abgesehen von der Tatsache, dass GL | ES, von dem Sie sagten, dass Sie es verwenden, nicht unterstützt wird).
Beachten Sie auch, dass Sie glGetAttribLocation wirklich nicht verwenden sollten. Diese Funktion ist ein schlechter Juju aus den ersten Tagen von GLSL, bevor die Verantwortlichen von GL wirklich anfingen zu überlegen, wie eine gute Schattierungssprache funktionieren sollte. Es ist nicht veraltet, da es gelegentlich verwendet wird. Bevorzugen Sie jedoch im Allgemeinen glBindAttibLocation oder die explizite Erweiterung des Attributstandorts (Kern in GL 3.3+).
Der Umgang mit den Unterschieden in den Shader-Sprachen ist bei weitem der schwierigste Teil der Portierungssoftware zwischen GL und D3D. Die API-Probleme, auf die Sie bei der Definition des Scheitelpunktlayouts stoßen, können auch nur als Shader-Sprachproblem angesehen werden, da GLSL-Versionen vor 3.30 keine explizite Attributposition (ähnlich der Attributsemantik in HLSL) und GLSL-Versionen zuvor unterstützen 4.10 iirc unterstützt keine expliziten einheitlichen Bindungen.
Der "beste" Ansatz besteht darin, eine übergeordnete Shading-Sprachbibliothek und ein Datenformat zu haben, die Ihre Shader-Pakete kapseln. Geben Sie NICHT einfach eine Menge rohes GLSL / HLSL in eine Thin Shader-Klasse ein und erwarten Sie, dass Sie eine vernünftige API entwickeln können.
Legen Sie stattdessen Ihre Shader in einer Datei ab. Wickeln Sie sie in ein paar Metadaten ein. Sie können XML verwenden und Shader-Pakete schreiben wie:
<shader name="bloom">
<profile type="glsl" version="1.30">
<source type="vertex"><![CDATA[
glsl vertex shader code goes here
]]></source>
<source type="fragment"><![CDATA[
glsl fragment shader code goes here
]]></source>
</profile>
<profile type="hlsl" version="sm3">
<source type="fx"><![CDATA[
hlsl effects code goes here
you could also split up the source elements for hlsl
]]></source>
</profile>
</shader>
Das Schreiben eines minimalen Parsers dafür ist trivial (verwenden Sie zum Beispiel einfach TinyXML). Lassen Sie Ihre Shader-Bibliothek dieses Paket laden, wählen Sie das richtige Profil für Ihren aktuellen Ziel-Renderer aus und kompilieren Sie die Shader.
Beachten Sie auch, dass Sie die Quelle außerhalb der Shader-Definition behalten können, die Datei jedoch weiterhin vorhanden ist. Fügen Sie einfach Dateinamen anstelle von Quelle in die Quellelemente ein. Dies kann nützlich sein, wenn Sie beispielsweise Shader vorkompilieren möchten.
Der schwierige Teil ist jetzt natürlich die Behandlung von GLSL und seinen Mängeln. Das Problem ist, dass Sie Attributpositionen an etwas binden müssen, das der HLSL-Semantik ähnelt. Dies kann erreicht werden, indem Sie diese Semantik in Ihrer API definieren und dann glBindAttribLocation verwenden, bevor Sie das GLSL-Profil verknüpfen. Ihr Shader-Paket-Framework kann dies explizit verarbeiten, ohne dass Ihre Grafik-API die Details offenlegen muss.
Sie können dies tun, indem Sie das obige XML-Format um einige neue Elemente im GLSL-Profil erweitern, um Attributpositionen explizit anzugeben, z
<shader name="bloom">
<profile type="glsl" version="1.30">
<attrib name="inPosition" semantic="POSITION"/>
<attrib name="inColor" semantic="COLOR0"/>
<source type="vertex"><![CDATA[
#version 150
in vec4 inPosition;
in vec4 inColor;
out vec4 vColor;
void main() {
vColor = inColor;
gl_Position = position;
}
]]></source>
</profile>
</shader>
Ihr Shader-Paketcode würde alle Attributelemente im XML einlesen, den Namen und die Semantik daraus abrufen, den vordefinierten Attributindex für jede Semantik nachschlagen und dann beim Verknüpfen des Shaders automatisch glBindAttribLocation für Sie aufrufen.
Das Endergebnis ist, dass sich Ihre API jetzt viel besser anfühlt, als Ihr alter GL-Code wahrscheinlich jemals ausgesehen hat, und sogar ein bisschen sauberer, als D3D11 es erlauben würde:
// simple example, easily improved
VertexLayout layout = api->createLayout();
layout.bind(gfx::POSITION, buffer0, gfx::FLOATx4, sizeof(Vertex), offsetof(Vertex, position));
layout.bind(gfx::COLOR0, buffer0, gfx::UBYTEx4, sizeof(Vertex), offsetof(Vertex, color));
Beachten Sie auch, dass Sie das Shader-Paketformat nicht unbedingt benötigen . Wenn Sie die Dinge einfach halten möchten, können Sie nur eine Funktion loadShader (const char * name) verwenden, die die GLSL-Dateien name.vs und name.fs im GL-Modus automatisch erfasst und kompiliert und verknüpft. Sie werden jedoch unbedingt diese Attribut-Metadaten wollen. Im einfachen Fall können Sie Ihren GLSL-Code mit speziellen, einfach zu analysierenden Kommentaren ergänzen, wie z.
#version 150
/// ATTRIB(inPosition,POSITION)
in vec4 inPosition;
/// ATTRIB(inColor,COLOR0)
in vec4 inColor;
out vec4 vColor
void main() {
vColor = inColor;
gl_Position = inPosition;
}
Sie können beim Kommentieren von Kommentaren so ausgefallen werden, wie Sie möchten. Nicht wenige professionelle Engines werden so weit gehen, dass sie kleinere Spracherweiterungen vornehmen, die sie analysieren und sogar modifizieren, z. B. das direkte Hinzufügen semantischer Deklarationen im HLSL-Stil. Wenn Ihre Kenntnisse über das Parsen solide sind, sollten Sie in der Lage sein, diese erweiterten Deklarationen zuverlässig zu finden, die zusätzlichen Informationen zu extrahieren und den Text dann durch den GLSL-kompatiblen Code zu ersetzen.
Unabhängig davon, wie Sie es tun, besteht die Kurzversion darin, Ihre GLSL mit den fehlenden Attribut-Semantikinformationen zu erweitern und Ihre Shader-Loader-Abstraktion mit dem Aufruf von glBindAttribLocation zu befassen, um Probleme zu beheben und sie den einfachen und effizienten modernen GLSL-Versionen und HLSL ähnlicher zu machen.