OpenGL-Vertex-Buffer- und -Index-Buffer-Objekte

Vertex-Buffer- und Index-Buffer-Objekte sind unverzichtbare Werkzeuge bei der Handhabung der 3D-Modell-Geometriedaten im Verlauf des Rendering-Prozesses.
Wie Sie bereits wissen, wird die Geometrie von 3D-Objekten mithilfe von Vertex-Daten beschrieben. Diese Daten sind in einem speziellen Speicherbereich auf der Grafikkarte, dem sogenannten Vertex Buffer, abgelegt.

Ein besonders einfaches 3D-Objekt ist beispielsweise ein Vertex-Quad (Viereck), welches häufig im Zusammenhang mit der Darstellung von Billboards zum Einsatz kommt. Für ein texturiertes Vertex-Quad, dass nicht beleuchtet werden soll, bietet sich bei der Definition der einzelnen Vertices die Verwendung der CSimpleTexturedVertex-Struktur an:

CSimpleTexturedVertex QuadSimpleTexturedVertices[4];

QuadSimpleTexturedVertices[0].x  = -1.0f;
QuadSimpleTexturedVertices[0].y  = 1.0f;
QuadSimpleTexturedVertices[0].z  = 0.0f;

QuadSimpleTexturedVertices[0].tu = 0.0f;
QuadSimpleTexturedVertices[0].tv = 0.0f;


QuadSimpleTexturedVertices[1].x  = -1.0f;
QuadSimpleTexturedVertices[1].y  = -1.0f;
QuadSimpleTexturedVertices[1].z  = 0.0f;

QuadSimpleTexturedVertices[1].tu = 0.0f;
QuadSimpleTexturedVertices[1].tv = 1.0f;


QuadSimpleTexturedVertices[2].x  = 1.0f;
QuadSimpleTexturedVertices[2].y  = 1.0f;
QuadSimpleTexturedVertices[2].z  = 0.0f;

QuadSimpleTexturedVertices[2].tu = 1.0f;
QuadSimpleTexturedVertices[2].tv = 0.0f;


QuadSimpleTexturedVertices[3].x  = 1.0f;
QuadSimpleTexturedVertices[3].y  = -1.0f;
QuadSimpleTexturedVertices[3].z  = 0.0f;

QuadSimpleTexturedVertices[3].tu = 1.0f;
QuadSimpleTexturedVertices[3].tv = 1.0f;


Nach der Definition der Vertices kann mittels glGenBuffers() das Vertex-Buffer-Objekt erzeugt werden. Wie Sie bereits wissen, erhält man jedes Mal, wenn man ein neues OpenGL-Objekt anlegt, als Rückgabewert die Objekt-ID in Form einer vorzeichenlosen 32-Bit-Ganzzahl vom Typ unsigned int. Bevor wir nun die Vertex-Daten in den Buffer kopieren können (via glBufferData()), müssen wir zunächst den zu verwendenden Vertex Buffer auswählen/binden (glBindBuffer()).


GLuint VertexBufferId; // ID des Vertex-Buffer-Objekts

[...]


glGenBuffers(1, &VertexBufferId); // Buffer erzeugen
glBindBuffer(GL_ARRAY_BUFFER, VertexBufferId); // Buffer auswählen

// Daten in den Buffer kopieren:
glBufferData(GL_ARRAY_BUFFER, 4*sizeof(CSimpleTexturedVertex),
             QuadSimpleTexturedVertices, GL_STATIC_DRAW);


Wird der Vertex Buffer nicht mehr benötigt, dann muss der auf der Grafikkarte belegte Speicherbereich wieder freigegeben werden. Hierfür ist die Funktion glDeleteBuffers() zuständig.

glDeleteBuffers(1, &VertexBufferId);


Die Vertex-Daten allein reichen für die Darstellung eines 3D-Modells jedoch nicht aus. Wir benötigen darüber hinaus eine Art Bauanleitung, wie sich aus den Vertexdaten die Oberfläche des 3D-Modells konstruieren lässt.

Hinweis:
Die Oberfläche eines 3D-Modells wird aus einer mehr oder weniger großen Zahl von Dreiecksflächen modelliert (Triangulation). Hierfür stehen uns diverse kostenpflichtige (z.B. 3ds Max) und kostenlose (z.B. Blender) Modeller-Programme zur Verfügung.
Die Anzahl der benötigten Dreiecksflächen ist geometrieabhängig. Für die Darstellung von gekrümmten Oberflächen (z.B. ein Planet) benötigt man eine recht große Anzahl von möglichst kleinen Dreiecksflächen, damit die Oberfläche weniger eckig und kantig wirkt.

Gespeichert wird diese Bauanleitung ebenfalls in einem speziellen Speicherbereich auf der Grafikkarte, dem sogenannten Index Buffer.

Die Bauanleitung für ein Vertex-Quad ist besonders einfach, da sich ein Viereck aus zwei Dreiecken zusammensetzen lässt. Jeweils drei Vertices bilden die Eckpunkte eines Dreiecks.


















Obgleich unter mathematischen Gesichtspunkten betrachtet, jede Dreiecksfläche aus zwei Seiten besteht, ist es wenig zielführend und darüber hinaus der Performance abträglich, wenn man beide Seiten auch tatsächlich rendern würde – stellen Sie sich in diesem Zusammenhang einmal vor, eine Weltraum-Grafik-Engine würde beispielsweise bei einem Planeten unsinnigerweise auch die Innenseite des Planetenmodells darstellen.
Um zu gewährleisten, dass wirklich nur die tatsächlich sichtbare Seite einer Dreiecksfläche gerendert wird, kommt das sogenannte Culling zum Einsatz:

// Culling einschalten:
glEnable(GL_CULL_FACE);

// Culling ausschalten:
glDisable(GL_CULL_FACE);

Standardmäßig werden beim Culling alle Dreiecksflächen dargestellt, deren Vorderseiten (GL_FRONT) im Gegenuhrzeigersinn definiert sind. Die von der Kamera abgewandten, im Gegenuhrzeigersinn definierten Seiten (die Rückseiten, GL_BACK) werden hingegen nicht gerendert:

// Standardeinstellungen:
glFrontFace(GL_CCW);
glCullFace(GL_BACK);

In der vorherigen Abbildung sind wir etwas anders vorgegangen und haben alle Dreiecke im Uhrzeigersinn (clockwise, CW) definiert. Damit unser Vertex-Quad gerendert wird, müssen wir die Culling-Einstellungen wie folgt verändern:

// im Uhrzeigersinn definiert Rückseiten
// werden gecullt:
glFrontFace(GL_CW);
glCullFace(GL_BACK);

bzw.

// im Gegenuhrzeigersinn definiert Vorderseiten
// werden gecullt:
glFrontFace(GL_CCW);
glCullFace(GL_FRONT);

Bevor wir gleich den eigentlichen Index Buffer initialisieren, tragen wir zunächst Dreieck für Dreieck die Indices der zugehörigen Vertices in ein Index-Array ein:

GLushort QuadIndices[6];

// Dreieck 1:
QuadIndices[0] = 1;
QuadIndices[1] = 0;
QuadIndices[2] = 2;

// Dreieck 2:
QuadIndices[3] = 1;
QuadIndices[4] = 2;
QuadIndices[5] = 3;


Im Anschluss daran kann der Index Buffer erzeugt werden. Hierbei nutzen wir die gleichen Funktionsaufrufe wie beim Erstellen des Vertex-Buffers:


GLushort QuadIndices[6];

// Dreieck 1:
QuadIndices[0] = 1;
QuadIndices[1] = 0;
QuadIndices[2] = 2;

// Dreieck 2:
QuadIndices[3] = 1;
QuadIndices[4] = 2;
QuadIndices[5] = 3;


Im Anschluss daran kann der Index Buffer erzeugt werden. Hierbei nutzen wir die gleichen Funktionsaufrufe wie beim Erstellen des Vertex-Buffers:

GLuint IndexBufferId; // ID des Index-Buffer-Objekts

[...]



glGenBuffers(1, &IndexBufferId);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IndexBufferId);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6*sizeof(GLushort), QuadIndices,
             GL_STATIC_DRAW);

Hinweis:
Um den Speicherbedarf eines Index-Buffers zu reduzieren, kann man jeden Index als Variable vom Typ GLushort (16 Bit) speichern. Bei mehr als 65536 Indexeinträgen müssen wir jedoch auf den 32-Bit-Typ GLuint zurückgreifen.


Auch die Freigabe eines Index-Buffers erfolgt analog zur Freigabe eines Vertex-Buffers:

glDeleteBuffers(1, &IndexBufferId);


Nachdem wir nun sowohl den Vertex wie auch den Index Buffer erzeugt haben, kann das Vertex-Quad mithilfe der glDrawElements()-Methode gerendert werden. Zuvor müssen wir natürlich die zu verwendenden Vertex und Index Buffer auswählen:

glBindBuffer(GL_ARRAY_BUFFER, VertexBufferId);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IndexBufferId);

[...]

// verwendete Grafikprimitive: GL_TRIANGLES (im Index Buffer haben
// wir eine Liste von Dreiecken abgespeichert.
// Es müssen zwei Dreiecke gerendert werden (6 Einträge im Index Buffer)
// Wir verwenden einen 16-Bit-Index-Buffer: GL_UNSIGNED_SHORT
// (Ein 32-Bit-Index-Buffer erfordert:
GL_UNSIGNED_INT)
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, NULL);


Damit die Indexdaten im Zuge des Rendering-Prozesses korrekt verarbeitet werden können, benötigt die glDrawElements()-Methode die Zusatzinformation, wie die Index-Buffer-Daten zu interpretieren sind. Hierfür stehen uns verschiedene Grafikprimitiven zur Auswahl. Für 3D-Objekte verwendet man gewöhnlich die Dreieckslisten-Primitive (GL_TRIANGLES) – im Index Buffer ist eine Liste der darzustellenden Dreiecke abgespeichert.

Bei der Darstellung von einzelnen Linien kommt die GL_LINES-Primitive zum Einsatz:

// eine einfache Linie:
glDrawElements(GL_LINES, 2, GL_UNSIGNED_SHORT, NULL);


Für geschlossene Linenzüge (Dreieck, Viereck, Kreis, usw.) bietet sich die Verwendung der GL_LINE_LOOP-Primitive an:

glDrawElements(GL_LINE_LOOP, NumVertices, GL_UNSIGNED_INT, NULL);


Eine Übersicht über die gebräuchlichsten Grafikprimitiven finden Sie in der nachfolgenden Abbildung: