Wie kann ich einen Renderer implementieren, der viele Arten von Grundelementen zeichnen kann?

8

Dies hängt etwas mit einer Frage zusammen, die ich zuvor bezüglich des Zeichnens indizierter Grundelemente gestellt habe .

Mein Problem war, dass ich nur einen Würfel zeichnete, wenn ich viele zeichnen wollte. Mir wurde gesagt, dass das Problem darin bestand, dass ich die Scheitelpunkt- und Indexpuffer bei jeder neuen Instanziierung von überschrieb Cubeund stattdessen einen am Ursprung erstellen und dann viele zeichnen sollte, indem ich eine Transformationsmatrix an den Shader weiterleitete, der ihn anders erscheinen lässt setzt. Das hat wunderbar funktioniert.

Ich habe jetzt jedoch ein neues Problem: Wie würde ich viele verschiedene Arten von Grundelementen zeichnen?

Hier ist mein Code aus der vorherigen Frage:

Cube::Cube(D3DXCOLOR colour, D3DXVECTOR3 min, D3DXVECTOR3 max)
{
// create eight vertices to represent the corners of the cube
VERTEX OurVertices[] =
{
    {D3DXVECTOR3(min.x, max.y, max.z), colour},
    {D3DXVECTOR3(min.x, max.y, min.z), colour},
    {D3DXVECTOR3(min.x, min.y, max.z), colour},
    {min, colour},
    {max, colour},
    {D3DXVECTOR3(max.x, max.y, min.z), colour},
    {D3DXVECTOR3(max.x, min.y, max.z), colour},
    {D3DXVECTOR3(max.x, min.y, min.z), colour},
};

// create the vertex buffer
D3D10_BUFFER_DESC bd;
bd.Usage = D3D10_USAGE_DYNAMIC;
bd.ByteWidth = sizeof(VERTEX) * 8;
bd.BindFlags = D3D10_BIND_VERTEX_BUFFER;
bd.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
bd.MiscFlags = 0;

device->CreateBuffer(&bd, NULL, &pBuffer);

void* pVoid;    // the void pointer

pBuffer->Map(D3D10_MAP_WRITE_DISCARD, 0, &pVoid);    // map the vertex buffer
memcpy(pVoid, OurVertices, sizeof(OurVertices));    // copy the vertices to the buffer
pBuffer->Unmap();

// create the index buffer out of DWORDs
DWORD OurIndices[] = 
{
    0, 1, 2,    // side 1
    2, 1, 3,
    4, 0, 6,    // side 2
    6, 0, 2,
    7, 5, 6,    // side 3
    6, 5, 4,
    3, 1, 7,    // side 4
    7, 1, 5,
    4, 5, 0,    // side 5
    0, 5, 1,
    3, 7, 2,    // side 6
    2, 7, 6,
};

// create the index buffer
// D3D10_BUFFER_DESC bd;    // redefinition
bd.Usage = D3D10_USAGE_DYNAMIC;
bd.ByteWidth = sizeof(DWORD) * 36;
bd.BindFlags = D3D10_BIND_INDEX_BUFFER;
bd.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
bd.MiscFlags = 0;

device->CreateBuffer(&bd, NULL, &iBuffer);

iBuffer->Map(D3D10_MAP_WRITE_DISCARD, 0, &pVoid);    // map the index buffer
memcpy(pVoid, OurIndices, sizeof(OurIndices));    // copy the indices to the buffer
iBuffer->Unmap();

//this is simply a single call to the update method that sets up the scale, rotation
//and translation matrices, in case the cubes are static and you don't want to have to 
//call update every frame
Update(D3DXVECTOR3(1, 1, 1), D3DXVECTOR3(0, 0, 0), D3DXVECTOR3(0, 0, 0));
}

Wenn ich den Code duplizieren und ändern würde, um ein anderes Objekt oder eine andere Form zu erhalten, würde die zuletzt zu initialisierende Form den Scheitelpunktpuffer überschreiben, nicht wahr?

Benutze ich mehrere Vertex-Puffer? Hänge ich den neuen Scheitelpunktpuffer an den alten an und zeichne sie mit den entsprechenden Indizes? Kann ich auch machen Beide?

Sir Yakalot
quelle

Antworten:

12

Es ist wahrscheinlich eine schlechte Idee, neue Klassen für jeden Geometrietyp zu erstellen, den Sie unterstützen möchten. Es ist nicht sehr skalierbar oder wartbar. Darüber hinaus scheint das gewünschte Klassendesign jetzt die Aufgaben der Verwaltung der Geometrie selbst und der Instanzdaten für diese Geometrie zusammenzuführen.

Hier ist ein Ansatz, den Sie wählen können:

Erstellen Sie zwei Klassen Meshund MeshInstance. A Meshenthält alle Eigenschaften der gemeinsam genutzten Geometrie - im Grunde einen Scheitelpunktpuffer und einen Indexpuffer. Wenn Sie möchten, können Sie Hilfsfunktionen erstellen, die Netze mit Würfelscheitelpunktdaten (oder Kugelscheitelpunktdaten oder was auch immer Sie möchten) erstellen. Sie sollten die öffentliche Schnittstelle der MeshKlasse so anpassen, dass solche Hilfsfunktionen als Nicht-Mitglieder- und Nicht-Freund-Funktionen implementiert werden können.

MeshInstancemuss andererseits unter Bezugnahme auf a konstruiert werden Mesh. Das MeshInstanceenthält die Eigenschaften eines einzelnen Objekts - es ist eine Welttransformation und Shader-Überschreibungen, die zum Rendern verwendet werden, und so weiter.

Auf diese Weise erhalten Sie beim Erstellen eines neuen Cubes zunächst das MeshObjekt, das einen Cube darstellt, aus einer gemeinsam genutzten Bibliothek primitiver Netzobjekte, die Sie beim Start erstellt haben. Dann erstellen Sie einen neuen MeshInstanceund weisen ihm diesen Würfel zu Mesh.

Beim Rendern erstellen Sie eine Liste aller MeshInstances, die Sie zeichnen möchten, und senden sie ab. Wenn Sie sie nach Meshoder nach Textur gruppieren , können Sie den Overhead für Statusänderungen optimieren (dh Sie zeichnen alle Netzinstanzen, die dem Würfelnetz entsprechen, auf einmal und dann alle Netzinstanzen, die dem Kugelnetz entsprechen, sodass Sie weniger SetVertexBufferAufrufe haben das D3D-Gerät). Sie können auch nach anderen Status gruppieren, z. B. nach Textur und Shader.

Auf diese Weise vermeiden Sie die Verschwendung von Speicher, der Scheitelpunktdaten dupliziert, und stellen sicher, dass Ihr Rendering-System auf einen beliebigen Satz von Grundelementen skaliert werden kann, indem Sie einfach neue (a) Funktionen zum programmgesteuerten Erstellen von Netzen oder (b) Funktionen zum Laden von Netzen aus Dateien von implementieren ein bestimmtes Format.

Sobald Ihre Render-Pipeline in Bezug auf verallgemeinerte MeshObjekte funktioniert , ist es viel einfacher, sie global an neue Techniken oder Optimierungen anzupassen.

Spezifische Kommentare:

Wenn ich den Code duplizieren und ändern würde, um ein anderes Objekt oder eine andere Form zu erhalten, würde die zuletzt zu initialisierende Form den Scheitelpunktpuffer überschreiben. würde es nicht?

Nein. In dem von Ihnen geposteten Code wurde nur pBufferdann überschrieben, wenn es sich um eine statische Elementvariable handelte. Wenn Sie den Klassengroßhandel kopiert haben, um (zum Beispiel) eine SphereKlasse zu erstellen , wäre dies eine neue statische Variable. Das ist immer noch eine schlechte Idee.

Benutze ich mehrere Vertex-Puffer? Hänge ich den neuen Scheitelpunktpuffer an den alten an und zeichne sie mit den entsprechenden Indizes? Kann ich auch machen Beide?

Die naive Implementierung der oben beschriebenen Technik umfasst mehrere Puffer (einen für jeden Satz gemeinsam genutzter Geometrie). Wenn diese Geometrie statisch ist, ist es möglich, alles in einem (oder mehreren, da es eine praktische optimale Grenze für die Puffergröße gibt) Puffer zu speichern, um Änderungen des Pufferzustands noch weiter zu minimieren. Dies sollte als Optimierung betrachtet werden und bleibt dem Leser als Übung überlassen. Lassen Sie es zuerst funktionieren und sorgen Sie sich dann darum, es schnell zu machen.


quelle
Ich weiß, wir sollten keine Dankeskommentare posten, aber das ist so hilfreich! Vielen Dank!
SirYakalot
Im Moment sind pBuffer und iBuffer extern. Soll ich diese Instanz zu Mitgliedern jedes Mesh-Objekts machen?
SirYakalot
1
Ja, das ist ein guter Anfang.
Jetzt, da ich dies implementiere, habe ich ein wenig Probleme damit, darüber nachzudenken, wie es geht. Nur der Teil, in dem Sie sagen: "Wenn Sie möchten, können Sie Hilfsfunktionen erstellen, die Netze erstellen, die Würfelscheitelpunktdaten (oder Kugelscheitelpunktdaten) enthalten. oder was auch immer Sie möchten). Sie sollten die öffentliche Schnittstelle der Mesh-Klasse so anpassen, dass solche Hilfsfunktionen als Nicht-Mitglied-, Nicht-Freund-Funktionen implementiert werden können. " Was genau meinst du mit Nicht-Helfer-, Nicht-Freund-Funktionen?
SirYakalot
Eine Funktion, die kein Mitglied der Klasse ist (also eine globale Funktion), die auch nicht als Freund der Klasse deklariert ist. Mit anderen Worten, eine "reguläre" Funktion, die das Netzobjekt nur über seine öffentliche API manipuliert.