Übersicht

OpenGL ist eine Sammlung nützlicher Klassen zur Erstellung von OpenGL Projekten mit FreePascal. Sie kapselt die wichtigsten OpenGL Objekte (wie z.B. RenderContext, Texturen, VertexArrays, FrameBuffers, …) in einfach zu handhabenden Klassen. Außerdem werden weitere nützliche Datentypen und Prozeduren rund um die Grafikprogrammierung zur Verfügung gestellt..

  1. dglOpenGL und dglOpenGLES
  2. Der RenderContext
  3. OpenGL Objekt Klassen

    1. TglcArrayBuffer
    2. TglcBitmap
    3. TglcFrameBufferObject
    4. TglcShaderProgram
    5. TglcVertexArrayObject
  4. Hilfs-Klassen

    1. TglcCamera
    2. TglcLight and TglcMaterial
  5. Hilfs-Typen

Links

1. dglOpenGL und dglOpenGLES

Die dglOpenGL.pas und die dglOpenGLES.pas enthalten all wichtigen Konstanten, Type und Funktionen um mit der OpenGL bzw. der OpenGLES Bibliothek zu arbeiten. Sie kümmern sich um das dynamische Laden der Funktionen aus der Bibliothek. Wenn wir ein reines OpenGL Programme schreiben würden, sind die zwei Units die einzigen die wir benötigen würden. Der Rest dieses Projektes macht es uns aber bei weitem einfacher.

Die dglOpenGL.pas und die dglOpenGLES.pas werden von der Delphi OpenGL Community gepflegt und weiter entwickelt.

2. Der Render Context

Die Render Context Klasse wird dazu genutzt um einen OpenGL Render Context zu erstellen. Das ist der erste Schritt den du machen must, bevor wir weiter an unserer OpenGL Anwendung arbeiten.

Als erstes müssen wir in Erfahrung bringen, welche Context Klasse wir nutzen müssen, weil jedes Betriebssystem (Windows, Linux, …) und jede Benutzeroberfläche (X11, Gtk2) nutzen anderen Routinen um einen Render Context zu erstellen. Zum Glück gibt es in diesem Projekt eine einfache Möglichkeit das zu tun.

var ContextClass: TglcContextClass;
ContextClass := TglcContext.GetPlatformClass();

Das zweite was wir für unseren Render Context benötigen ist ein entsprechendes Pixel Format für unseren Device Context. Das geht mit Hilfe dieses Projekts genau so einfach wie der vorherige Schritt.

var cpfs: TglcContextPixelFormatSettings;
cpfs := ContextClass.MakePF();

Jetzt habe wir alle Benötigten Daten um unseren Render Context Objekt zu erstellen. Dieser Schritt erzeugt noch nicht den eigentlichen Render Context, sondern nur das Objekt um den Render Context zu verwalten. Der Render Context lässt sich dann später über das Objekt erstellen.

var Context: TglcContext;
Context := ContextClass.Create(aWinControl, cpfs);

Jetzt ist alles bereit um es zu nutzen. Wir können jetzt den Render Context erstellen.

!Notiz: Dieser Schritt kann in einem separatem Thread ausgeführt werden. Dies ist z.B. sinnvoll wenn man Offscreen-Rendering in einem extra Thread nutzen will. Achtung: Der Render Context kann nur in dem Thread genutzt werden in dem erzeugt wurde.

Context.BuildContext();

Glückwunsch, wir haben jetzt einen gültigen Render Context und können nun damit beginnen Formen zu zeichnen. Wenn wir fertig damit sind sollten wir alles wieder entsprechend freigeben. Falls wir das nicht tun, ist das aber auch nich weiter schlimm, denn das TglcContext Objekt kümmert sich spätestens darum wenn es freigegeben wird.

Context.CloseContext();

Letzter Schritt: Context Objekt wieder freigeben.

FreeAndNil(Context);

3. OpenGL Objekt Klassen

3.1. TglcArrayBuffer

Array Buffer, auch bekannt als Vertex Buffer Objekte, werden genutzt um Vertex Daten im Grafikspeicher abzulegen. Das bringt beim Rendern einen erheblichen Performanz Vorteil. Bevor wir und um das Hochladen der Daten kümmern müssen wir beschreiben wie unsere Vertices aufgebaut sind. Wir definieren ein Vertex mit folgenden Eigenschaften: eine dreidimensionale Position, eine zweidimensionale Textur Koordinate und ein dreidimensionaler Normalen Vektor.

type
  TVertex = packed record
    pos: TgluVector3f; // Vertex Position
    tex: TgluVector2f; // Textur Koordinate
    nor: TgluVector3f; // Normalen Vektor
  end;
  PVertex = ^TVertex;

Jetzt da wir wissen wie unsere Daten definuert sind, können ein Array Buffer erstellen und die Daten in den Video Speicher hochladen. In diesem Beispiel nutzen wir indiziertes Vertex Rendering. Dabei wird jeder Vertex nur einmal im Speicher abgelegt. Zusätzlich legen wir ein Index Array Buffer an, der definiert welches Vertices gerendert werden sollen.

var
  VertexBuffer: TglcArrayBuffer;
  IndexBuffer: TglcArrayBuffer;
  p: Pointer;
 
  { Buffer Objekte erstellen }
  VertexBuffer := TglcArrayBuffer.Create(TglcBufferTarget.btArrayBuffer);
  IndexBuffer := TglcArrayBuffer.Create(TglcBufferTarget.btElementArrayBuffer);
 
  { Vertex Daten in Speicher schreiben }
  vBuffer.Bind;
 
  // 4 * sizeof(TVertex) Bytes Grafikspeicher reservieren und die Nutzung auf 'StaticDraw' setzen
  vBuffer.BufferData(4, SizeOf(TVertex), TglcBufferUsage.buStaticDraw, nil);
 
  // Video Speichern in unsere Anwendung mappen
  p := vBuffer.MapBuffer(TglcBufferAccess.baWriteOnly);
 
  // Daten in den Speicher schreiben
  try
    PVertex(p).pos := gluVertex3f(0.0, 0.0, 0.0);
    PVertex(p).tex := gluVertex2f(0.0, 0.5);
    PVertex(p).nor := gluVertex3f(0.0, 1.0, 0.0);
    inc(p, SizeOf(TVertex));
 
    { ... weitere Vertices folgen }
 
  finally
    vBuffer.UnmapBuffer;
    vBuffer.Unbind;
  end;
 
  { Index Buffer mit Daten füllen }
  iBuffer.Bind;
  iBuffer.BufferData(4, SizeOf(GLuint), TglcBufferUsage.buStaticDraw, nil);
  p := iBuffer.MapBuffer(TglcBufferAccess.baWriteOnly);
  try
    PGLuint(p) := 0;
    inc(p, sizeof(GLuint));
 
    { ... weiter Indizes folgen }
 
  finally
    iBuffer.UnmapBuffer;
    iBuffer.Unbind;
  end;

Nachdem alle Daten hochgeladen wurden, können wir unsere Vertices mit einem der glDraw Methoden zeichnen. Aber befor wir glDraw aufrufen können müssen wir OpenGL noch mitteilen wie genau die Daten im Speicher organisiert sind.

// Array Buffer für Render Vorgang binden
VertexBuffer.Bind;
IndexBuffer.Bind;
try
  // OpenGL mitteilen wo die Vertex Position liegt
  // 3 Fließkomma Werte mit Offset 0
  glEnableClientState(GL_VERTEX_ARRAY);
  glVertexPointer(3, GL_FLOAT, SizeOf(TVertex), Pointer(0));
 
  // OpenGL mitteilen wo die Texture Koordinaten liegen
  // 2 Fließkomma Werte mit Offset von 12 (= 3 * sizeof(GLfloat))
  glEnableClientState(GL_TEXTURE_COORD_ARRAY);
  glTexCoordPointer(2, GL_FLOAT, SizeOf(TVertex), Pointer(12));
 
  // OpenGL mitteilen wo die Normalen Vektorn liegen
  // Normal Vektor mit Offset von 20 (= 5 * sozeof(GLfloat))
  glEnableClientState(GL_NORMAL_ARRAY);
  glNormalPointer(GL_FLOAT, SizeOf(TVertex), Pointer(20));
 
  // OpenGl mitteilen unseren Index Buffer für indiziertes Rendern zu nutzen
  glEnableClientState(GL_INDEX_ARRAY);
  glIndexPointer(GL_INT, 0, nil);
 
  glDrawElements(GL_QUADS, iBuffer.DataCount, GL_UNSIGNED_INT, nil);
 
  glDisableClientState(GL_INDEX_ARRAY);
  glDisableClientState(GL_VERTEX_ARRAY);
  glDisableClientState(GL_TEXTURE_COORD_ARRAY);
  glDisableClientState(GL_NORMAL_ARRAY);
finally
  IndexBuffer.Unbind;
  VertexBuffer.Unbind;
end;

Das ist alles. Zum Schluss nicht vergessen die Buffer Objekte wieder freizugeben.

FreeAndNil(VertexBuffer);
FreeAndNil(IndexBuffer);

3.2. TglcBitmap

Die TglcBitmal Klassen werden genutzt um Texturen zu verwalten und zu rendern. Um eine Textur zu laden benötigen wir zwei Objekte. Ein TglcBitmapData Objekt um die Textur Daten aus einer Datei zu laden und in ein entsprechendes Format zu konvertieren und ein TglcBitmap2D Objekt um das OpenGl Textur Objekt zu verwalten, Daten in die Textur zu laden und die TExtur zum Rendern zu nutzen. Klingt doch einfach, oder nicht?

// Textur Objekt erstellen
var Texture: TglcBitmap2D;
Texture := TglcBitmap2D.Create;
 
// Textur Daten Objekt erstellen
var TextureData: TglcBitmapData;
TextureData := TglcBitmapData.Create;
try
  TextureData.LoadFromFile('./my-texture.png');
  // Daten konvertieren wenn sie in keinem der unterstützen Formate vorliegen
  if (TextureData.Format <> TextureData.FormatDescriptor.OpenGLFormat) then
  TextureData.ConvertTo(TextureData.FormatDescriptor.OpenGLFormat);
  Texture.UploadData(TextureData);
finally
  FreeAndNil(TextureData);
end;

Wie wir sehen kann das Textur Daten Objekt freigegeben werden, sobald die Daten an die Textur übergeben wurden. Der Speicher wird nicht länger benötigt. Alle Operationen auf dem Textur Daten Objekt könnten auch in einem extra Thread ausgeführt werden. So erhält man eine bessere Prozessor Auslastung und verhindert Laderuckler.

3.3. TglcFrameBufferObject

Frame Buffer Objekte werden meißtens für Offscreen Rendering genutzt. Wir können einfach ein Frame Buffer Objekt anlegen, einen Farb und Teifen Speichern binden und beliebige Formen darin zeichnen. Es gibt zwei Arten von Speichern die man an das Frame Buffer Objekt binden kann. Zum einen gibt es Render Speicher sind einfache Speicher in denen OpenGL die benötigten Daten ablegt. Diese Daten können nichtvon uns gelesen werden. Auf der anderen Seite gibt es Textur Speicher. Textur Speicher können nach dem Zeichnen in das Frame Buffer Objekt als normale Textur genutzt werden.Klingt kompliziert, ist aber einfacher als gedacht.

var
  fbo: TglcFrameBufferObject;
  tex: TglcTextureBuffer;
  buf: TglcRenderBuffer;
 
fbo := TglcFrameBufferObject.Create;
try
  // Frame Buffer Größe festlegen
  fbo.SetSize(800, 600);
 
  // Textur Speicher erzeugen und als Farb Buffer binden
  tex := TglcTextureBuffer.Create(TglcFormat.fmRGBA, TglcInternalFormat.ifRGBA16F);
  fbo.AddBuffer(tex, TglcAttachment.atColor0, true);
 
  // Render Buffer erzeugen und als Tiefen Buffer binden
  buf := TglcRenderBuffer.Create(TglcInternalFormat.ifDepthComponent);
  fbo.AddBuffer(buf, TglcAttachment.atDepth, true);
 
  // Formen ins Frame Buffer Objekt zeichnen
  fbo.Bind;
  { ... }
  fbo.Unbind;
 
  // Textur Buffer als Textur nutzen
  tex.Bind;
  { ... }
  tex.Unbind;
finally
  FreeAndNil(fbo);
end;

3.4. TglcShaderProgram

Was wäre eine OpenGL Anwendung ohne schicke Shader? Die TglcShaderProgram Klasse hilft uns dabei Shader zu laden und in unserer Anwendung zu nutzen. Das einfachste Beispiel für die Shader Klasse ist das Laden und Nutzen eines Shaders.

Shader Code:

/* ShaderObject: GL_VERTEX_SHADER */
#version 330
layout(location = 0) in vec3 inPos;
void main(void)
{
    gl_Position = vec4(inPos, 1.0);
}
 
/* ShaderObject: GL_FRAGMENT_SHADER */
#version 330
out vec4 outColor;
void main(void)
{
    outColor = vec4(1.0, 0.0, 0.0, 1.0);
}

Anwendungs Code:

var Shader: TglcShaderProgram;
{ 'Log' ist ein Callback um Log Ausgaben des Shaders zu mitzuteilen. }
{ So kann man Fehler im Shader Code besser aufspühren. }
Shader := TglcShaderProgram.Create(@Log);
Shader.LoadFromFile('./my-shader-file.glsl');
Shader.Compile;
Shader.Enable;
{ ... }
Shader.Disable;

Wie oben schon erwähnt ist das ein sehr einfaches Beispiel. Die TglcShaderProgram  Klasse beitet noch mehr nützliche Eigenschaften, wie zum Beispiel das setzen von Uniform Variablen, das Binden von Attribut Positionen und das Linken eines Shaders der Schritt für Schritt im Code der Anwendung aus TglcShaderObjects Objekten zusammen gestellt wurde.

3.5. TglcVertexArrayObject

Vertex Array Objekte werden genutzt um alle benötigten Informationen von Vertex Buffer und Index Buffer Objekten zu speichern. Anstatt OpenGL diese Daten vor jedem Render Aufruf einzeln mitzuteilen, können diese dann durch das binden des Vertex Array Objektes mit nur einem API Aufruf bekannt gegeben werden.

var
vbo: TglcArrayBuffer;
vao: TglcVertexArrayObject;
{ ... Vertex Buffer Objekt erstellen }
vao := TglcVertexArrayObject.Create;
bao.BindArrayBuffer(vbo, true);
vao.VertexAttribPointer(LAYOUT_LOCATION_POS, 3, GL_FLOAT, False, SizeOf(TVertex), GLint(@PVertex(nil)^.pos));
vao.VertexAttribPointer(LAYOUT_LOCATION_TEX, 2, GL_FLOAT, False, SizeOf(TVertex), GLint(@PVertex(nil)^.tex));
vao.Bind;
{ ... Vertices mit glDraw zeichnen }
vao.Unbind;

4. Hilfs Klassen

4.1. TglcCamera

Die TglcCamera ist eine sehr einfache Klasse. Sie beinhaltet die Model View Matrix und einige nützliche Methoden, wie zum Beispiel Tilt, Turn oder Move, um diese zu manipulieren.

var Camera: TglcCamera;
 
Camera := TglcCamera.Create;
Camera.Perspective(45, 0.01, 100, 800/600); // Perspektive setzen
Camera.Move(gluVector(2, 3, -5)); // Kamera bewegen
Camera.Tilt(-25); // Kamera neigen
Camera.Turn(-10); // Kamera drehen
Camera.Activate; // Kamera aktivieren
 
{ ... normal zeichnen }

4.2. TglcLight und TglcMaterial

Die TglcLight und TglcMaterial Klassen sind ebenfalls sehr einfache Klassen. Sie kapslen die OpenGL Licht und Material Eigenschaften in einem Objekt. TglLight lässt sich außerdem in drei weitere Klassen spezialisieren:

  • TglcLightGlobal – für globales Licht
  • TglcLightPoint – für Licht Punkte
  • TglcLightSpot – für Spot Lichter

5. Hilfs Typen

  • glcTypes – Beinhaltet eine Liste von allen OpenGL Enumerationen die in diesem Projekt zu Anwendung kommen.
  • gluMatrix – Beinhaltet einige nützliche Methoden rund um die Berechnung von Matrizen. gluMatrixEx is eine erweiterte Version von gluMatrix, die die Type Helper von FreePascal nutzt um Member Methoden für die Matrix Daten Typen zu implementieren.
  • gluVector – Beinhaltet einige nützliche Methoden rund um die Berechnung von Vektoren. gluVectorEx is eine erweiterte Version von gluVector, die die Type Helper von FreePascal nutzt um Member Methoden für die Vektor Daten Typen zu implementieren.
  • gluQuaternion – Beinhaltet einige nützliche Methodenrund um die Berechnung von Quaternionen.

Leave a Reply

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert