VBO ohne glInterleavedArrays: Unterschied zwischen den Versionen

Aus DGL Wiki
Wechseln zu: Navigation, Suche
K (Tippfehler eliminiert)
(4. Nachher reinen Tisch machen)
 
(12 dazwischenliegende Versionen von 4 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. In den OpenGL-Versionen ab Nummer 3 ist es die '''einzige''' Möglichkeit.
+
"VBO" ist die Abkürzung von "Vertex Buffer Object" und ist eine Möglichkeit der Darstellung von Objekten mit OpenGL.
  
Es gibt aber mehrere Möglichkeiten, einen VBO zu verwenden. Die Variante, die ich unten erkläre, ist also nicht die einzige.
+
Es gibt mehrere Möglichkeiten, einen VBO zu verwenden. Die Variante, die ich unten erkläre, ist also nicht die einzige.
  
  
VBO mit frei definierter Datenstruktur, ohne glInterleavedArrays  
+
==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. :)
 
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. :)
Zeile 12: Zeile 12:
  
  
'''1. Zunächst die globalen Deklarationen'''
+
===1. Zunächst die globalen Deklarationen===
  
 
Typen
 
Typen
  TVector4D = Packed Record X,Y,Z,W: Single; End;  
+
<source lang="pascal">
 
+
type
  TVBOVertex = Packed Record  
+
  TVector4f = packed record
      Position,
+
    X, Y, Z, W: Single;  
      Color,
+
  end;  
      Empty: TVector4D;
+
 
  End;
+
  TVBOVertex = packed record  
 
+
    Position,
  TVBOBuffer = Array Of TVBOVertex;
+
    Color,
  TIndexBuffer = Array Of Word;
+
    Empty: TVector4f;
 +
  end;
 +
 
 +
  TVBOBuffer   = array of TVBOVertex;
 +
  TIndexBuffer = array of Word;
 +
</source>
  
 
Konstanten
 
Konstanten
  POS_OFFSET  =  0;
+
<source lang="pascal">
  COL_OFFSET  =  SizeOf(TVector4D);
+
const
  STRIDE      =  SizeOf(TVBOVertex);
+
  POS_OFFSET  =  Pointer(0);                 // Position-Offset als Zeiger
 +
  COL_OFFSET  =  Pointer(SizeOf(TVector4D)); // Color-Offset als Zeiger
 +
  STRIDE      =  SizeOf(TVBOVertex);
 +
</source>
  
 
Variablen
 
Variablen
  VBOBuffer: TVBOBuffer;         // VBO-Daten
+
<source lang="pascal">
  IndexBuffer: TIndexBuffer;    // VBO-Indices
+
var
  VertexBufSize,                // VBO-Daten: Puffergröße
+
  VBOBuffer:   TVBOBuffer;       // VBO-Daten
  IndexBufSize,                  // VBO-Indices: Puffergröße  
+
  IndexBuffer: TIndexBuffer;    // VBO-Indices
  IndexCount,                    // Anzahl der Indices
+
  VertexBufSize,                // VBO-Daten: Puffergröße
  IDVbo,IDIndex: Cardinal;       // Opengl-"Handles" für VBO- und Indexpuffer
+
  IndexBufSize,                  // VBO-Indices: Puffergröße  
 +
  IndexCount,                    // Anzahl der Indices
 +
  IDVbo, IDIndex: Cardinal;     // Opengl-"Handles" für VBO- und Indexpuffer
 +
</source>
  
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 benutzerdefierten mischen kann. Das wird hier aber nicht behandelt.  
+
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. :(
 
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===
 +
<source lang="pascal">
 +
  IndexCount :=  3;
  
 +
  VertexBufSize := IndexCount * SizeOf(TVBOVertex);
 +
  IndexBufSize  := IndexCount * SizeOf(Word);
 +
</source>
  
 +
Vertexpuffer erzeugen,initialisieren und mit Daten bestücken
 +
<source lang="pascal">
 +
  SetLength(VBOBuffer, IndexCount);
 +
  FillChar(VBOBuffer[0], Length(VBOBuffer) * SizeOf(TVBOVertex), #0);
  
'''2. Dann das Hochladen der Daten aus dem Hauptspeicher in den Grafikkartenspeicher
+
  with VBOBuffer[0] do begin
'''
+
    with Position do begin
  IndexCount:= 3;
+
      X := -1.0;
 
+
      Y := -1.0;
  VertexBufSize:= IndexCount*SizeOf(TVBOVertex);
+
      Z := +0.0;
  IndexBufSize:= IndexCount*SizeOf(Word);
+
      W := +1.0;
 +
    end;
 +
    with Color do begin
 +
      X := +1.0;
 +
      Y := +0.0;
 +
      Z := +0.0;
 +
      W := +1.0;
 +
    end;
 +
  end;
  
Vertexpuffer erzeugen,initialisieren und mit Daten bestücken
+
  with VBOBuffer[1] do begin
  SetLength(VBOBuffer,IndexCount);
+
    with Position do begin
  FillChar(VBOBuffer[0],Length(VBOBuffer)*SizeOf(TVBOVertex),#0);
+
      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[0] Do
+
  with VBOBuffer[2] do begin
  Begin
+
    with Position do begin
       With Position Do Begin X:=-1.0; Y:=-1.0; Z:=+0.0; W:=+1.0; End;
+
       X := +0.0;
       With Color    Do Begin X:=+1.0; Y:=+0.0; Z:=+0.0; W:=+1.0; End;
+
       Y := +1.0;
  End;
+
       Z := +0.0;
  With VBOBuffer[1] Do
+
       W := +1.0;
  Begin
+
    end;
       With Position Do Begin X:=+1.0; Y:=-1.0; Z:=+0.0; W:=+1.0; End;
+
    with Color do begin
       With Color    Do Begin X:=+0.0; Y:=+1.0; Z:=+0.0; W:=+1.0; End;
+
       X := +0.0;
  End;
+
      Y := +0.0;
  With VBOBuffer[2] Do
+
       Z := +1.0;
  Begin
+
      W := +1.0;
       With Position Do Begin X:=+0.0; Y:=+1.0; Z:=+0.0; W:=+1.0; End;
+
    end;
       With Color    Do Begin X:=+0.0; Y:=+0.0; Z:=+1.0; W:=+1.0; End;
+
  end;
  End;
+
</source>
  
 
Vertexdaten hochladen
 
Vertexdaten hochladen
  glGenBuffers(1,@IDVbo);
+
<source lang="pascal">
  glBindBuffer(GL_ARRAY_BUFFER, IDVbo);
+
  glGenBuffers(1, @IDVbo);
  glBufferData(GL_ARRAY_BUFFER,VertexBufSize,@VBOBuffer[0],GL_STATIC_DRAW);
+
  glBindBuffer(GL_ARRAY_BUFFER, IDVbo);
 +
  glBufferData(GL_ARRAY_BUFFER, VertexBufSize, @VBOBuffer[0], GL_STATIC_DRAW);
 +
</source>
  
 
Dasselbe mit dem Indexpuffer
 
Dasselbe mit dem Indexpuffer
  SetLength(IndexBuffer,IndexCount);
+
<source lang="pascal">
 +
  SetLength(IndexBuffer, IndexCount);
  
  IndexBuffer[0]:= 0;
+
  IndexBuffer[0] := 0;
  IndexBuffer[1]:= 1;
+
  IndexBuffer[1] := 1;
  IndexBuffer[2]:= 2;
+
  IndexBuffer[2] := 2;
 +
</source>
  
 
Indexdaten hochladen
 
Indexdaten hochladen
  glGenBuffers(1,@IDIndex);
+
<source lang="pascal">
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IDIndex);
+
  glGenBuffers(1,@IDIndex);
  glBufferData(GL_ELEMENT_ARRAY_BUFFER,IndexBufSize,@IndexBuffer[0],GL_STATIC_DRAW);
+
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IDIndex);
 +
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, IndexBufSize,@IndexBuffer[0], GL_STATIC_DRAW);
 +
</source>
  
 
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.  
 
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.  
Zeile 94: 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===
  
 
Vertexpuffer binden
 
Vertexpuffer binden
  glBindBuffer(GL_ARRAY_BUFFER, IDVbo);
+
<source lang="pascal">
 +
  glBindBuffer(GL_ARRAY_BUFFER, IDVbo);
 +
</source>
  
 
Nötige Zeiger setzen
 
Nötige Zeiger setzen
  glVertexPointer(4, GL_FLOAT, STRIDE, POS_OFFSET);
+
<source lang="pascal">
  glColorPointer(4, GL_FLOAT, STRIDE, COL_OFFSET);
+
  glVertexPointer(4, GL_FLOAT, STRIDE, POS_OFFSET);
 +
  glColorPointer(4, GL_FLOAT, STRIDE, COL_OFFSET);
 +
</source>
  
 
Indexpuffer binden
 
Indexpuffer binden
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IDIndex);
+
<source lang="pascal">
 +
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IDIndex);
 +
</source>
  
 
Zeichnen
 
Zeichnen
  glEnableClientState(GL_VERTEX_ARRAY);
+
<source lang="pascal">
  glEnableClientState(GL_COLOR_ARRAY);
+
  glEnableClientState(GL_VERTEX_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);
  glDisableClientState(GL_VERTEX_ARRAY);
+
  glDisableClientState(GL_VERTEX_ARRAY);
 +
</source>
  
 
Ü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 Defnitionsteil 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).  
+
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 129: 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).
  
  
'''4. Nacher reinen Tisch machen'''
+
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. :)  
  Finalize(IndexBuffer);
 
  Finalize(VBOBuffer);
 
  glDeleteBuffers(1,@IDVbo);
 
  glDeleteBuffers(1,@IDIndex);
 
  
 +
Insgesamt fand ich am Ende, dass ich Euch vielleicht ein wenig Zeit und Frust ersparen kann, wenn ich das Ganze ins DGL-Wiki stelle.
  
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. :) Allerdings lässt uns OpenGL keine Wahl, denn ab OpenGL 3 gibt es keine anderen Möglichkeiten mehr. Also müssen wir uns mit dem Jumbojet anfreunden.
+
Ich hoffe, es hilft.<br>
 +
Viele Grüße,<br>
 +
[[Benutzer:Traude|Traude]]
  
Insgesamt fand ich am Ende, dass ich Euch vielleicht ein wenig Zeit und Frust ersparen kann, wenn ich das Ganze ins WIKI stelle.
 
  
Ich hoffe, es hilft.
+
''Fragen und Verbesserungsvorschläge wie immer ins Feedback-Forum bei DelphiGL.com.''
Viele Grüße,
 
Traude
 

Aktuelle Version vom 25. August 2018, 21: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.


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.