Vertex Array Object: Unterschied zwischen den Versionen
K ("Siehe auch"-Abschnitt ergänzt) |
K (Zwei Vertipper korrigiert) |
||
Zeile 13: | Zeile 13: | ||
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. | 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 | + | Ebenfalls wichtig: Obwohl im VAO gespeichert wird, aus welchem VBO die Daten beim Rendern entnommen werden sollen, führt das Binden des 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. |
<br> | <br> | ||
==Anwendung== | ==Anwendung== | ||
Zeile 38: | Zeile 38: | ||
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(TVertex), 24); // 2 floats als Textur-Koordinaten</cpp> | glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(TVertex), 24); // 2 floats als Textur-Koordinaten</cpp> | ||
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. | 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 | + | 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 |
<cpp>glBindVertexArray(0);</cpp> | <cpp>glBindVertexArray(0);</cpp> | ||
Damit sichern wir, dass sich nachfolgende Aufrufe von ''glVertexAttribPointer'' nicht auf unser VAO auswirken. | Damit sichern wir, dass sich nachfolgende Aufrufe von ''glVertexAttribPointer'' nicht auf unser VAO auswirken. |
Version vom 20. August 2013, 15:03 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.
Inhaltsverzeichnis
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 des 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! :)
Siehe auch
Tutorial Vertexbufferobject
VBO ohne glInterleavedArrays
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)