Benutzer:Mori/OGL3 Quickstart: Unterschied zwischen den Versionen
Mori (Diskussion | Beiträge) |
Mori (Diskussion | Beiträge) |
||
Zeile 44: | Zeile 44: | ||
Hierbei möchte ich nochmals auf die Werte ''Offset'' und ''Stride'' eingehen, welche unter Umständen falsch verstanden werden können. Ein beispielhaftes Vertex im VBO könnte aus 3 Float Positionswerten (X,Y,Z) und 3 Float Farbwerten (R,G,B) bestehen. Im VBO stehen nun für ein Vertex die Werte X,Y,Z,R,G,B hintereinander im Speicher. Hierbei wird die Größe des gesamten Vertex (in Byte) wird im Wert Stride angegeben, dieser beschreibt die Entfernung der Verticies im Speicher voneinander. Der Wert ''Offset'' beinhaltet hierbei die Unterteilung des Vertex selbst. Da die Position am Anfang des Vertices gespeichert wird ist der Offset hier 0. Für die Farben erhalten wir hierfür allerdings einen Offset von 12 Bytes, welcher die Verschiebung relativ zum Vertex beschreibt. Da vor den Farben noch die 3 Positionswerte als Float gespeichert werden (SizeOf(Float)=4) müssen wir den Offset auf 3*4=12 setzen. Das folgende Bild veranschaulicht nochmals, wie man Stride und Offset für Daten berechnen kann.<br> | Hierbei möchte ich nochmals auf die Werte ''Offset'' und ''Stride'' eingehen, welche unter Umständen falsch verstanden werden können. Ein beispielhaftes Vertex im VBO könnte aus 3 Float Positionswerten (X,Y,Z) und 3 Float Farbwerten (R,G,B) bestehen. Im VBO stehen nun für ein Vertex die Werte X,Y,Z,R,G,B hintereinander im Speicher. Hierbei wird die Größe des gesamten Vertex (in Byte) wird im Wert Stride angegeben, dieser beschreibt die Entfernung der Verticies im Speicher voneinander. Der Wert ''Offset'' beinhaltet hierbei die Unterteilung des Vertex selbst. Da die Position am Anfang des Vertices gespeichert wird ist der Offset hier 0. Für die Farben erhalten wir hierfür allerdings einen Offset von 12 Bytes, welcher die Verschiebung relativ zum Vertex beschreibt. Da vor den Farben noch die 3 Positionswerte als Float gespeichert werden (SizeOf(Float)=4) müssen wir den Offset auf 3*4=12 setzen. Das folgende Bild veranschaulicht nochmals, wie man Stride und Offset für Daten berechnen kann.<br> | ||
[[Datei:VBODaten.png|700px]] | [[Datei:VBODaten.png|700px]] | ||
+ | In unserer VBO Klasse benötigen wir diese Informationen für Verticies,Texturen,Farben und Normalen. Zusätzlich brauchen wir wieder ein Handle ''hid'' welches das VBO im OpenGL Treiber repräsentiert und Funktionen um Daten in das VBO schreiben zu können und dieses zu "binden". Eine solche Umsetzung könnte ungefähr so aussehen:<source lang="pascal"> TGLVBO=class | ||
+ | private | ||
+ | hid:GLUInt; | ||
+ | public | ||
+ | vertices:TVBOElementOffset; | ||
+ | textures:TVBOElementOffset; | ||
+ | colors:TVBOElementOffset; | ||
+ | normals:TVBOElementOffset; | ||
+ | |||
+ | constructor Create(); | ||
+ | destructor Destroy(); override; | ||
+ | |||
+ | procedure bind(); | ||
+ | |||
+ | function startEdit(size:Integer=-1; mode:Cardinal=GL_STATIC_DRAW):Pointer; | ||
+ | procedure endEdit(); | ||
+ | end; | ||
+ | |||
+ | procedure TGLVBO.bind; | ||
+ | begin | ||
+ | glBindBuffer(GL_ARRAY_BUFFER,hid); | ||
+ | end; | ||
+ | |||
+ | constructor TGLVBO.Create; | ||
+ | begin | ||
+ | glGenBuffers(1,@hid); | ||
+ | bind(); | ||
+ | end; | ||
+ | |||
+ | destructor TGLVBO.Destroy; | ||
+ | begin | ||
+ | glDeleteBuffers(1,@hid); | ||
+ | inherited; | ||
+ | end; | ||
+ | </source>Wir sehen, dass wir im Konstruktor einen Buffer mit ''glGenBuffers'' für unsere Daten anfordern und diesem im Destructor mit ''glDeleteBuffers'' wieder löschen. Zusätzlich binden wir den Buffer im Konstruktor als '''GL_ARRAY_BUFFER'''. Das bedeutet, das unser Buffer (welcher im Moment noch keine Daten enthält) als aktueller Array Buffer aktiviert wird. Das bedeutet, dass alle folgenden Befehle für Array Buffer auf diesen Buffer angewendet werden. Solche Befehle werden zum Beispiel in den folgenden Edit-Funktionen verwendet.<source lang="pascal">function TGLVBO.startEdit(size:Integer=-1; mode:Cardinal=GL_STATIC_DRAW):Pointer; | ||
+ | begin | ||
+ | bind(); | ||
+ | if size>0 then | ||
+ | glBufferData(GL_ARRAY_BUFFER,size,nil,mode); | ||
+ | |||
+ | Result:=glMapBuffer(GL_ARRAY_BUFFER_ARB,GL_READ_WRITE); | ||
+ | end; | ||
+ | |||
+ | procedure TGLVBO.endEdit; | ||
+ | begin | ||
+ | glUnmapBuffer(GL_ARRAY_BUFFER_ARB); | ||
+ | end;</source>Diese beiden Funktionen erlauben das Verändern unseres VBOs. Zuerst wird wieder das VBO gebunden, damit wir nicht versehentlich ein anderes noch gebundenes VBO verändern. Danach wird ein optionaler Parameter geprüft, welcher die Größe des VBOs in Bytes angibt (diese Größe wird meißt durch die Vertex Anzahl * die Vertex Größe angegeben). Dieser Parameter muss beim ersten Editieren angegeben werden und legt durch den glBufferData Befehl den eigendlichen Buffer an. Je nach Aufgabe des VBOs kann hier auch ein zweiter Parameter angegeben werden, welcher (je nach Treiber) das VBO auf Zeichne-, Lese- oder Schreiboperationen optimiert. Der glMapBuffer Befehl gibt uns danach einen Pointer auf einen Speicherbereich mit der vorher angegeben Größe, welchen wir jetzt mit unseren Vertex Daten füllen können. Nach dem Befüllen des Buffers müssen wir den Befehl glUnmapBuffer aufrufen, welcher das Editieren beendet und die Daten im VBO sichert. | ||
erstellen (laden?) binden | erstellen (laden?) binden |
Version vom 12. Februar 2013, 17:41 Uhr
Inhaltsverzeichnis
OpenGL 3.x Quickstart
Diese Seite ist noch in Bearbeitung/Planung |
Vorwort
warum ogl3 / nachteile /vorteile OpenGL gibt es nun schon seit einigen Jahren und entwickelt sich auch seitdem kontinuierlich weiter. Einer der größten Änderungen erhielt Einzug mit OpenGL 3. Veraltete Befehle werden (unter bestimmten Umständen) nicht mehr supportet und es ist ein neuer Context erforderlich um OpenGL 3 nutzen zu können. Zu den weiteren Änderungen zählen auch das Wegfallen der statischen Renderpipeline und vorgegebene Variablen. Dies ermöglicht uns als Programmierern größere Freiheiten in der Verwendung von OpenGL erzwingt aber gleichzeitig, dass wir uns selber um bestimmte Dinge, wie Modelview- und Projectionmatrizen, kümmern. Dieses Tutorial soll einen kurzen Einstieg in die Möglichkeiten von OpenGL 3 geben und zeigen, wie man die erste Anwendung einfach selber compilieren kann. Da da dieses Tutorial nur einen kurzen Einstieg in OpenGl geben soll sind an den entsprechenden Stellen Verlinkungen zu weiterführenden Artikeln angegeben.
Context
Der erste wesentliche Unterschied zu OpenGL Versionen kleiner als 3.0 besteht in einem weiteren Rendercontext, welcher zusätzlich erstellt weren muss. Wie auch die 1.x/2.x Rendercontexte (im Folgenden RC), erzeugt man hiermit eine neue OpenGL "Instanz", welche zum Rendern auf einen DeviceContext(im Folgenden DC) genutzt werden kann. Der wesentliche Unterschied ist, dass wir einen gültigen 1.x/2.x RenderContext benötigen, um unseren 3.x'er Context anfordern zu können. Wesentliche Änderungen sind:
- Wegfallen des Direct-mode (glBegin,glEnd)
- Wegfallen des der festen Render-Pipeline
- Wegfallen von Displaylisten
Dies führt zu einer Reihe von Neuerungen im Code, hilft aber auch eine gewisse Struktur in die API zu bringen und das OpenGL Interface zu vereinheitlichen.
Um den Context zu bekommen, könnte unsere Funktion in etwa so aussehen:
constructor TGLContext.Create(DeviceContext:HDC);
begin
DC:=DeviceContext; RC:=0;
if InitOpenGL()=false then exit;
RC:=CreateRenderingContextVersion(DC,[opDoubleBuffered],3,1,false,32,24,0,0,0,0);
ActivateRenderingContext(DC,RC);
SetViewPort(1,1);
glClearColor(0.0,0.4,0.8,0.0);
glEnable(GL_DEPTH_TEST);
//glEnable(GL_CULL_FACE);
end;
Unsere Funktion nimmt hierbei einen DeviceContext entgegen (dieser gibt die Zeichenfläche an und ist kann mit der GetDC Funktion abgefragt werden. ZB. getDC(Form1.Handle) ). InitOpenGL ist eine Funktion unseres Headers, welche die OpenGL Library in unser Programm lädt. Hiernach können wir unseren OpenGL 3.x Context anfordern. Die Header-Funktion CreateRenderingContextVersion nimmt uns den Aufwand des erstellen beider Contexte ab und gibt uns (falls OpenGL 3.0 Unterstützt wird) unseren gewünschten RenderContext zurück. Sollte eine andere OpenGL Version als 3.1 gewünscht werden kann diese in dieser Funktion mit den Parametern MajorVersion und MinorVersion angefordert werden. Weiterhin können auch Color-, Z-, Stencil, AccumBits und die Anzahl der AuxBuffers hier eingestellt werden. Zuletzt können wir mit dem Parameter ForwardCompatible wählen, ob der Kontext die veralteten 2.x Funktionen unterstützen soll. Wenn keine Notwendigkeit besteht, sollte dieser Parameter auf true gestellt werden um einen puren 3.x Kontext zu erzeugen. Um OpenGL im Folgenden verwenden zu können müssen wir unseren neuen Kontext aktivieren und ein paar Grundeinstellungen treffen.
error handling
VBOs
Ein elementarer Bestandteil auf dem Weg zu einer sichtbaren Ausgabe sind Vertex Buffer Arrays (im Folgenden: VBO's). Diese beinhalten die 3D-Daten welche auf der Grafikkarte verarbeitet und gezeichnet werden. Damit OpenGL die Daten verarbeiten kann, müssen wir vor dem Zeichnen bekanntgeben in welchem Format die Daten vorliegen. Folgende Informationen werden benötigt:
TVBOElementOffset=record
dtype:Cardinal; //Datentyp: GL_FLOAT, GL_BYTE, ...
normalized:GLboolean; //Sind die Werte normalisiert? (GL_TRUE/GL_FALSE)
count:Cardinal; //Wie viele Elemente sind enthalten? (bei einer Farbe: r,g,b,a = 4)
offset:Cardinal; //Was ist der Offset zu den Werten in einem Vertex
stride:Cardinal; //Schrittweite, bis zum nächsten Vertex
end;
Hierbei möchte ich nochmals auf die Werte Offset und Stride eingehen, welche unter Umständen falsch verstanden werden können. Ein beispielhaftes Vertex im VBO könnte aus 3 Float Positionswerten (X,Y,Z) und 3 Float Farbwerten (R,G,B) bestehen. Im VBO stehen nun für ein Vertex die Werte X,Y,Z,R,G,B hintereinander im Speicher. Hierbei wird die Größe des gesamten Vertex (in Byte) wird im Wert Stride angegeben, dieser beschreibt die Entfernung der Verticies im Speicher voneinander. Der Wert Offset beinhaltet hierbei die Unterteilung des Vertex selbst. Da die Position am Anfang des Vertices gespeichert wird ist der Offset hier 0. Für die Farben erhalten wir hierfür allerdings einen Offset von 12 Bytes, welcher die Verschiebung relativ zum Vertex beschreibt. Da vor den Farben noch die 3 Positionswerte als Float gespeichert werden (SizeOf(Float)=4) müssen wir den Offset auf 3*4=12 setzen. Das folgende Bild veranschaulicht nochmals, wie man Stride und Offset für Daten berechnen kann.
TGLVBO=class
private
hid:GLUInt;
public
vertices:TVBOElementOffset;
textures:TVBOElementOffset;
colors:TVBOElementOffset;
normals:TVBOElementOffset;
constructor Create();
destructor Destroy(); override;
procedure bind();
function startEdit(size:Integer=-1; mode:Cardinal=GL_STATIC_DRAW):Pointer;
procedure endEdit();
end;
procedure TGLVBO.bind;
begin
glBindBuffer(GL_ARRAY_BUFFER,hid);
end;
constructor TGLVBO.Create;
begin
glGenBuffers(1,@hid);
bind();
end;
destructor TGLVBO.Destroy;
begin
glDeleteBuffers(1,@hid);
inherited;
end;
function TGLVBO.startEdit(size:Integer=-1; mode:Cardinal=GL_STATIC_DRAW):Pointer;
begin
bind();
if size>0 then
glBufferData(GL_ARRAY_BUFFER,size,nil,mode);
Result:=glMapBuffer(GL_ARRAY_BUFFER_ARB,GL_READ_WRITE);
end;
procedure TGLVBO.endEdit;
begin
glUnmapBuffer(GL_ARRAY_BUFFER_ARB);
end;
erstellen (laden?) binden
EABs
Ein Element Array Buffer (im folgenden EAB), "erweitert" die Möglichkeiten Meshes zu Zeichnen. Der Einsatz von EABs bietet sowohl die Möglichkeit Meshes dynamischer zu zeichnen, als auch den Speicherverbrauch (je nach Mesh) drastisch zu reduzieren. Wie der Name schon andeutet, handelt es sich um einen Buffer zum Speichern von Element Array's. Diese Elemente sind in diesem Fall die Indices der einzelnen Mesh-Vertices. In diesem Buffer geben wir daher die Daten nichtmehr direkt an, sondern nur noch einen Index und können erstens Vertices zwischen verschiedenen Faces sharen (zB. bei Ecken in Objekten, welche von mehreren Dreiecken verwendet werden) und zweitens, verschiedene Rendermodes (zB. GL_TRIANGLES, GL_TRIANGLE_STRIP, ...) auf ein und das selbe VBO anwenden, ohne die Reihenfolge der Vertices auf der Grafikkarte verändern zu müssen. Zuerst holen wir uns deshalb, wie schon beim VBO, ein Handle (hier hid:GLUInt) von OpenGL und binden dieses als GL_ELEMENT_ARRAY_BUFFER. Dieses Handle wird am Ende wieder mit glDeleteBuffers freigegeben:
constructor TGLEAB.Create;
begin
glGenBuffers(1,@hid);
bind();
end;
destructor TGLEAB.Destroy;
begin
glDeleteBuffers(1,@hid);
inherited;
end;
procedure TGLEAB.bind;
begin
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,hid);
end;
Nachdem wir jetzt unser EAB Handle erzeugt haben müssen wir es nur noch mit Daten befüllen. Dazu brauchen wir einen Vertex-count und die Größe (in Byte) eines einzelnen Vertex um den entsprechenden Speicher reservieren zu können.
function TGLEAB.startEdit(valType:Cardinal; count:Cardinal; size: Integer=-1; mode:Cardinal=GL_STATIC_DRAW): Pointer;
begin
self.valType:=valType;
elementsCount:=count;
bind();
if size>0 then
glBufferData(GL_ELEMENT_ARRAY_BUFFER,size,nil,mode);
Result:=glMapBuffer(GL_ELEMENT_ARRAY_BUFFER,GL_READ_WRITE);
end;
procedure TGLEAB.endEdit;
begin
glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);
end;
Hierbei erzeugen wir mit glBufferData einen neuen EAB mit der entsprechenden Größe (bei size=-1 lassen wir ein schon bestehendes EAB in der Größe unverändert). Wie schon beim VBO lassen wir uns danach mit glMapBuffer einen Pointer auf den reservierten Speicherbereich zurückgeben, welchen wir beschreiben können. Wie schon beim erzeugen müssen wir beim Binden wieder GL_ELEMENT_ARRAY_BUFFER angeben, damit OpenGL unser gebundenes EAB auswählt und nicht ein VBO.
glDrawElements(GL_TRIANGLES,indicies.elementsCount*3,indicies.valType,nil);
Shader
vorrausetzungen (benötigte matrizen + daten vbo mapping) shader arten
Matrizen
Wie wir im vorherigen Abschnitt gesehen haben, müssen wir dem Shader Matrizen mitgeben. Diese Matrizen benutzen wir im folgenden, um unsere Objekte zu Positionieren (durch Verschiebung, Rotation und Skalierung) und sie auf unseren Viewport (meist den Bildschirm) zu projizieren. Daher nennen wir die folgenden Matrizen ModelView- und Projection- 4x4-Matrix. Zum initialisieren benutzt man am besten eine Math-Libary welche die benötigten Funktionen bereitstellt. Als erstes richten wir die Projection-Matrix ein, welche wir nur einmal in der Szene benötigen:
projectionMatrix.perspective(45,Viewport.Width/Viewport.Height,0.5,100);
Diese Initialisierung der Matrix bewirkt, das wir auf dem Bildschirm eine Abbildung der Vertices mit einem Blickwinkel von 45 Grad erhalten. Außerdem geben wir mit den Werten 0.5 bis 100 die Near- und Far-Clipping Plane an, welche angibt, ab welcher Entfernung Objekte nichtmehr sichtbar sein sollen. Als zweites Initialisieren wir unsere Modelview Matrix. Diese gibt, wie der Name schon sagt, die Verschiebung der Objekte in unserer Szene an. Diese wird als Identitäts-Matrix initialisiert und im Folgenden zB. Rotiert oder Verschoben.
mvMatrix.indentity();
mvMatrix.rotateX(degToRad(45)); //Rotation um 45 Grad
mvMatrix.translate(0,0,-5); //Verschiebung auf der Z-Achse
Es macht Sinn für die Kamera und für jedes Objekt eine eigene ModelView-Matrix zu erstellen, um bei einer Bewegung der Kamera, nicht jede Matrix neu berechnen zu müssen. Bei einer Änderung, zB. der Verschiebung der Kamera Position, kann man die neue modelviewMatrix der Objekte durch Multiplizieren der Objekt-ModelviewMatrix mit der Kamera-ModelviewMatrix erhalten. Da dieses Tutorial nur ein kurzer Einstieg in OpenGl ist, beschäftigen wir uns hier nicht ausführlich mit Matrizen, weiterführende Erklärungen zur Matrizenrechnung kann man im Tutorial_Nachsitzen finden.
Zeichnen
eab/vbo/shader binding
Weiterführendes/Aussicht
erläuterungen, weiterführende links