Ü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..
Links
- Git Repository
OpenGLCore Source (zip) (369,0 KiB, 3.093 hits)
OpenGLCore Source (tar.gz) (336,5 KiB, 2.684 hits)
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.