Engine-Rendering-Pipeline: Generieren von Shadern

10

Ich versuche, eine 2D-Spiel-Engine mit OpenGL ES 2.0 (iOS für jetzt) ​​zu erstellen. Ich habe Application Layer in Objective C und ein separates eigenständiges RendererGLES20 in C ++ geschrieben. Außerhalb des Renderers wird kein GL-spezifischer Aufruf getätigt. Es funktioniert perfekt.

Aber ich habe einige Designprobleme bei der Verwendung von Shadern. Jeder Shader hat seine eigenen Attribute und Uniformen, die unmittelbar vor dem Hauptzeichnungsaufruf festgelegt werden müssen (in diesem Fall glDrawArrays). Zum Beispiel würde ich Folgendes tun, um eine Geometrie zu zeichnen:

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(.......);
}

Wie Sie sehen können, ist dieser Code extrem starr. Wenn ich einen anderen Shader verwenden würde, beispielsweise einen Ripple-Effekt, müsste ich zusätzliche Uniformen, Vertex-Attribute usw. übergeben. Mit anderen Worten, ich müsste den RendererGLES20-Render-Quellcode ändern, um den neuen Shader zu integrieren.

Gibt es eine Möglichkeit, das Shader-Objekt vollständig generisch zu gestalten? Was ist, wenn ich nur das Shader-Objekt ändern möchte und mir keine Sorgen über das Neukompilieren der Spielquelle machen möchte? Gibt es eine Möglichkeit, den Renderer von Uniformen und Attributen usw. unabhängig zu machen? Auch wenn wir Daten an Uniformen weitergeben müssen, was ist der beste Ort, um dies zu tun? Modellklasse? Ist der Modellklasse Shader-spezifische Uniformen und Attribute bekannt?

Folgendes zeigt Schauspielerklasse:

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

Modellcontroller-Klasse: Klasse ModelController {Klasse IShader * Shader; int textureId; vec4 Farbton; float alpha; struct Vertex * vertexArray; };

Die Shader-Klasse enthält nur das Shader-Objekt, das Kompilieren und Verknüpfen von Unterroutinen usw.

In der Game Logic-Klasse rendere ich das Objekt tatsächlich:

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

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

Bitte beachten Sie, dass ich, obwohl Actor ISceneNode erweitert, noch nicht mit der Implementierung von SceneGraph begonnen habe. Ich werde das tun, sobald ich dieses Problem behoben habe.

Irgendwelche Ideen, wie man das verbessern kann? Verwandte Designmuster etc?

Vielen Dank für das Lesen der Frage.

fakhir
quelle
Mögliches Duplikat von Game Engine Design - Ubershader - Shader Management Design
Sean Middleditch
Ich bin mir nicht sicher, ob das ein genaues Duplikat ist, aber es kommt auf das an, was Sie versuchen, denke ich.
Sean Middleditch

Antworten:

12

Es ist möglich, Ihr Shader-System datengesteuerter zu gestalten, sodass Sie nicht so viel Shader-spezifischen Code für Uniformen und Scheitelpunktformate haben, sondern diese programmgesteuert basierend auf den an die Shader angehängten Metadaten festlegen.

Zunächst der Haftungsausschluss: Ein datengesteuertes System kann das Hinzufügen neuer Shader erleichtern, ist jedoch mit Kosten verbunden, da das System komplexer wird und die Wartung und das Debuggen erschwert werden. Es ist daher eine gute Idee, sorgfältig darüber nachzudenken, wie viel Datenqualität für Sie von Vorteil ist (für ein kleines Projekt lautet die Antwort möglicherweise "keine"), und nicht zu versuchen, ein System zu erstellen, das übermäßig verallgemeinert ist.

Okay, lassen Sie uns zuerst über Scheitelpunktformate (Attribute) sprechen. Sie können eine Datenbeschreibung erstellen, indem Sie eine Struktur erstellen, die die Daten enthält, an die übergeben werden soll glVertexAttribPointer- Index, Typ, Größe usw. eines einzelnen Attributs - und ein Array dieser Strukturen zur Darstellung des gesamten Scheitelpunktformats haben. Anhand dieser Informationen können Sie programmgesteuert den gesamten GL-Status für die Scheitelpunktattribute einrichten.

Woher stammen die Daten zum Auffüllen dieser Beschreibung? Konzeptionell denke ich, dass der sauberste Weg darin besteht, dass es dem Shader gehört. Wenn Sie die Scheitelpunktdaten für ein Netz erstellen, suchen Sie nach dem für das Netz verwendeten Shader, suchen das für diesen Shader erforderliche Scheitelpunktformat und erstellen den Scheitelpunktpuffer entsprechend. Sie brauchen nur eine Möglichkeit für jeden Shader, um das erwartete Scheitelpunktformat anzugeben.

Es gibt verschiedene Möglichkeiten, dies zu tun. Sie könnten beispielsweise einen Standardsatz von Namen für die Attribute im Shader ("attrPosition", "attrNormal" usw.) sowie einige fest codierte Regeln wie "position is 3 float" verwenden. Anschließend können Sie mit glGetAttribLocationoder ähnlich abfragen, welche Attribute der Shader verwendet, und die Regeln anwenden, um das Scheitelpunktformat zu erstellen. Eine andere Möglichkeit besteht darin, ein XML-Snippet zu haben, das das Format definiert, in einen Kommentar in der Shader-Quelle eingebettet und von Ihren Tools extrahiert wird, oder etwas in dieser Richtung.

Wenn Sie für Uniformen OpenGL 3.1 oder höher verwenden können, empfiehlt es sich, einheitliche Pufferobjekte zu verwenden (das OpenGL-Äquivalent der konstanten Puffer von D3D). Leider hat GL ES 2.0 diese nicht, so dass Uniformen individuell behandelt werden müssen. Eine Möglichkeit wäre, eine Struktur zu erstellen, die die einheitliche Position für jeden Parameter enthält, den Sie einstellen möchten - die Kameramatrix, die Spiegelkraft, die Weltmatrix usw. Auch hier könnten sich Sampler-Positionen befinden. Dieser Ansatz hängt davon ab, dass alle Shader einen Standardsatz von Parametern gemeinsam haben. Nicht jeder Shader muss jeden einzelnen Parameter verwenden, aber alle Parameter müssten sich in dieser Struktur befinden.

Jeder Shader verfügt über eine Instanz dieser Struktur. Wenn Sie einen Shader laden, fragen Sie ihn nach den Positionen aller Parameter unter Verwendung glGetUniformLocationstandardisierter Namen ab. Wenn Sie dann eine Uniform aus dem Code festlegen müssen, können Sie überprüfen, ob sie in diesem Shader vorhanden ist, und einfach die Position nachschlagen und festlegen.

Nathan Reed
quelle