Szenendiagramm für die verzögerte Rendering-Engine

10

Als Lernübung habe ich eine verzögerte Rendering-Engine geschrieben. Jetzt möchte ich dieser Engine ein Szenendiagramm hinzufügen, aber ich bin ein bisschen verwirrt, wie das geht.

Bei einer normalen (Forward-Rendering-Engine) würde ich einfach alle Elemente (alle, die IDrawable und IUpdateAble implementieren) zu meinem Szenendiagramm hinzufügen, dann zuerst die Breite des Szenendiagramms durchlaufen und überall Draw () aufrufen.

In einer verzögerten Rendering-Engine muss ich jedoch Draw-Aufrufe trennen. Zuerst muss ich die Geometrie zeichnen, dann die Schattenwerfer und dann die Lichter (alle zu verschiedenen Renderzielen), bevor ich sie alle kombiniere. In diesem Fall kann ich also nicht einfach über das Szenendiagramm fahren und einfach Zeichnen aufrufen. So wie ich es sehe, muss ich entweder dreimal über das gesamte Szenendiagramm fahren und prüfen, welche Art von Objekt gezeichnet werden muss, oder ich muss drei separate Szenendiagramme erstellen, die irgendwie miteinander verbunden sind. Beides scheint eine schlechte Lösung zu sein. Ich möchte Szenenobjekte transparenter behandeln.

Eine andere Lösung, an die ich gedacht habe, war, wie gewohnt durch das Szenendiagramm zu reisen und Elemente zu 3 separaten Listen hinzuzufügen, Geometrie, Schattenwerfer und Lichter zu trennen und diese Listen dann zu wiederholen, um das richtige Material zu zeichnen. Ist das besser und ist es? klug, 3 Listen pro Frame neu zu füllen?

Roy T.
quelle

Antworten:

6

Ein Ansatz, den ich in einem C ++ - Projekt verwendet habe, besteht darin, dass der Szenendiagramm (der den räumlichen Index hat) einen 'sichtbaren' std :: -Vektor von Treffern basierend auf dem aktuellen Betrachtungsstumpf ausfüllt. Diese sichtbare Liste wird vom Szenendiagramm verwaltet und nur dann neu berechnet, wenn sich die Kamera bewegt. Verschobene Objekte im Diagramm werden in dieser Liste verschoben und mit Grabsteinen und unsortierten Änderungslisten versehen, die nach Bedarf sortiert und wieder zusammengeführt werden.

Die Liste der sichtbaren Elemente wird zuerst nach Shader-ID und innerhalb jedes Typs nach Entfernung von der Kamera sortiert. Die Shader-IDs werden so zugewiesen, dass das Gelände zuerst sortiert wird und dann Gebäude und dann Einheiten und dann Projektile und dann Partikel usw. - es handelt sich um ein RTS. Einige Modelle haben mehr als einen Shader, aber sie bewerben nur ihren primären Shader. Wenn sie zum Zeichnen aufgefordert werden, fügen sich diejenigen, die Bits benötigen, die ebenfalls mit einem anderen Shader gezeichnet wurden, zu einer Single-Linked-Liste mit dem nächsten Durchgang hinzu.

Das Zeichnen durchläuft also das sichtbare Array in einem Durchgang, und in diesem einen Durchgang wird eine verknüpfte Liste der zu überarbeitenden Elemente erstellt, und sie werden in einem zweiten Durchgang usw. gezeichnet.

Das Zeichnen von vorne nach hinten und undurchsichtig und dann transparent hilft dabei, alles gesund zu halten.

Dies minimiert möglicherweise nicht die Anzahl der Shader-Änderungen usw., ist jedoch ziemlich praktikabel und einfach zu implementieren.

Ich habe keine Ahnung von XNA und wie anwendbar dies ist und wie viel von diesem Low-Level-Zeug, das Sie erstellen, fürchte ich. Es wäre jedoch sehr interessant zu wissen, was die Veteranen von diesem Ansatz für C ++ - RTS halten.

Wille
quelle
Hey Will, diese Antwort gefällt mir sehr gut, vor allem, weil sie völlig anders ist als bisher. Ihre Methode scheint sehr vernünftig zu sein, besonders wenn Sie auch an halbtransparente Objekte denken (die ich bisher meistens vermieden habe). Das Erstellen einer (verknüpften) Liste aus Ihrem Szenendiagramm für zu besuchende Objekte scheint eine sehr gute Idee zu sein. Und ja, in XNA müssen wir all diese Low-Level-Sachen auch machen :).
Roy T.
3

Mein Vorschlag wäre ein zweistufiger Ansatz, der auf Ihre spezifischen Anforderungen zugeschnitten ist, ähnlich wie Sie es selbst beschrieben haben. Sie benötigen für jeden Rendering-Schritt ein Szenendiagramm und eine "Render-Sammlung", in Ihrem Fall Schatten, Geometrie, Lichter (vielleicht ist ein vierter transparente Objekte?)

Das Szenendiagramm kann auf jeder Art von Beziehungen basieren, aber meine persönliche Präferenz würde auf räumlichen Beziehungen basieren, bei denen jeder Knoten die anderen Knoten enthalten kann, um ein schnelles Keulen zu ermöglichen.

Die Render-Sammlungen können beliebige Datenstrukturen sein, die auf den jeweiligen Schritt zugeschnitten sind. Beispielsweise könnte die Schattensammlung eine Liste oder ein Baum sein, die nach Tiefe sortiert sind, um die frühzeitige Z-Zurückweisung zu maximieren. Die Geometriesammlung kann nach Shader-Verwendung sortiert werden, um Shader- (Status-) Änderungen zu minimieren. Die Lichtsammlung kann eine Liste oder ein Baum sein, sortiert nach Lichtabstand, Größe oder einer Kombination davon, sodass Sie die Lichtwiedergabe nur auf die effektivsten Lichter beschränken können, wenn die Leistung ein Problem darstellt.

Unabhängig davon, welche Datenstrukturen Sie auswählen, stellen Sie sicher, dass der Einfügevorgang schnell ist, und stellen Sie sicher, dass Pooling und andere Techniken verwendet werden, um jegliche Zuordnung / Zerstörung von Daten zu vermeiden, da Sie diese Listen in jedem Frame löschen und füllen.

Jetzt ist es einfach, alles zusammenzubinden. Gehen Sie einfach durch das Szenendiagramm und fügen Sie jedes Element der entsprechenden Render-Sammlung (en) hinzu. Es ist hilfreich, wenn Ihre Datenstruktur neue Einträge automatisch nach den Anforderungen sortiert / strukturiert. Wenn Sie fertig sind, gehen Sie die Render-Sammlungen in der gewünschten Reihenfolge durch und rendern Sie sie.

Da Ihre Datenstrukturen schnell eingefügt werden und keinen Müll erzeugen, gibt es keine Strafe für das erneute Auffüllen von Listen, wie Sie erwähnt haben.

Mart
quelle