Vertex Buffer Objects (VBO)
Vertex Buffer Objects (VBO) sind Speicherbereiche mit speziellen Inhalten die von einer Anwendung auf die Grafikkarte übertragen werden können. Dadurch, dass sie dann direkt auf der Grafikkarte verfügbar sind ist der Zugriff auf diese Daten durch die Grafikkarte sehr schnell. VBOs bieten also große Performancevorteile.
Für diesen Artikel betrachten wir zwei Arten von VBOs: GL_ARRAY_BUFFER_ARB für Geometriedaten wie Vertices, Normal- oder Texturkoordinaten und GL_ELEMENT_ARRAY_BUFFER_ARB für Indexdaten. Über die Indexdaten werden die Geometriedaten referenziert und damit werden dann die grafischen Elemente gezeichnet.
Erstellen und Nutzen von VBOs
Um VBOs verwenden zu können müssen zuerst IDs für sie erstellt werden:
vboBuffers = IntBuffer.allocate(2);
/* Die Buffer generieren */
gl.glGenBuffersARB(2, vboBuffers);
/* Nun haben wir in vboBuffers.get(0) und .get(1) die Ids der beiden Buffer */
Anschließend bindet man diese IDs an den Buffer-Typ der verwendet werden soll:
gl.glBindBufferARB(GL.GL_ELEMENT_ARRAY_BUFFER_ARB, vboBuffers.get(1));
Dadurch weiß OpenGL nun eindeutig welcher Speicherbereich verwendet werden soll wenn wir nun einen der beiden Buffer-Typen ansprechen. Das tun wir auch direkt indem wir die Inhalte füllen:
Im Beispielprogramm haben wir pro Vertex dessen Koordinaten und Normalenvektor die wir speichern müssen. Das sind 2 Vektoren zu je 3 Elemente zu je 4 Bytes (float-Typ): Daher übertragen wir numVertices*2*3*4 Bytes in den Buffer. Bei den Indizes werden wir Dreiecke zeichnen und daher müssen wir für jedes Dreieck 3 Punkte zu je 4 Bytes (integer-Typ) übertragen:
gl.glBufferDataARB(GL.GL_ELEMENT_ARRAY_BUFFER_ARB, numTriangles*3*4, indices, GL.GL_STATIC_DRAW_ARB);
Es ist übrigens nicht nötig die Buffer zu initialisieren - die Buffer werden auch erstellt wenn "null" für den Datenbereich angegeben wird. In diesem Fall muss man allerdings mit der Methode glMapBuffer() sich den Zugriff auf den angelegten Buffer erst besorgen um ihn dann füllen zu können. Das macht Sinn wenn die Daten sich ändern. In unserem einfachen Beispiel ist das jedoch nicht so.
Die Angabe GL_STATIC_DRAW_ARB ist ein Hinweis wie die Buffer verwendet werden- in diesem Fall werden sie einmal angegeben und dann für viele Zeichenoperationen lesend benutzt. Wenn sich die Bufferinhalte oft ändern empfiehlt sich die Angabe GL_DYNAMIC_DRAW_ARB oder gar GL_STREAM_DRAW_ARB. Dadurch ermöglicht man dem System den optimalen Speicher für die Buffer zu verwenden.
Um nun die Inhalt der VBOs zum Zeichnen zu verwenden informieren wir OpenGL darüber, dass wir einen Vertex und einen Normal-Pointer haben:
gl.glNormalPointer(GL.GL_FLOAT, 24, 0);
/* Die Vertices stehen hinter der Normalen (3 Elemente zu je 4 Bytes = offset 12) */
gl.glVertexPointer(3, GL.GL_FLOAT, 24, 12);
/* (insgesamt ist ein Punkt durch 24 Bytes definiert - 12 für die Normale und 12 für den Vertex)
Im Gegensatz zu den Aufrufen von glNormalPointer() und glVertexPointer() ohne die Benutzung von VBOs, wird als letzter Parameter hier kein Zeiger auf das Array selbst übergeben, sondern lediglich der Offset der zu benutzen ist um an die Datenelemente zu gelangen.
Zuletzt müssen diese Arrays noch aktiviert werden:
gl.glEnableClientState(GL.GL_NORMAL_ARRAY);
Nun sind alle Vorbereitungen getroffen um die VBOs zeichnen zu können! Das Zeichnen erfolgt am einfachsten mit folgendem Aufruf:
Die 0 als letzten Parameter sagt OpenGL dass VBOs verwendet werden sollen. OpenGL verwendet hierzu dann automatisch den Buffer der als GL_ELEMENT_ARRAY_BUFFER_ARB gebunden ist.
Nachdem die Buffer zum Zeichnen benutzt wurden schaltet man sie einfach wieder ab:
gl.glDisableClientState(GL.GL_NORMAL_ARRAY);
Nun können per glBindBuffer() andere Buffer zugeordnet werden um weitere VBOs zu zeichnen.
Um die Bindung der Buffer komplett aufzuheben übergibt man 0 an die glBindBuffer() Methode. Dadurch schaltet man auch das alte Vertex-Array Verfahren in OpenGL wieder ein.
gl.glBindBufferARB(GL.GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
Am Ende des Programms sollte man auf jeden Fall die IDs der Buffer ganz freigeben:
Performance
Der Aufruf von glVertexPointer und Freunden ist teuer in Bezug auf die Performance da hier laut [1] einiges an Setup durchgeführt wird. Daher sollten unnötige Aufrufe dieser Methode unbedingt vermieden werden. Günstig hingegen ist der Aufruf von glBindBuffer().
Auf keinen Fall sollte gl.glLightModeli(GL.GL_LIGHT_MODEL_TWO_SIDE,1); (kein two sided lighting) aktiviert werden - dadurch scheinen die VBOs komplett deaktiviert zu werden. Zumindest ergeben das meine Messungen. Es ist also besser geschlossene Meshes zu rendern als flache die zweiseitig beleuchtet werden sollen. Da die aktuellen Programme und Spiele aber ohnehin die Standard-OpenGL Beleuchtung nicht mehr verwenden sollte man nicht in Versuchung geraten dies zu nutzen :)
Verweise
[1] http://developer.nvidia.com/attach/6427
Beispielprogramm
Ein Beispielprogramm das die Möglichkeit bietet VBOs an- und abzuschalten und die damit erreichte Performance zu vergleichen findet ihr hier.
| Attachment | Size |
|---|---|
| VBODemo.java | 19.14 KB |
