Vertex Array Object: Unterschied zwischen den Versionen

Aus DGL Wiki
Wechseln zu: Navigation, Suche
(Hinweise für Delphi-Programmierer ergänzt)
Zeile 19: Zeile 19:
 
====VBO====
 
====VBO====
 
Es empfiehlt sich, zuerst das VBO zu initialisieren. Da möchte ich an dieser Stelle nicht zu sehr in die Details gehen, denn dazu gibt es bereits ein eigenes Tutorial. Daher nur ein kurzes Stück Beispielcode:
 
Es empfiehlt sich, zuerst das VBO zu initialisieren. Da möchte ich an dieser Stelle nicht zu sehr in die Details gehen, denn dazu gibt es bereits ein eigenes Tutorial. Daher nur ein kurzes Stück Beispielcode:
<cpp>GLuint VboID;
+
<cpp>GLuint VboID;              // für Delphi-Programmierer: var VboID : GLuint;
glGenBuffers(1, &VboID);
+
glGenBuffers(1, &VboID);  // in Pascal: glGenBuffers(1, @VboID);
 
glBindBuffer(GL_ARRAY_BUFFER, VboID);
 
glBindBuffer(GL_ARRAY_BUFFER, VboID);
 
glBufferData(GL_ARRAY_BUFFER, vbo_size, data, usage);</cpp>
 
glBufferData(GL_ARRAY_BUFFER, vbo_size, data, usage);</cpp>

Version vom 20. August 2013, 12:08 Uhr

Willkommen zu meinem ersten Tutorial. Hier möchte ich euch erklären, was es mit Vertex Array Objects (VAOs) auf sich hat und wie man sie verwendet.

Warum VAOs?

Nun werdet ihr euch vermutlich fragen, warum ihr schon wieder ein neues "Object" (neben VBO, IBO, FBO usw.) kennenlernen sollt. Da gibt es im Wesentlichen zwei Gründe: Erstens habt ihr keine Wahl, wenn ihr mit einem OpenGL-Kontext ab Version 3.0 im forward-compatible Modus VBOs benutzen möchtet. Ohne VAOs geht es da nicht. Zweitens, und das ist vermutlich der Grund für diesen Zwang in neueren OpenGL-Versionen: Es erspart euch beim Rendern einige API-Funktionsaufrufe (konkret: glBindBuffer, glEnableVertexAttribArray und glDisableVertexAttribArray), was bekanntlich der Performance zuträglich ist.

Was merkt sich ein VAO?

In einem VAO wird gespeichert, welches Datenformat die Vertices haben, die ihr rendern möchtet. Also beispielsweise, dass ein Vertex aus 3 floats für die Position, 3 unsigned bytes für den Normalenvektor und 2 floats als Textur-Koordinaten besteht. Außerdem merkt sich das VAO, woher (aus welchem VBO) diese Daten genomen werden sollen.

Kurz: Das VAO merkt sich alles, was ihr mit glVertexAttribPointer einstellen könnt.

Es ist wichtig, den Unterschied zwischen VBO und VAO zu verstehen. Man könnte sagen, das VBO enthält die eigentlichen Vertexdaten und das VAO enthält die Informationen, in welchem VBO sich die benötigten Daten befinden und in welchem Format sie vorliegen. Das VAO ist also eine Lesevorschrift für die Daten im VBO.

Ebenfalls wichtig: Obwohl im VAO gespeichert wird, aus welchem VBO die Daten beim Rendern entnommen werden sollen, führt das Binden den VAOs nicht dazu, dass das zugehörige VBO auch gebunden wird. Das ist auch nicht notwendig, denn welches VBO während des Zeichnens gebunden ist, hat keinen Einfluss auf den Rendervorgang. Entscheidend ist nur, welches VBO gebunden war, als das VAO initialisiert wurde (genauer: als glVertexAttribPointer aufgerufen wurde). Zum genauen Ablauf kommen wir im nächsten Abschnitt.

Anwendung

Initialisierung

VBO

Es empfiehlt sich, zuerst das VBO zu initialisieren. Da möchte ich an dieser Stelle nicht zu sehr in die Details gehen, denn dazu gibt es bereits ein eigenes Tutorial. Daher nur ein kurzes Stück Beispielcode:

GLuint VboID;              // für Delphi-Programmierer: var VboID : GLuint;
glGenBuffers(1, &VboID);   // in Pascal: glGenBuffers(1, @VboID);
glBindBuffer(GL_ARRAY_BUFFER, VboID);
glBufferData(GL_ARRAY_BUFFER, vbo_size, data, usage);

VAO

Wie fast alles in OpenGL, haben auch Vertex Array Objects einen Namen, der auch hier eigentlich nur ein Integer ist. Um einen solchen Namen zu erhalten, rufen wir die Funktion glGenVertexArrays auf:

GLuint VaoID;
glGenVertexArrays(1, &VaoID);

Anschließend wird das VAO das erste Mal gebunden:

glBindVertexArray(VaoID);

Spätestens hier sollte das VBO gebunden werden, falls noch nicht geschehen. Nun werden die Funktionen glEnableVertexAttribArray und glVertexAttribPointer aufgerufen, um das Vertexformat festzulegen. Zum Beispiel:

glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(TVertex), 0);  // 3 floats für Position
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(TVertex), 12); // 3 floats für den Normalenvektor
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(TVertex), 24); // 2 floats als Textur-Koordinaten

Der erste Parameter der beiden Funktionen ist der Index des jeweils betroffenen Vertexshader-Attributs (eine Variable, die im Vertexshader mit attribute oder in deklariert wurde). Dieser muss entweder vor dem Linken des Shaders mit glBindAttribLocation festgelegt werden, oder er wird nach dem Linken mit glGetAttribLocation abgefragt. Nun haben wir das VAO vollständig gefüllt. Jetzt können wir, falls wir das möchten ein anderes VAO binden, oder wir entbinden das aktuelle einfach mit

glBindVertexArray(0);

Damit sichern wir, dass sich nachfolgende Aufrufe von glVertexAttribPointer nicht auf unser VAO auswirken.

IBO

An dieser Stelle besteht nun optional die Möglichkeit, auch einen Indexpuffer anzulegen (dies kann aber genau so gut ganz am Anfang geschehen). Dazu wieder ein kleines Stück Code:

GLuint IboID;
glGenBuffers(1, &IboID);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IboID);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, ibo_size, indexdata, usage);

Rendern

Jetzt kommt der schöne Part. Da das komplette Vertexformat nämlich schon im VAO gespeichert ist, reicht ein einziger Aufruf von

glBindVertexArray(VaoID);

und schon stellt OpenGL alles so ein, wie wir es bei der Initialisierung festgelegt haben. Es ist jetzt nicht einmal mehr notwendig, ein VBO zu binden. Wir können einfach drauflos zeichnen:

glDrawArrays(GL_TRIANGLES, 0, vertices_count);

Wer nicht nur ein VBO, sondern auch einen Indexbuffer angelegt hat, der schreibt statt der letzten Zeile nun:

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IboID);
glDrawElements(GL_TRIANGLES, indices_count, GL_UNSIGNED_SHORT, 0);

Aufräumen

Wenn wir das VAO nicht mehr benötigen, sollten wir es der Sauberkeit halber freigeben:

glDeleteVertexArrays(1, &VaoID);

glDeleteVertexArrays ist sozusagen das genaue Gegenstück zu glGenVertexArrays.

Schlusswort

Das war schon alles! War doch gar nicht so schwer, oder? Falls dennoch Fragen offen geblieben sind, oder ihr etwas nicht versteht, dürft, nein sollt, ihr im Forum gerne eure Fragen stellen. Ebenfalls freuen würde ich mich über Feedback zu diesem Tutorial oder zum Wiki allgemein. Dazu einfach einen Beitrag ins Unterforum "Feedback" schreiben. In diesem Sinne: Vielen Dank fürs Lesen und Füße zurück! :)

Links

http://www.opengl.org/wiki/Vertex_Specification#Vertex_Array_Object
ARB_vertex_array_object (Extension, um VAOs bereits in OpenGL 2.1 zu nutzen)