VBO ohne glInterleavedArrays: Unterschied zwischen den Versionen

Aus DGL Wiki
Wechseln zu: Navigation, Suche
K (4. Nachher reinen Tisch machen: Klarstellung, was "Reinen Tisch machen" bedeutet)
(4. Nachher reinen Tisch machen)
 
(4 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 16: Zeile 16:
 
Typen
 
Typen
 
<source lang="pascal">
 
<source lang="pascal">
   TVector4D = Packed Record X,Y,Z,W: Single; End;  
+
type
 +
   TVector4f = packed record
 +
    X, Y, Z, W: Single;  
 +
  end;  
 
    
 
    
   TVBOVertex = Packed Record  
+
   TVBOVertex = packed record  
 
     Position,
 
     Position,
 
     Color,
 
     Color,
     Empty: TVector4D;
+
     Empty: TVector4f;
   End;
+
   end;
 
    
 
    
   TVBOBuffer = Array Of TVBOVertex;
+
   TVBOBuffer   = array of TVBOVertex;
   TIndexBuffer = Array Of Word;
+
   TIndexBuffer = array of Word;
 
</source>
 
</source>
  
 
Konstanten
 
Konstanten
 
<source lang="pascal">
 
<source lang="pascal">
 +
const
 
   POS_OFFSET  =  Pointer(0);                  // Position-Offset als Zeiger
 
   POS_OFFSET  =  Pointer(0);                  // Position-Offset als Zeiger
 
   COL_OFFSET  =  Pointer(SizeOf(TVector4D));  // Color-Offset als Zeiger
 
   COL_OFFSET  =  Pointer(SizeOf(TVector4D));  // Color-Offset als Zeiger
Zeile 37: Zeile 41:
 
Variablen
 
Variablen
 
<source lang="pascal">
 
<source lang="pascal">
   VBOBuffer: TVBOBuffer;         // VBO-Daten
+
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;       // Opengl-"Handles" für VBO- und Indexpuffer
+
   IDVbo, IDIndex: Cardinal;     // Opengl-"Handles" für VBO- und Indexpuffer
 
</source>
 
</source>
  
Zeile 51: Zeile 56:
 
===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
+
   with VBOBuffer[0] 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 := -1.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 := +1.0;
   End;
+
      Y := +0.0;
   With VBOBuffer[2] Do
+
      Z := +0.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;
+
 
 +
   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 99: 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 106: 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 133: 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 141: 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 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). '''Ganz wichtig''': die Offsets müssen an die gl*Pointer-Funktionen als Zeiger übergeben werden, siehe oben Definition der Offsets.
+
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 148: Zeile 180:
  
 
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===
 
===4. Nachher reinen Tisch machen===
 
<source lang="pascal">
 
<source lang="pascal">
   SetLength(IndexBuffer,0);
+
   SetLength(IndexBuffer, 0);
   SetLength(VBOBuffer,0);
+
   SetLength(VBOBuffer, 0);
   glDeleteBuffers(1,@IDVbo);
+
   glDeleteBuffers(1, @IDVbo);
   glDeleteBuffers(1,@IDIndex);
+
   glDeleteBuffers(1, @IDIndex);
 
</source>
 
</source>
  

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.


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.