VBO ohne glInterleavedArrays: Unterschied zwischen den Versionen
Flash (Diskussion | Beiträge) |
(→4. Nachher reinen Tisch machen) |
||
(9 dazwischenliegende Versionen von 3 Benutzern werden nicht angezeigt) | |||
Zeile 1: | Zeile 1: | ||
(Voraussetzung: mindestens OpenGL 1.5) | (Voraussetzung: mindestens OpenGL 1.5) | ||
− | "VBO" ist die Abkürzung von "Vertex Buffer Object" und ist eine Möglichkeit der Darstellung von Objekten mit OpenGL | + | "VBO" ist die Abkürzung von "Vertex Buffer Object" und ist eine Möglichkeit der Darstellung von Objekten mit OpenGL. |
− | Es gibt | + | Es gibt mehrere Möglichkeiten, einen VBO zu verwenden. Die Variante, die ich unten erkläre, ist also nicht die einzige. |
Zeile 16: | Zeile 16: | ||
Typen | Typen | ||
<source lang="pascal"> | <source lang="pascal"> | ||
− | + | type | |
+ | TVector4f = packed record | ||
+ | X, Y, Z, W: Single; | ||
+ | end; | ||
− | TVBOVertex = | + | TVBOVertex = packed record |
Position, | Position, | ||
Color, | Color, | ||
− | Empty: | + | Empty: TVector4f; |
− | + | end; | |
− | TVBOBuffer = | + | TVBOBuffer = array of TVBOVertex; |
− | TIndexBuffer = | + | TIndexBuffer = array of Word; |
</source> | </source> | ||
Konstanten | Konstanten | ||
<source lang="pascal"> | <source lang="pascal"> | ||
− | POS_OFFSET = 0; | + | const |
− | COL_OFFSET = SizeOf(TVector4D); | + | POS_OFFSET = Pointer(0); // Position-Offset als Zeiger |
+ | COL_OFFSET = Pointer(SizeOf(TVector4D)); // Color-Offset als Zeiger | ||
STRIDE = SizeOf(TVBOVertex); | STRIDE = SizeOf(TVBOVertex); | ||
</source> | </source> | ||
Zeile 37: | Zeile 41: | ||
Variablen | Variablen | ||
<source lang="pascal"> | <source lang="pascal"> | ||
− | VBOBuffer: TVBOBuffer; | + | var |
+ | VBOBuffer: TVBOBuffer; // VBO-Daten | ||
IndexBuffer: TIndexBuffer; // VBO-Indices | IndexBuffer: TIndexBuffer; // VBO-Indices | ||
VertexBufSize, // VBO-Daten: Puffergröße | VertexBufSize, // VBO-Daten: Puffergröße | ||
IndexBufSize, // VBO-Indices: Puffergröße | IndexBufSize, // VBO-Indices: Puffergröße | ||
IndexCount, // Anzahl der Indices | IndexCount, // Anzahl der Indices | ||
− | IDVbo,IDIndex: Cardinal; | + | IDVbo, IDIndex: Cardinal; // Opengl-"Handles" für VBO- und Indexpuffer |
</source> | </source> | ||
Zeile 48: | Zeile 53: | ||
Ihr könnt die Typen definieren, wie Ihr wollt, aber es ergeben sich daraus Konsequenzen. Die Datendeklaration hat unmittelbaren Einfluss auf den untenstehenden Initialisierungs- und Rendercode. Wenn also der Datentyp geändert wird, muss man den ganzen untenstehenden Code auf notwendige Anpassungen untersuchen. Wie sich gezeigt hat, habe ich dabei immer wieder das eine oder andere vergessen. :( | Ihr könnt die Typen definieren, wie Ihr wollt, aber es ergeben sich daraus Konsequenzen. Die Datendeklaration hat unmittelbaren Einfluss auf den untenstehenden Initialisierungs- und Rendercode. Wenn also der Datentyp geändert wird, muss man den ganzen untenstehenden Code auf notwendige Anpassungen untersuchen. Wie sich gezeigt hat, habe ich dabei immer wieder das eine oder andere vergessen. :( | ||
− | |||
− | |||
− | |||
===2. Dann das Hochladen der Daten aus dem Hauptspeicher in den Grafikkartenspeicher=== | ===2. Dann das Hochladen der Daten aus dem Hauptspeicher in den Grafikkartenspeicher=== | ||
<source lang="pascal"> | <source lang="pascal"> | ||
− | IndexCount:= 3; | + | IndexCount := 3; |
− | VertexBufSize:= IndexCount*SizeOf(TVBOVertex); | + | VertexBufSize := IndexCount * SizeOf(TVBOVertex); |
− | IndexBufSize:= IndexCount*SizeOf(Word); | + | IndexBufSize := IndexCount * SizeOf(Word); |
</source> | </source> | ||
Vertexpuffer erzeugen,initialisieren und mit Daten bestücken | Vertexpuffer erzeugen,initialisieren und mit Daten bestücken | ||
<source lang="pascal"> | <source lang="pascal"> | ||
− | SetLength(VBOBuffer,IndexCount); | + | SetLength(VBOBuffer, IndexCount); |
− | FillChar(VBOBuffer[0],Length(VBOBuffer)*SizeOf(TVBOVertex),#0); | + | FillChar(VBOBuffer[0], Length(VBOBuffer) * SizeOf(TVBOVertex), #0); |
+ | |||
+ | with VBOBuffer[0] do begin | ||
+ | with Position do begin | ||
+ | X := -1.0; | ||
+ | Y := -1.0; | ||
+ | Z := +0.0; | ||
+ | W := +1.0; | ||
+ | end; | ||
+ | with Color do begin | ||
+ | X := +1.0; | ||
+ | Y := +0.0; | ||
+ | Z := +0.0; | ||
+ | W := +1.0; | ||
+ | end; | ||
+ | end; | ||
− | + | with VBOBuffer[1] do begin | |
− | + | with Position do begin | |
− | + | X := +1.0; | |
− | + | Y := -1.0; | |
− | + | Z := +0.0; | |
− | + | W := +1.0; | |
− | + | end; | |
− | + | with Color do begin | |
− | + | X := +0.0; | |
− | + | Y := +1.0; | |
− | + | Z := +0.0; | |
− | + | W := +1.0; | |
− | + | end; | |
− | + | end; | |
− | + | ||
+ | with VBOBuffer[2] do begin | ||
+ | with Position do begin | ||
+ | X := +0.0; | ||
+ | Y := +1.0; | ||
+ | Z := +0.0; | ||
+ | W := +1.0; | ||
+ | end; | ||
+ | with Color do begin | ||
+ | X := +0.0; | ||
+ | Y := +0.0; | ||
+ | Z := +1.0; | ||
+ | W := +1.0; | ||
+ | end; | ||
+ | end; | ||
</source> | </source> | ||
Vertexdaten hochladen | Vertexdaten hochladen | ||
<source lang="pascal"> | <source lang="pascal"> | ||
− | glGenBuffers(1,@IDVbo); | + | glGenBuffers(1, @IDVbo); |
glBindBuffer(GL_ARRAY_BUFFER, IDVbo); | glBindBuffer(GL_ARRAY_BUFFER, IDVbo); | ||
− | glBufferData(GL_ARRAY_BUFFER,VertexBufSize,@VBOBuffer[0],GL_STATIC_DRAW); | + | glBufferData(GL_ARRAY_BUFFER, VertexBufSize, @VBOBuffer[0], GL_STATIC_DRAW); |
</source> | </source> | ||
Dasselbe mit dem Indexpuffer | Dasselbe mit dem Indexpuffer | ||
<source lang="pascal"> | <source lang="pascal"> | ||
− | SetLength(IndexBuffer,IndexCount); | + | SetLength(IndexBuffer, IndexCount); |
− | IndexBuffer[0]:= 0; | + | IndexBuffer[0] := 0; |
− | IndexBuffer[1]:= 1; | + | IndexBuffer[1] := 1; |
− | IndexBuffer[2]:= 2; | + | IndexBuffer[2] := 2; |
</source> | </source> | ||
Zeile 102: | Zeile 133: | ||
glGenBuffers(1,@IDIndex); | glGenBuffers(1,@IDIndex); | ||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IDIndex); | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IDIndex); | ||
− | glBufferData(GL_ELEMENT_ARRAY_BUFFER,IndexBufSize,@IndexBuffer[0],GL_STATIC_DRAW); | + | glBufferData(GL_ELEMENT_ARRAY_BUFFER, IndexBufSize,@IndexBuffer[0], GL_STATIC_DRAW); |
</source> | </source> | ||
Zeile 109: | Zeile 140: | ||
Abgesehen von der Entscheidung, welchen Datentyp man nehmen soll, ist das der einfachere Teil des Ganzen. Und damit es nicht ganz trivial wird, habe ich noch einen winzigen Indexbuffer definiert, damit ich zum Zeichnen glDrawElements verwenden kann. | Abgesehen von der Entscheidung, welchen Datentyp man nehmen soll, ist das der einfachere Teil des Ganzen. Und damit es nicht ganz trivial wird, habe ich noch einen winzigen Indexbuffer definiert, damit ich zum Zeichnen glDrawElements verwenden kann. | ||
− | In den Beispielen, die man im Web finden kann, gibt es außerdem noch die Variante, wo glBufferdata nur zur Definition der Puffer-Parameter benutzt wird, aber für das Hochladen der Daten auf die Grafikkarte wird glBufferSubdata verwendet. Damit kann man entweder den ganzen Puffer oder auch nur Teile davon neu beschicken. | + | In den Beispielen, die man im Web finden kann, gibt es außerdem noch die Variante, wo glBufferdata nur zur Definition der Puffer-Parameter benutzt wird, aber für das Hochladen der Daten auf die Grafikkarte wird glBufferSubdata verwendet. Damit kann man entweder den ganzen Puffer oder auch nur Teile davon neu beschicken. |
− | |||
− | |||
===3. Der Rendercode=== | ===3. Der Rendercode=== | ||
Zeile 136: | Zeile 165: | ||
glEnableClientState(GL_COLOR_ARRAY); | glEnableClientState(GL_COLOR_ARRAY); | ||
− | glDrawElements(GL_TRIANGLES,IndexCount,GL_UNSIGNED_SHORT,NIL); | + | glDrawElements(GL_TRIANGLES, IndexCount, GL_UNSIGNED_SHORT, NIL); |
glDisableClientState(GL_COLOR_ARRAY); | glDisableClientState(GL_COLOR_ARRAY); | ||
Zeile 144: | Zeile 173: | ||
Über das Binden der Puffer gibt es eigentlich nicht viel zu sagen. Die OpenGL-Pufferobjekte sind alle ziemlich ähnlich. | Über das Binden der Puffer gibt es eigentlich nicht viel zu sagen. Die OpenGL-Pufferobjekte sind alle ziemlich ähnlich. | ||
− | Aber bei den Zeigern kann man eine Menge falsch machen. Etliche Leute im Netz haben z.B. nachgeforscht, wie man den Parameter "Stride" zu interpretieren hat. Ich habe Stride ganz oben im | + | Aber bei den Zeigern kann man eine Menge falsch machen. Etliche Leute im Netz haben z.B. nachgeforscht, wie man den Parameter "Stride" zu interpretieren hat. Ich habe Stride ganz oben im Definitionsteil als Konstante definiert: STRIDE = SizeOf(TVBOVertex); damit ist klar, dass hier die gesamte Blockgröße des Vertex gemeint ist. Die "Offset"-Parameter habe ich als Konstanten definiert weil sie von der Struktur des Vertex abhängen und daher vermutlich nicht dauernd geändert werden. "Offset" ist hier genau das richtige Wort. Zwischen dem Beginn des Vertex und der Color liegt eine Position mit dem Typ "TVector4D", daher ist der Offset der Color SizeOf(TVector4D). '''Ganz wichtig''': die Offsets müssen an die gl*Pointer-Funktionen als Zeiger übergeben werden, siehe oben Definition der Offsets. |
Ich habe hier zwar kein nächstes Vertex-Element, aber wenn ich eines hätte, dann wäre dessen Offset COL_OFFSET+SizeOf(TVector4D) oder auch in Pseudoschreibweise: Offset(Color)+Size(Color). | Ich habe hier zwar kein nächstes Vertex-Element, aber wenn ich eines hätte, dann wäre dessen Offset COL_OFFSET+SizeOf(TVector4D) oder auch in Pseudoschreibweise: Offset(Color)+Size(Color). | ||
Zeile 152: | Zeile 181: | ||
So, jetzt kann man endlich zeichnen. Die Typangabe im glDrawElements beschreibt den Index und der letzte Parameter muss "NIL" sein, denn in unserem Fall ist dieser Parameter überflüssig (er wird in einem anderen Zusammenhang zum Nachladen der Daten benutzt). | So, jetzt kann man endlich zeichnen. Die Typangabe im glDrawElements beschreibt den Index und der letzte Parameter muss "NIL" sein, denn in unserem Fall ist dieser Parameter überflüssig (er wird in einem anderen Zusammenhang zum Nachladen der Daten benutzt). | ||
+ | ===4. Nachher reinen Tisch machen=== | ||
+ | <source lang="pascal"> | ||
+ | SetLength(IndexBuffer, 0); | ||
+ | SetLength(VBOBuffer, 0); | ||
+ | glDeleteBuffers(1, @IDVbo); | ||
+ | glDeleteBuffers(1, @IDIndex); | ||
+ | </source> | ||
+ | |||
+ | Am Ende werden die Daten freigegeben, sowohl im RAM (mit SetLength(Buffer,0)) als auch im GRAM (mit glDeleteBuffers). | ||
− | + | Und, naja, wenn man etwas auf eine solche Art zeichnet, sollte es natürlich nicht grade ein Dreieck sein. Das wäre nämlich so, als ob man um die Ecke in den Supermarkt gehen wollte und dazu einen Jumbojet benutzt. :) | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
+ | Insgesamt fand ich am Ende, dass ich Euch vielleicht ein wenig Zeit und Frust ersparen kann, wenn ich das Ganze ins DGL-Wiki stelle. | ||
− | + | Ich hoffe, es hilft.<br> | |
+ | Viele Grüße,<br> | ||
+ | [[Benutzer:Traude|Traude]] | ||
− | |||
− | + | ''Fragen und Verbesserungsvorschläge wie immer ins Feedback-Forum bei DelphiGL.com.'' | |
− | |||
− |
Aktuelle Version vom 25. August 2018, 20:21 Uhr
(Voraussetzung: mindestens OpenGL 1.5)
"VBO" ist die Abkürzung von "Vertex Buffer Object" und ist eine Möglichkeit der Darstellung von Objekten mit OpenGL.
Es gibt mehrere Möglichkeiten, einen VBO zu verwenden. Die Variante, die ich unten erkläre, ist also nicht die einzige.
Inhaltsverzeichnis
VBO mit frei definierter Datenstruktur, ohne glInterleavedArrays
Der untenstehende Pascal-Code zeichnet das Opengl-Dreieck mit einem VBO. Ich hatte beim Erzeugen dieses Code-Stücks eine steile Lernkurve und jede Menge Frust, denn beim Ändern einzelner Teile sind dann natürlich auch korrespondierende Teile anderswo zu ändern, aber da kann man leicht etwas übersehen, speziell wenn es gegen vier Uhr morgens geht. :)
1. Zunächst die globalen Deklarationen
Typen
type
TVector4f = packed record
X, Y, Z, W: Single;
end;
TVBOVertex = packed record
Position,
Color,
Empty: TVector4f;
end;
TVBOBuffer = array of TVBOVertex;
TIndexBuffer = array of Word;
Konstanten
const
POS_OFFSET = Pointer(0); // Position-Offset als Zeiger
COL_OFFSET = Pointer(SizeOf(TVector4D)); // Color-Offset als Zeiger
STRIDE = SizeOf(TVBOVertex);
Variablen
var
VBOBuffer: TVBOBuffer; // VBO-Daten
IndexBuffer: TIndexBuffer; // VBO-Indices
VertexBufSize, // VBO-Daten: Puffergröße
IndexBufSize, // VBO-Indices: Puffergröße
IndexCount, // Anzahl der Indices
IDVbo, IDIndex: Cardinal; // Opengl-"Handles" für VBO- und Indexpuffer
Bei dieser Variante des VBO kann man sein eigenes Vertex definieren und ist nicht auf vorgegebene Datenstrukturen beschränkt wie bei den glInterleavedArrays. Der "Empty"-Vektor im TVBOVertex soll darauf hinweisen, dass man vorgegebene Vertexattribute mit benutzerdefinierten mischen kann. Das wird hier aber nicht behandelt.
Ihr könnt die Typen definieren, wie Ihr wollt, aber es ergeben sich daraus Konsequenzen. Die Datendeklaration hat unmittelbaren Einfluss auf den untenstehenden Initialisierungs- und Rendercode. Wenn also der Datentyp geändert wird, muss man den ganzen untenstehenden Code auf notwendige Anpassungen untersuchen. Wie sich gezeigt hat, habe ich dabei immer wieder das eine oder andere vergessen. :(
2. Dann das Hochladen der Daten aus dem Hauptspeicher in den Grafikkartenspeicher
IndexCount := 3;
VertexBufSize := IndexCount * SizeOf(TVBOVertex);
IndexBufSize := IndexCount * SizeOf(Word);
Vertexpuffer erzeugen,initialisieren und mit Daten bestücken
SetLength(VBOBuffer, IndexCount);
FillChar(VBOBuffer[0], Length(VBOBuffer) * SizeOf(TVBOVertex), #0);
with VBOBuffer[0] do begin
with Position do begin
X := -1.0;
Y := -1.0;
Z := +0.0;
W := +1.0;
end;
with Color do begin
X := +1.0;
Y := +0.0;
Z := +0.0;
W := +1.0;
end;
end;
with VBOBuffer[1] do begin
with Position do begin
X := +1.0;
Y := -1.0;
Z := +0.0;
W := +1.0;
end;
with Color do begin
X := +0.0;
Y := +1.0;
Z := +0.0;
W := +1.0;
end;
end;
with VBOBuffer[2] do begin
with Position do begin
X := +0.0;
Y := +1.0;
Z := +0.0;
W := +1.0;
end;
with Color do begin
X := +0.0;
Y := +0.0;
Z := +1.0;
W := +1.0;
end;
end;
Vertexdaten hochladen
glGenBuffers(1, @IDVbo);
glBindBuffer(GL_ARRAY_BUFFER, IDVbo);
glBufferData(GL_ARRAY_BUFFER, VertexBufSize, @VBOBuffer[0], GL_STATIC_DRAW);
Dasselbe mit dem Indexpuffer
SetLength(IndexBuffer, IndexCount);
IndexBuffer[0] := 0;
IndexBuffer[1] := 1;
IndexBuffer[2] := 2;
Indexdaten hochladen
glGenBuffers(1,@IDIndex);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IDIndex);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, IndexBufSize,@IndexBuffer[0], GL_STATIC_DRAW);
Ich habe hier für den VBO einen Record genommen, weil bei den Demos im Netz normalerweise immer Arrays benutzt werden. Der Recordtyp ist zusätzlich viel selbsterklärender als ein Array und man kann ihn auch noch durch treffende Namensgebung verbessern.
Abgesehen von der Entscheidung, welchen Datentyp man nehmen soll, ist das der einfachere Teil des Ganzen. Und damit es nicht ganz trivial wird, habe ich noch einen winzigen Indexbuffer definiert, damit ich zum Zeichnen glDrawElements verwenden kann.
In den Beispielen, die man im Web finden kann, gibt es außerdem noch die Variante, wo glBufferdata nur zur Definition der Puffer-Parameter benutzt wird, aber für das Hochladen der Daten auf die Grafikkarte wird glBufferSubdata verwendet. Damit kann man entweder den ganzen Puffer oder auch nur Teile davon neu beschicken.
3. Der Rendercode
Vertexpuffer binden
glBindBuffer(GL_ARRAY_BUFFER, IDVbo);
Nötige Zeiger setzen
glVertexPointer(4, GL_FLOAT, STRIDE, POS_OFFSET);
glColorPointer(4, GL_FLOAT, STRIDE, COL_OFFSET);
Indexpuffer binden
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IDIndex);
Zeichnen
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glDrawElements(GL_TRIANGLES, IndexCount, GL_UNSIGNED_SHORT, NIL);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
Über das Binden der Puffer gibt es eigentlich nicht viel zu sagen. Die OpenGL-Pufferobjekte sind alle ziemlich ähnlich.
Aber bei den Zeigern kann man eine Menge falsch machen. Etliche Leute im Netz haben z.B. nachgeforscht, wie man den Parameter "Stride" zu interpretieren hat. Ich habe Stride ganz oben im Definitionsteil als Konstante definiert: STRIDE = SizeOf(TVBOVertex); damit ist klar, dass hier die gesamte Blockgröße des Vertex gemeint ist. Die "Offset"-Parameter habe ich als Konstanten definiert weil sie von der Struktur des Vertex abhängen und daher vermutlich nicht dauernd geändert werden. "Offset" ist hier genau das richtige Wort. Zwischen dem Beginn des Vertex und der Color liegt eine Position mit dem Typ "TVector4D", daher ist der Offset der Color SizeOf(TVector4D). Ganz wichtig: die Offsets müssen an die gl*Pointer-Funktionen als Zeiger übergeben werden, siehe oben Definition der Offsets.
Ich habe hier zwar kein nächstes Vertex-Element, aber wenn ich eines hätte, dann wäre dessen Offset COL_OFFSET+SizeOf(TVector4D) oder auch in Pseudoschreibweise: Offset(Color)+Size(Color).
Nach dem Binden des Indexpuffers muss man den VBO Client-seitig aktivieren, und zwar für jeden Attribut-Typ extra. Wenn Ihr also eine Texturkoordinate ergänzen wollt - dazu muss man natürlich die Vertex-Definition ändern -, dann vergeßt bloß nicht das "glEnableClientState(GL_TEXTURE_COORD_ARRAY)" nachzutragen.
So, jetzt kann man endlich zeichnen. Die Typangabe im glDrawElements beschreibt den Index und der letzte Parameter muss "NIL" sein, denn in unserem Fall ist dieser Parameter überflüssig (er wird in einem anderen Zusammenhang zum Nachladen der Daten benutzt).
4. Nachher reinen Tisch machen
SetLength(IndexBuffer, 0);
SetLength(VBOBuffer, 0);
glDeleteBuffers(1, @IDVbo);
glDeleteBuffers(1, @IDIndex);
Am Ende werden die Daten freigegeben, sowohl im RAM (mit SetLength(Buffer,0)) als auch im GRAM (mit glDeleteBuffers).
Und, naja, wenn man etwas auf eine solche Art zeichnet, sollte es natürlich nicht grade ein Dreieck sein. Das wäre nämlich so, als ob man um die Ecke in den Supermarkt gehen wollte und dazu einen Jumbojet benutzt. :)
Insgesamt fand ich am Ende, dass ich Euch vielleicht ein wenig Zeit und Frust ersparen kann, wenn ich das Ganze ins DGL-Wiki stelle.
Ich hoffe, es hilft.
Viele Grüße,
Traude
Fragen und Verbesserungsvorschläge wie immer ins Feedback-Forum bei DelphiGL.com.