Tutorial Quickstart: Unterschied zwischen den Versionen
Flash (Diskussion | Beiträge) K () |
K (→Methode 1: Timer) |
||
(62 dazwischenliegende Versionen von 13 Benutzern werden nicht angezeigt) | |||
Zeile 3: | Zeile 3: | ||
==Einleitung== | ==Einleitung== | ||
− | Willkommen beim Delphi & OpenGL Quickstart. Diese kurze Einleitung soll | + | Willkommen beim Delphi & OpenGL Quickstart. Diese kurze Einleitung soll euch auf die Tutorials bei DelphiGL.com und allgemein auf die Grafikprogrammierung mit OpenGL und Delphi vorbereiten. Dieser Quickstart ist kein Tutorial für sich, sondern soll ein Grundgefühl vermitteln, wie OpenGL und Delphi miteinander arbeiten. |
− | + | {{Hinweis|Dieses Tutorial beschreibt den Einstieg in OpenGL auf Basis eines Delphi-VCL* Ansatzes. Dieses Tutorial basiert damit nicht auf SDL oder easySDL wie die folgenden Einsteigertutorials. Aus folgenden Gründen sollte man trotzdem dieses Tutorial lesen: <br/> | |
+ | #Weil der hier vorgestellte Ansatz für eure (Windows-)Projekte genutzt werden kann (easySDL ist nicht dafür gedacht).<br/> | ||
+ | #Weil ihr so seht wie der Code aus den easySDL Beispielen in ein VCL Beispiel übertragen werden kann.<br/> | ||
+ | #Weil der zweite Abschnitt des Tuts ein paar allgemeine Worte zur Spieleprogrammierung enthält, '''die jeder Einsteiger lesen sollte'''.}} | ||
+ | <small>''*Zur Erklärung für nicht Delphiprogrammierer: die VCL ist die '''Visuelle Component Library''' von Delphi. Diese enthält die GUI Komponenten (z.B. Formulare) aus denen man unter Delphi Anwendungen zusammensetzen kann.''</small> | ||
+ | ''Wozu OpenGL?'' Mit OpenGL kann man eine Vielzahl von Aufgaben bewältigen. Ob man Forschungsergebnisse aller Art visualisieren, 2D oder 3D Spiele schreiben oder einfach seiner Anwendung eine Oberfläche geben möchte, die nicht dem grauen Standardlook entspricht. All das ist möglich mit OpenGL. | ||
==Wie fange ich an?== | ==Wie fange ich an?== | ||
− | Genau zwei Dinge braucht der OpenGL-Programmierer um effektiv arbeiten zu können: | + | Genau zwei Dinge braucht der OpenGL-Programmierer, um effektiv arbeiten zu können: |
#Einen OpenGL Header | #Einen OpenGL Header | ||
− | #Eine Codebasis von der aus man neue Projekte starten kann (ein | + | #Eine Codebasis, von der aus man neue Projekte starten kann (ein sogenanntes Template) |
+ | ===Der OpenGL-Header=== | ||
+ | Das is ja easy! Denn Delphi bringt ja schon einen OpenGL-Header mit... | ||
+ | |||
+ | '''STOP!''' | ||
+ | |||
+ | Denn wir reden hier von einem guten Header. Leider ist der standardmäßig von Delphi mitgelieferte Header alles andere als zu empfehlen. Er ist fehlerhaft, hält sich nicht an OpenGL-Normen und ist außerdem absolut veraltet. | ||
− | + | Was nun? Ganz einfach: Bei [http://www.DelphiGL.com DelphiGL.com] (kurz DGL) gibt es '''DEN''' OpenGL-Header für alle Pascalsprachen: | |
− | |||
− | |||
− | |||
− | Was nun? | ||
'''Die [[DGLOpenGL.pas]].''' | '''Die [[DGLOpenGL.pas]].''' | ||
− | Diesen solltet | + | Diesen solltet Ihr euch jetzt besorgen, wenn Ihr ihn nicht schon habt. Der Header wird bei neuen OpenGL-Versionen vom DGL-Team aktualisiert, und ist so immer auf der Höhe der Zeit. |
===Codebasis/Templates=== | ===Codebasis/Templates=== | ||
− | So... das war schon alles was Ihr aus dem Netz benötigt. Den Rest machen wir jetzt per Hand. | + | So... das war schon alles, was Ihr aus dem Netz benötigt. Den Rest machen wir jetzt per Hand. |
− | |||
− | |||
+ | Im nächsten Kapitel zeige ich euch wie man sich ein einfaches [[Template]] schreibt. Natürlich hat DelphiGL.com auch bereits [[:Kategorie:TemplateFile|fertige Lösungen]], die durchaus zu empfehlen sind und auch extra Features wie Vollbildrendering besitzen. ABER aus Erfahrung kann ich sagen: Man findet sich im eigenen Code viel einfacher zurecht. (Und die Extras kann man nachher immer noch einbauen.) | ||
+ | ==Das Template - Delphi fit für OpenGL machen== | ||
+ | Bevor man wirklich loslegen kann, muss noch die runtergeladene DGLOpenGL.pas an den richtigen Ort gebracht werden. Gut wäre z.B., sie in das Verzeichnis "\lib" in Eurem Delphiverzeichnis zu legen. (Wenn ihr das [[DGLSDK]] verwendet, wurden die Suchpfade schon eingerichtet.) | ||
− | + | Dann startet mal Delphi. Vor euch sollte jetzt ein leeres Projekt erscheinen. Das leere Formular kann gleich minimiert werden, denn jetzt wird erstmal hübsch gecodet. | |
− | |||
− | + | {{Hinweis|Lest das Tutorial erst einmal durch bevor ihr versucht die Teile zusammen zu fügen. Vieles wird erst im Zusammenhang klar. Wer Zweifel an der Richtigkeit seines Templates hat, kann sich ein DGL-Template runterladen, um den Code als Vorlage zu verwenden.}} | |
+ | {{Hinweis|Am Ende von Kapitel 1.3 findet ihr den Kopf der Template-Klasse. Dort seht ihr auch, welche Variablen von welchem Typ deklariert werden müssen.}} | ||
===Initialisieren von OpenGL=== | ===Initialisieren von OpenGL=== | ||
Zeile 40: | Zeile 48: | ||
Zuerst einmal solltet Ihr die DGLOpenGL.pas in die '''uses'''-Klausel des '''interface'''-Teils der Unit1 schreiben. | Zuerst einmal solltet Ihr die DGLOpenGL.pas in die '''uses'''-Klausel des '''interface'''-Teils der Unit1 schreiben. | ||
− | Die eigentliche Initialisierung soll direkt beim | + | Die eigentliche Initialisierung soll direkt beim Erstellen des Formulars gemacht werden. Deshalb kommt der folgende Quelltext ins OnCreate-Ereignis des Formulars. |
− | <pascal>procedure TForm1.FormCreate(Sender: TObject); | + | <source lang="pascal">procedure TForm1.FormCreate(Sender: TObject); |
begin | begin | ||
DC:= GetDC(Handle); | DC:= GetDC(Handle); | ||
Zeile 52: | Zeile 60: | ||
0); | 0); | ||
ActivateRenderingContext(DC, RC); | ActivateRenderingContext(DC, RC); | ||
− | end;</ | + | end;</source> |
'''Zeile 3:''' Hier wird der Gerätekontext (Device Context) von Formular Form1 abgefragt. | '''Zeile 3:''' Hier wird der Gerätekontext (Device Context) von Formular Form1 abgefragt. | ||
− | '''Zeile 4:''' Mit InitOpenGL wird OpenGL initialisiert. Wenn das nicht funktioniert wird die gesamte Anwendung sofort beendet. | + | '''Zeile 4:''' Mit InitOpenGL wird OpenGL initialisiert. Wenn das nicht funktioniert, wird die gesamte Anwendung sofort beendet. |
− | '''Zeile 5:''' Hier wird der | + | '''Zeile 5:''' Hier wird der [[Renderkontext]] erzeugt. Den braucht OpenGL zum Zeichnen auf das Formular. Mehr dazu lernt ihr im [[Tutorial_lektion1]]. |
− | '''Zeile 11:''' Abschließend wird der | + | '''Zeile 11:''' Abschließend wird der Renderkontext aktiviert. OpenGL ist jetzt prinzipiell startbereit. |
+ | {{Hinweis|DC und RC sind Eigenschaften des Formulars. Siehe [[Tutorial_quickstart#Das_fertige_Templateformular|Definition des Templateformulars]].}} | ||
− | Nach dieser durchaus simplen Initialisierung (man kann auch alles per Hand machen was InitOpenGL macht!) steht OpenGL ziemlich nackt da. Soll heißen, alle OpenGL Eigenschaften/Zustände stehen auf den definierten Anfangswerten. Es kommt aber durchaus oft - eigentlich ständig - vor, dass bestimmte Einstellungen von OpenGL benutzt werden sollen. Deshalb schreiben wir uns noch eine kleine | + | Nach dieser durchaus simplen Initialisierung (man kann auch alles per Hand machen, was InitOpenGL macht!) steht OpenGL ziemlich nackt da. Soll heißen, alle OpenGL Eigenschaften/Zustände stehen auf den definierten Anfangswerten. Es kommt aber durchaus oft - eigentlich ständig - vor, dass bestimmte Einstellungen von OpenGL benutzt werden sollen. Deshalb schreiben wir uns noch eine kleine Zusatzprozedur: SetupGL |
− | <pascal>procedure TForm1.SetupGL; | + | <source lang="pascal">procedure TForm1.SetupGL; |
begin | begin | ||
− | glClearColor(0.3, 0.4, 0.7, 0.0); //Hintergrundfarbe | + | glClearColor(0.3, 0.4, 0.7, 0.0); //Hintergrundfarbe: Hier ein leichtes Blau |
glEnable(GL_DEPTH_TEST); //Tiefentest aktivieren | glEnable(GL_DEPTH_TEST); //Tiefentest aktivieren | ||
glEnable(GL_CULL_FACE); //Backface Culling aktivieren | glEnable(GL_CULL_FACE); //Backface Culling aktivieren | ||
− | end;</ | + | end;</source> |
− | Was hier passiert wird durch die Kommentare bereits erklärt (Für mehr Infos siehe [[Tiefentest]] bzw. [[Backface Culling]]). Die Hintergrundfarbe könnt ihr nach Belieben einstellen. (Wenn ihr später einmal geschlossene Szenen rendern wollt, dann ist es günstig eine sehr schräge Farbe als Hintergrundfarbe einzustellen, so findet man leichter Fehler in der Szene.) | + | Was hier passiert, wird durch die Kommentare bereits erklärt (Für mehr Infos siehe [[Tiefentest]] bzw. [[Backface Culling]]). |
+ | {{Hinweis|Backface Culling ist eine simple, aber effektive Methode eure Anwendung zu beschleunigen. Allerdings muss man bei der Ausgabe auf die Reihenfolge der Vertices achten. Lest euch deshalb kurz den Artikel zu Backface Culling durch oder kommentiert die obige Zeile einfach ganz aus. Sonst könnte es passieren, dass Teile eurer Szene plötzlich "verschwinden".}} | ||
+ | Die Hintergrundfarbe könnt ihr nach Belieben (mit [[glClearColor]]) einstellen. (Wenn ihr später einmal geschlossene Szenen rendern wollt, dann ist es für die Entwicklung günstig eine sehr schräge Farbe als Hintergrundfarbe einzustellen, so findet man leichter Fehler in der Szene. In OpenGL werden Farben immer als Mischung aus Rot, Grün und Blau angegeben. Dies wird in späteren Tutorials noch erklärt. Aber herumspielen an den Werten hilft hier auch schnell weiter. ;-) ) | ||
− | Außerdem hat man ja hin und wieder auch noch globale Variablen, die man initialisieren möchte. Da wir mit solchen Sachen unser schön aufgeräumtes '''FormCreate''' nicht zumüllen wollen bietet sich ein Unterprogramm namens '''InitGlobals''' oder kurz '''Init''' an. | + | Außerdem hat man ja hin und wieder auch noch globale Variablen, die man initialisieren möchte. Da wir mit solchen Sachen unser schön aufgeräumtes '''FormCreate''' nicht zumüllen wollen, bietet sich ein Unterprogramm namens '''InitGlobals''' oder kurz '''Init''' an. (Wobei das jetzt nicht direkt etwas mit OpenGL zu tun hat.) |
Beide Unterprogramme (SetupGL und Init) sollten am Ende von '''FormCreate''' gerufen werden: | Beide Unterprogramme (SetupGL und Init) sollten am Ende von '''FormCreate''' gerufen werden: | ||
− | <pascal> [ | + | <source lang="pascal"> [...] |
ActivateRenderingContext(DC, RC); | ActivateRenderingContext(DC, RC); | ||
SetupGL; | SetupGL; | ||
− | Init; | + | Init; //Was man sonst so initialisieren will. |
− | end;</ | + | end;</source> |
− | |||
===Die Ereignisbehandlung=== | ===Die Ereignisbehandlung=== | ||
− | Für OpenGL sind vor allem die Ereignisse von Bedeutung, die an der Zeichenfläche von OpenGL herumwerkeln. Da OpenGL direkt auf das Formular (oder auch auf ein Panel) zeichnet, müssen Ereignisse, die diese Zeichenfläche ändern, behandelt werden. Dies wären ''' | + | Für OpenGL sind vor allem die Ereignisse von Bedeutung, die an der Zeichenfläche von OpenGL herumwerkeln. Da OpenGL direkt auf das Formular (oder auch auf ein Panel) zeichnet, müssen Ereignisse, die diese Zeichenfläche ändern, behandelt werden. Dies wären das '''OnResize-''' und das '''OnDestroy'''-Ereignis. |
Zuerst '''FormResize''': | Zuerst '''FormResize''': | ||
− | <pascal>procedure TForm1.FormResize(Sender: TObject); | + | <source lang="pascal">procedure TForm1.FormResize(Sender: TObject); |
var tmpBool : Boolean; | var tmpBool : Boolean; | ||
begin | begin | ||
Zeile 98: | Zeile 108: | ||
glLoadIdentity; | glLoadIdentity; | ||
IdleHandler(Sender, tmpBool); | IdleHandler(Sender, tmpBool); | ||
− | end;</ | + | end;</source> |
+ | |||
+ | Außerdem müssen im '''const''' Teil die beiden Konstanten Near- bzw. FarClipping definiert werden. Diese geben die Entfernung für die [[Clipping Plane|Clippingebenen]] (Szenenbegrenzung) an und spielen beim [[Tiefentest]] eine gewisse Rolle. | ||
− | + | <source lang="pascal"> NearClipping = 1; | |
+ | FarClipping = 1000;</source> | ||
− | + | '''Zu FormResize''': | |
− | |||
− | + | '''Zeile 2:''' Diese Boolean-Variable wird in Zeile 11 verwendet und ist nur ein [[Dummy]]. | |
− | '''Zeile 2:''' Diese Boolean-Variable wird in Zeile 11 verwendet und ist nur ein Dummy. | ||
− | '''Zeile 4:''' Mittels [[ | + | '''Zeile 4:''' Mittels [[glViewport]] sagt Ihr OpenGL, wie groß die OpenGL-Ausgabe werden soll. Genau diese Größe hatte sich ja durch das Resize verändert. |
− | '''Zeile 5/9:''' Hier seht Ihr | + | '''Zeile 5/9:''' Hier seht Ihr zwei der drei möglichen Matrixmodi. '''GL_PROJECTION''' wird benutzt, um nachfolgend die OpenGL-Ausgabe zu manipulieren, '''GL_MODELVIEW''', um OpenGL mit Daten zu füttern. |
'''Zeile 6:''' [[glLoadIdentity]] füllt die aktuelle Matrix mit der Identitätsmatrix. | '''Zeile 6:''' [[glLoadIdentity]] füllt die aktuelle Matrix mit der Identitätsmatrix. | ||
− | '''Zeile 7:''' Hier wird eingestellt wie der Betrachter die Welt sehen soll | + | '''Zeile 7:''' Hier wird eingestellt, wie der Betrachter die Welt sehen soll. |
− | |||
− | |||
+ | '''Zeile 11:''' Was der IdleHandler macht, kommt später im Abschnitt 1.3.3 (Zeichenroutine). | ||
Nun noch schnell das '''FormDestroy''': | Nun noch schnell das '''FormDestroy''': | ||
− | <pascal>procedure TForm1.FormDestroy(Sender: TObject); | + | <source lang="pascal">procedure TForm1.FormDestroy(Sender: TObject); |
begin | begin | ||
DeactivateRenderingContext; | DeactivateRenderingContext; | ||
DestroyRenderingContext(RC); | DestroyRenderingContext(RC); | ||
ReleaseDC(Handle, DC); | ReleaseDC(Handle, DC); | ||
− | end;</ | + | end;</source> |
Was es macht? Steht doch da: Den RenderingContext deaktivieren und freigeben. | Was es macht? Steht doch da: Den RenderingContext deaktivieren und freigeben. | ||
− | |||
===Die Zeichenroutine=== | ===Die Zeichenroutine=== | ||
Das Herzstück unseres Templates fehlte bisher. Irgendwann muss der Grafikkarte ja auch gesagt werden, was sie denn überhaupt ausgeben soll. Das kommt jetzt: '''TForm1.Render''' | Das Herzstück unseres Templates fehlte bisher. Irgendwann muss der Grafikkarte ja auch gesagt werden, was sie denn überhaupt ausgeben soll. Das kommt jetzt: '''TForm1.Render''' | ||
− | <pascal>procedure TForm1.Render; | + | <source lang="pascal">procedure TForm1.Render; |
begin | begin | ||
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); | glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); | ||
Zeile 141: | Zeile 150: | ||
glLoadIdentity; | glLoadIdentity; | ||
gluPerspective(45.0, ClientWidth/ClientHeight, NearClipping, FarClipping); | gluPerspective(45.0, ClientWidth/ClientHeight, NearClipping, FarClipping); | ||
− | |||
− | |||
glMatrixMode(GL_MODELVIEW); | glMatrixMode(GL_MODELVIEW); | ||
glLoadIdentity; | glLoadIdentity; | ||
+ | |||
+ | glTranslatef(0, 0, -5); | ||
glBegin(GL_QUADS); | glBegin(GL_QUADS); | ||
Zeile 155: | Zeile 164: | ||
SwapBuffers(DC); | SwapBuffers(DC); | ||
− | end;</ | + | end;</source> |
− | '''Zeile 3:''' Dieser Aufruf sorgt dafür, dass | + | '''Zeile 3:''' Dieser Aufruf sorgt dafür, dass [[Farbpuffer]] und [[Tiefenpuffer]] gelöscht werden. Wenn man das nicht macht, sieht man alles mögliche, nur nicht das, was Ihr rendern wollt. Probiert es ruhig mal ohne aus! Man wird dadurch nicht dümmer. Der Farbpuffer wird nicht einfach gelöscht sondern mit der ClearColor überschrieben, die ihr per glClearColor definiert habt. (siehe FormCreate-Methode) |
− | '''Zeile 7:''' Hier wird wieder die Perspektive gesetzt. Dieser Aufruf und der bei '''FormResize''' | + | '''Zeile 7:''' Hier wird wieder die Perspektive gesetzt. Dieser Aufruf und der bei '''FormResize''' müssen von den Parametern identisch sein. Sonst sieht die Ausgabe nach einem Resize ganz einfach anders aus. |
− | müssen von den Parametern identisch sein. Sonst sieht die Ausgabe nach einem Resize | + | Wenn sich die Perspektive zwischen den Renderdurchgängen nicht ändert, kann dieser Befehl auch weggelassen werden. |
− | |||
− | '''Zeile | + | '''Zeile 12:''' Dieser Aufruf verschiebt die "Kamera" (so etwas gibt eigentlich nicht, aber das nur am Rande. Mehr zu diesem Thema gibt es im [[Tutorial Matrix2]]) etwas nach hinten. Schließlich wollen wir das, was wir zeichnen, auch sehen. Alles, was zu nah ist, wird durch die '''Near-[[Clipping Plane]]''' abgeschnitten. |
− | |||
− | [[Bild: | + | [[Bild:GlShadeModel_SMOOTH.jpg|right]] |
− | '''Zeile 14:''' [[glBegin]]/[[glEnd]] kapseln die eigentlichen Zeichenbefehle. Diese sorgen hier für ein '''hübsches buntes Viereck'''. Das soll für den ersten Test ausreichen. Wichtig ist in dem Zusammenhang noch | + | '''Zeile 14:''' [[glBegin]]/[[glEnd]] kapseln die eigentlichen Zeichenbefehle. Diese sorgen hier für ein '''hübsches buntes Viereck''' (Die ClearColor hatte ich aus ästhetischen Gründen für dieses Bild geändert. ;) ). Das soll für den ersten Test ausreichen. Wichtig ist in dem Zusammenhang noch Folgendes: OpenGL ist es egal, woher ein Befehl kommt. Alles wird ausgewertet und landet unter Umständen im Framebuffer. Ihr könnt also ein Unterprogramm, welches ein Unterprogramm, welches ein Unterprogramm ... , welches die OpenGL Befehle enthält, schreiben. Das interessiert OpenGL bzw. die Grafikkarte überhaupt nicht. |
− | '''Zeile 21:''' [[SwapBuffers]] sorgt | + | '''Zeile 21:''' [[SwapBuffers]] sorgt dafür, dass der Inhalt des [[Framebuffer]]s auf dem Bildschirm erscheint. Ohne diesen Befehl seht Ihr gar nichts von OpenGL. (Interessanter Artikel dazu: [[Doppelpufferung]]) |
− | Bildschirm erscheint. Ohne diesen Befehl seht Ihr gar nichts von OpenGL. (Interessanter Artikel dazu: [[Doppelpufferung]]) | ||
+ | So... ganz toll. Jetzt habt Ihr Eure Zeichenfunktion... und nun? Irgendwie müsst Ihr diese auch aufrufen. Das wäre aber zu einfach. Die Ausgabe ändert sich ja normalerweise (z.B. in Spielen). Deshalb muss die Zeichenfunktion immer wieder ausgegeben werden. Dazu gibt es zwei Möglichkeiten, die beide Ihre Vor- und Nachteile haben. | ||
− | + | <div align="center"> | |
− | + | {|{{Prettytable_B1}} | |
− | {| | ||
!Argument | !Argument | ||
!Timer | !Timer | ||
Zeile 205: | Zeile 211: | ||
|NEIN! | |NEIN! | ||
|} | |} | ||
+ | </div> | ||
− | Wer einfache Anwendungen schreiben möchte, die auch mit 25 | + | Wer einfache Anwendungen schreiben möchte, die auch mit 25 [[FPS]] (Bilder pro Sekunde) auskommen, und den Akku von Laptopusern schonen will, sollte die Timervariante nutzen. Wer die Potenziale der Grafikkarten voll ausnutzen möchte, sollte OnIdle verwenden. |
− | |||
====Methode 1: Timer==== | ====Methode 1: Timer==== | ||
− | Bei dieser Methode muss ein Timer (zu finden bei den Systemkomponenten) auf das Formular gezogen werden. Der Timer besitzt eine Eigenschaft names "Interval". Mit dieser Eigenschaft kann man einstellen | + | Bei dieser Methode muss ein Timer (zu finden bei den Systemkomponenten) auf das Formular gezogen werden. Der Timer besitzt eine Eigenschaft names "Interval". Mit dieser Eigenschaft kann man einstellen, nach wie vielen Millisekunden das Ereignis OnTimer ausgelöst wird. Man kann "Interval" nicht beliebig verkleinern. Werte unter 25 können von dem Standardtimer, den Windows verwendet, nicht mehr korrekt erzeugt werden. |
Der Inhalt von OnTimer könnte dieser sein: | Der Inhalt von OnTimer könnte dieser sein: | ||
− | <pascal>procedure TForm1.Timer1Timer(Sender: TObject); | + | <source lang="pascal">procedure TForm1.Timer1Timer(Sender: TObject); |
begin | begin | ||
− | + | inc(FrameCount); | |
− | + | Render; | |
− | + | If FrameCount = 20 then begin | |
− | + | ErrorHandler; | |
− | + | FrameCount := 0; | |
− | + | end; | |
− | + | end;</source> | |
− | end;</ | ||
− | Tiefgreifende Erklärungen sind hier nicht notwendig. Was der ErrorHandler ist wird nach der Methode 2 erklärt. | + | Tiefgreifende Erklärungen sind hier nicht notwendig. Was der ErrorHandler ist, wird nach der Methode 2 erklärt. |
+ | ====Methode 2: OnIdle==== | ||
− | + | OnIdle ist ein besonderes Ereignis, welches das gesamte Programm betrifft. Wenn die Anwendung nichts zu tun hat, also faul ist (englisch idle), tritt das Ereignis ein. | |
− | |||
Die Methode mit OnIdle kann gleich mit zum Auswerten der Framezahlen (Anzahl Bildwiederholungen pro Sekunde) benutzt werden (Framecounter). Der nachfolgende Code enthält selbigen bereits. | Die Methode mit OnIdle kann gleich mit zum Auswerten der Framezahlen (Anzahl Bildwiederholungen pro Sekunde) benutzt werden (Framecounter). Der nachfolgende Code enthält selbigen bereits. | ||
− | <pascal>procedure TForm1.IdleHandler(Sender: TObject; var Done: Boolean); | + | <source lang="pascal">procedure TForm1.IdleHandler(Sender: TObject; var Done: Boolean); |
begin | begin | ||
StartTime:= GetTickCount; | StartTime:= GetTickCount; | ||
Render; | Render; | ||
DrawTime:= GetTickCount - StartTime; | DrawTime:= GetTickCount - StartTime; | ||
− | + | Inc(TimeCount, DrawTime); | |
− | + | Inc(FrameCount); | |
if TimeCount >= 1000 then begin | if TimeCount >= 1000 then begin | ||
Zeile 250: | Zeile 255: | ||
Done:= false; | Done:= false; | ||
− | end;</ | + | end;</source> |
− | '''Zeile 3:''' Mittels GetTickCount wird die Systemzeit gemessen. Dies ist nicht nötig um | + | '''Zeile 3:''' Mittels GetTickCount wird die Systemzeit gemessen. Dies ist nicht nötig, um erfolgreich zu zeichnen, sondern dient ausschließlich der Berechnung der Framerate. Diese wiederum ist ein guter Performancemesser. |
− | erfolgreich zu zeichnen, sondern dient ausschließlich der Berechnung der Framerate. Diese wiederum ist ein guter Performancemesser. | ||
'''Zeile 4:''' Hier erfolgt der Aufruf unserer Zeichenroutine. | '''Zeile 4:''' Hier erfolgt der Aufruf unserer Zeichenroutine. | ||
− | '''Zeile 9:''' Der hier angeordnete Block wird nur pro Sekunde einmal ausgeführt und sorgt | + | '''Zeile 9:''' Der hier angeordnete Block wird nur pro Sekunde einmal ausgeführt und sorgt dafür, dass die Framerate angezeigt wird. Außerdem wird der Errorhandler aufgerufen. |
− | dafür, dass die Framerate angezeigt wird. Außerdem wird der Errorhandler aufgerufen. | + | |
+ | '''Zeile 14:''' Der Errorhandler wird im Anschluss beschrieben. | ||
+ | |||
+ | '''Zeile 17:''' Wenn ''Done'' nach der Ausführung ''false'' ist und das Programm wieder nichts zu tun hat, wird OnIdle erneut ausgeführt. Wenn ''Done = true'' ist wird OnIdle nur einmal ausgeführt. | ||
− | + | Um auf das "Idle-Event" reagieren zu können, müsst ihr jetzt nur noch diese Funktion an das Event koppeln. Das macht ihr, indem ihr den nachfolgenden Code in die letzte Zeile eurer FormCreate-Methode schreibt. | |
− | + | <source lang="pascal">Application.OnIdle := IdleHandler;</source> | |
− | |||
− | + | Wie ihr bald selbst feststellen werdet, sorgt diese Methode für eine hundertprozentige Prozessorauslastung (Und das ist schlecht für die Akkulaufzeiten von Notebooks). Umgehen könnt ihr dies mit einem kleinen Trick: | |
− | + | :Fügt vor dem "Done := false" (Zeile 17 des IdleHandlers) noch ein sleep(1) oder sleep(5) ein. Dadurch sinkt die Prozessorlast auf ca. 80%, was schon ein Fortschritt ist. | |
− | <pascal>procedure TForm1.ErrorHandler; | + | ''Noch ein Hinweis, weil im Forum öfters Probleme damit auftraten: OnIdle ist eine Funktion des Formulars und muss entsprechend auch in der Klassendeklaration eingetragen werden. Am Ende des Tutorials findet ihr die Klassendeklartion aus meinem Beispiel. Wenn ihr das vergesst meldet der Compiler "Undefinierter Bezeichner: 'IdleHandler'".'' |
+ | |||
+ | ===Der ErrorHandler - Fehler erkannt, Fehler gebannt=== | ||
+ | Der Errorhandler ist wieder eine sehr einfache Funktion, denn OpenGL bietet von Haus aus eine Möglichkeit, OpenGL-Fehler zu erkennen. Deshalb ist der ErrorHandler auch so klein: | ||
+ | |||
+ | <source lang="pascal">procedure TForm1.ErrorHandler; | ||
begin | begin | ||
− | gluErrorString(glGetError); | + | Form1.Caption := gluErrorString(glGetError); |
− | end;</ | + | end;</source> |
− | |||
− | |||
+ | Enttäuscht? Ihr könnt den ErrorHandler nach belieben auch komplexer machen. Zum Beispiel könnt ihr, anstatt die Fehler im Fenstertitel anzuzeigen, lieber den Fehler in ein Logfile schreiben. Ganz nebenbei: Falls kein Fehler auftritt, liefert [[glGetError]] '''GL_NO_ERROR'''. | ||
− | Eure Klasse TForm1 sollte also jetzt so | + | ===Das fertige Templateformular=== |
− | <pascal>TForm1 = class(TForm) | + | Eure Klasse TForm1 sollte also jetzt so oder so ähnlich aussehen: |
+ | <source lang="pascal">TForm1 = class(TForm) | ||
procedure FormCreate(Sender: TObject); | procedure FormCreate(Sender: TObject); | ||
procedure IdleHandler(Sender: TObject; var Done: Boolean); | procedure IdleHandler(Sender: TObject; var Done: Boolean); | ||
Zeile 286: | Zeile 297: | ||
Frames, DrawTime : Cardinal; //& Timebased Movement | Frames, DrawTime : Cardinal; //& Timebased Movement | ||
procedure SetupGL; | procedure SetupGL; | ||
− | |||
procedure Render; | procedure Render; | ||
procedure ErrorHandler; | procedure ErrorHandler; | ||
Zeile 292: | Zeile 302: | ||
DC : HDC; //Handle auf Zeichenfläche | DC : HDC; //Handle auf Zeichenfläche | ||
RC : HGLRC;//Rendering Context | RC : HGLRC;//Rendering Context | ||
− | end;</ | + | end;</source> |
− | |||
− | |||
− | '''Zeile | + | '''Zeile 3:''' Wie man sieht, benutzt mein Template die zweite Methode (OnIdle). |
+ | '''Zeile 8:''' Was [[Timebased Movement]] ist, könnt ihr ja mal nachlesen. | ||
− | + | '''Zeile 14/15:''' HDC und HGLRC sind Typen die von Windows zur Verfügung gestellt werden. Ihr findet sie in der Unit "Windows". (Diese Unit sollte bei einem neuen Projekt bereits durch Delphi eingebunden worden sein.) | |
+ | So... fertig. Eigentlich seid Ihr jetzt soweit, von den Tutorialschreibern so richtig mit OpenGL-Wissen vollgepumpt zu werden. Das nachfolgende Kapitel könnt Ihr euch trotzdem ruhigen Gewissens durchlesen. Wer es liest, tappt vielleicht nicht gleich mit der ersten Frage im DGL-Forum ins berüchtigte Fettnäpfchen. | ||
==Tipps für den OpenGL Anfänger== | ==Tipps für den OpenGL Anfänger== | ||
− | Wenn man mit OpenGL anfängt ist man meist total perplex und ein "Das war ja einfach!" huscht einem nicht nur einmal über die Lippen. Vor allem zu Beginn Eurer OpenGL-Karriere werdet Ihr viel lernen und dabei nur auf verhältnismäßig geringen Widerstand stoßen. Aber glaubt mir es gibt ihn... | + | Wenn man mit OpenGL anfängt, ist man meist total perplex und ein "Das war ja einfach!" huscht einem nicht nur einmal über die Lippen. Vor allem zu Beginn Eurer OpenGL-Karriere werdet Ihr viel lernen und dabei nur auf verhältnismäßig geringen Widerstand stoßen. Aber glaubt mir, es gibt ihn... |
− | Häufig tauchen | + | Häufig tauchen hochmotivierte OpenGL Anfänger im Forum auf und verkünden stolz, sie würden gerade an einer Engine arbeiten die ''"nur auf Doom 1 Niveau arbeiten soll"''. |
− | Dazu gibt es häufig eine durchaus beachtliche Anzahl von Forenmitgliedern die dann ungefähr folgendes sagen: | + | Dazu gibt es häufig eine durchaus beachtliche Anzahl von Forenmitgliedern die dann ungefähr folgendes sagen: ''"Fang gar nicht erst damit an. Vergiss es, und komm in einem Jahr noch mal darauf zurück!"'' (Die Forensuche sollte euch einige dieser Threads zeigen) |
− | Sind das | + | Sind das böse Pessimisten, die Probleme nutzen um aufzugeben? '''Nein!''' Sie haben zumeist die Erfahrungen gemacht, die Ihr noch machen werdet. Deshalb will ich euch an dieser Stelle einweihen: |
+ | <u>1. Je älter der Code ist, desto besser wird er.</u> | ||
− | + | Leider nein. Code und Wein unterscheiden sich hier leider grundlegend. Viele... die meisten... eigentlich alle von uns (die weniger als 3Jahre programmieren) bekommen Ausschlag, wenn sie sich ihren Code vom Vorjahr angucken. Erst wenn man so schätzungsweise 3 bis 5 Jahre mit OpenGL gearbeitet hat, hat man ein echtes Gefühl für den Code. Am Anfang hat man nämlich, ob man will oder nicht, die Tendenz, sich schon nach den ersten 3 Wochen Programmierarbeit den Code so zu zerschießen, dass die weitere Arbeit keinen Spaß mehr macht. | |
− | |||
− | Leider nein. Code und Wein unterscheiden sich hier leider grundlegend. Viele ... die meisten ... eigentlich alle von uns bekommen Ausschlag, wenn sie sich ihren Code | ||
''Ich selbst wollte jetzt ein Projekt weiterbearbeiten, welches ungefähr ein 3/4 Jahr alt ist... Ich hab den Code weggeworfen und das fast fertige Spiel neu angefangen. (Glaubt mir: Aus solchen Fehlern lernt man!)'' | ''Ich selbst wollte jetzt ein Projekt weiterbearbeiten, welches ungefähr ein 3/4 Jahr alt ist... Ich hab den Code weggeworfen und das fast fertige Spiel neu angefangen. (Glaubt mir: Aus solchen Fehlern lernt man!)'' | ||
+ | <u>2. Große Projekte = Großer Ruhm</u> | ||
− | + | Stimmt! ABER Ruhm gibt es in der Szene nur für beendete Projekte. Und dreimal dürft Ihr raten, was durch die im Punkt 1 angesprochenen Probleme meist nicht mit euren Projekten passiert... | |
− | + | Wenn Ihr OpenGL nur zur Visualisierung von z.B. wissenschaftlichen Ergebnissen benutzt, ist die Arbeit im Bezug auf OpenGL sehr übersichtlich. Wenn Ihr allerdings Spiele programmieren wollt, werdet Ihr schnell merken, dass Probleme auf euch zukommen werden, die euch beim Projektstart völlig unbekannt waren. Dies ist ein sicheres Zeichen dafür, dass doch noch etwas mehr Erfahrung nötig sein wird. | |
− | + | '''Und woher soll ich bitte Erfahrung nehmen?''' | |
− | + | Darin liegt der Trick: | |
− | Darin liegt der Trick: | ||
− | |||
+ | Alle OpenGLer haben einmal "klein" angefangen. Berühmt-berüchtigt sind die 3DPong-Clone, die zahlreich im Internet anzufinden sind. Auch Tetris-, Memory- oder "Vier Gewinnt"-Clone sind solche stillen Zeugen eines großen Lernvorgangs. Das Besondere an solchen Spielen ist, dass die Spiellogik relativ einfach ist und euch damit nur wenig von der Visualisierung ablenkt. | ||
− | Ihr denkt jetzt bestimmt "Was kann man denn schon in so nem Clon unterbringen?" VERDAMMT VIEL! Hier mal ein kleiner Auszug: | + | Ihr denkt jetzt bestimmt "Was kann man denn schon in so nem Clon unterbringen?" '''VERDAMMT VIEL!''' Hier mal ein kleiner Auszug: |
*3D-Spieldarstellung | *3D-Spieldarstellung | ||
Zeile 340: | Zeile 349: | ||
*Kamerasteuerung /Bewegung durch die Szene (Da gibt es ganze Tutorials dazu) | *Kamerasteuerung /Bewegung durch die Szene (Da gibt es ganze Tutorials dazu) | ||
− | Als Ansporn solltet Ihr | + | Als Ansporn solltet Ihr euch am Anfang ein kleines Ziel setzen und sagen "Ich möchte den besten und schönsten XYZ-Clon im ganzen Internet schaffen!". So etwas trifft in der Szene auf wesentlich mehr Anerkennung, als wenn mal wieder ein Anfänger etwas von Engine und Doom X faselt. |
− | |||
− | + | Wenn Ihr ein oder zwei solcher Projekte abgeschlossen habt, und regelmäßig im Forum bzw. Wiki gelesen habt, werdet Ihr es schon merken, wenn Euer Traumprojekt endlich in Angriff genommen werden kann. (Ganz vergessen müsst ihr es nämlich doch nicht ;) ) | |
− | + | '''Als kurze Zusammenfassung solltet Ihr euch merken:''' | |
− | |||
− | |||
− | *Ein | + | *Ein fertiges Projekt bringt euch Ruhm (Seelenbalsam). |
− | *Ein | + | *Ein hübsches, fertiges Projekt bringt euch mehr Ruhm. |
+ | *Ein großes, fertiges, hübsches Projekt bringt euch noch mehr Ruhm. | ||
+ | *Ein abgebrochenes oder eingefrorenes Projekt bringt euch Frust. | ||
+ | *Ein großes, abgebrochenes Projekt bringt euch noch mehr Frust, denn der verschwendeten Zeit werdet Ihr nachtrauern. | ||
<u>3.Das es lang dauert ist egal. Ich interessiere mich halt dafür.</u> | <u>3.Das es lang dauert ist egal. Ich interessiere mich halt dafür.</u> | ||
− | Diese Aussage gilt nur dann, wenn Ihr schon über ein Jahr an ein und demselben Projekt gearbeitet habt. Anfänglich ist das kein Problem. Aber wenn man lange an etwas arbeitet und die gemachten Änderungen sind nicht sichtbar (weil sie z.B. den eigentlichen Motor der Anwendung betreffen und nicht die Ausgabe) dann verliert man schon mal die Lust. Folge sind die berüchtigten "Erfolgsmeldungen" wie: "Das Projekt wurde von mir bis auf weiteres aufs Eis gelegt. Ich werde sicherlich später daran weiterarbeiten." | + | Diese Aussage gilt nur dann, wenn Ihr schon über ein Jahr an ein und demselben Projekt gearbeitet habt. Anfänglich ist das kein Problem. Aber wenn man lange an etwas arbeitet und die gemachten Änderungen sind nicht sichtbar (weil sie z.B. den eigentlichen Motor der Anwendung betreffen und nicht die Ausgabe), dann verliert man schon mal die Lust. Folge sind die berüchtigten "Erfolgsmeldungen" wie: "Das Projekt wurde von mir bis auf weiteres aufs Eis gelegt. Ich werde sicherlich später daran weiterarbeiten." Was stand gleich nochmal unter Punkt 1? - Das Endergebnis dürfte klar sein. |
+ | |||
+ | '''Tipps für den Erfolg''' | ||
+ | # Lernt eure Programmiersprache kennen. | ||
+ | # Lernt ObjektOrientierung. Nur weil ihr Klassen benutzt, heißt das nicht, dass ihr Objekt orientiert programmiert. | ||
+ | # Lernt eure Entwicklungsumgebung kennen. (Debugging! Dazu gibts hier ein Tutorial für Delphi.) | ||
+ | # Kleine Brötchen backen (Testprojekte) und Projekte zu Ende bringen! | ||
==Nachwort== | ==Nachwort== | ||
− | Soviel zur Euphoriebremse. OpenGL ist toll. OpenGL ist die Lösung für Eure Traumanwendung. Aber die wird auch mit OpenGL nicht von heute auf morgen programmiert. Deshalb heißt es Tutorials lesen kleine Testanwendungen schreiben um Effekte zu testen und Projekte bearbeiten. Dann wird es auch etwas mit den Traumprojekten. Zudem kann man anfangs auch versuchen in sehr kleinen Anwendungen einen Effekt auszuprobieren und diesen in Form eines kleinen Beispielprogramms oder eines Tutorials zu veröffenlichen. Somit lernt man nicht nur selbst etwas dazu... | + | Soviel zur Euphoriebremse. OpenGL ist toll. OpenGL ist die Lösung für Eure Traumanwendung. Aber die wird auch mit OpenGL nicht von heute auf morgen programmiert. Deshalb heißt es, Tutorials lesen, kleine Testanwendungen schreiben um Effekte zu testen und Projekte bearbeiten. Dann wird es auch etwas mit den Traumprojekten. Zudem kann man anfangs auch versuchen, in sehr kleinen Anwendungen einen Effekt auszuprobieren und diesen in Form eines kleinen Beispielprogramms oder eines Tutorials zu veröffenlichen. Somit lernt man nicht nur selbst etwas dazu... |
+ | Nach diesen unglaublich Weise klingenden Worten, die ich mit einem hoch ernsten Gesicht geschrieben habe, könnt ihr euch jetzt hochmotiviert an die restlichen Tutorials machen. | ||
− | + | Bis bald im [http://delphiGL.com/forum Forum] | |
+ | '''Euer''' | ||
− | |||
− | |||
'''Flash (Kevin Fleischer)''' | '''Flash (Kevin Fleischer)''' | ||
+ | PS: Feedback wird nicht nur gewünscht, sondern ausdrücklich gefordert. Deshalb: Ab mit deinen Meinungen und Ratschlägen ins [http://www.delphigl.com/forum/viewforum.php?f=8 Feedback-Forum]! | ||
− | + | {{TUTORIAL_NAVIGATION|-|[[Tutorial Lektion 1]]}} | |
− | + | [[Kategorie:Tutorial|Quickstart]] |
Aktuelle Version vom 15. Juli 2018, 22:06 Uhr
Inhaltsverzeichnis
Quickstart: Delphi & OpenGL
Einleitung
Willkommen beim Delphi & OpenGL Quickstart. Diese kurze Einleitung soll euch auf die Tutorials bei DelphiGL.com und allgemein auf die Grafikprogrammierung mit OpenGL und Delphi vorbereiten. Dieser Quickstart ist kein Tutorial für sich, sondern soll ein Grundgefühl vermitteln, wie OpenGL und Delphi miteinander arbeiten.
*Zur Erklärung für nicht Delphiprogrammierer: die VCL ist die Visuelle Component Library von Delphi. Diese enthält die GUI Komponenten (z.B. Formulare) aus denen man unter Delphi Anwendungen zusammensetzen kann.
Wozu OpenGL? Mit OpenGL kann man eine Vielzahl von Aufgaben bewältigen. Ob man Forschungsergebnisse aller Art visualisieren, 2D oder 3D Spiele schreiben oder einfach seiner Anwendung eine Oberfläche geben möchte, die nicht dem grauen Standardlook entspricht. All das ist möglich mit OpenGL.
Wie fange ich an?
Genau zwei Dinge braucht der OpenGL-Programmierer, um effektiv arbeiten zu können:
- Einen OpenGL Header
- Eine Codebasis, von der aus man neue Projekte starten kann (ein sogenanntes Template)
Der OpenGL-Header
Das is ja easy! Denn Delphi bringt ja schon einen OpenGL-Header mit...
STOP!
Denn wir reden hier von einem guten Header. Leider ist der standardmäßig von Delphi mitgelieferte Header alles andere als zu empfehlen. Er ist fehlerhaft, hält sich nicht an OpenGL-Normen und ist außerdem absolut veraltet.
Was nun? Ganz einfach: Bei DelphiGL.com (kurz DGL) gibt es DEN OpenGL-Header für alle Pascalsprachen: Die DGLOpenGL.pas.
Diesen solltet Ihr euch jetzt besorgen, wenn Ihr ihn nicht schon habt. Der Header wird bei neuen OpenGL-Versionen vom DGL-Team aktualisiert, und ist so immer auf der Höhe der Zeit.
Codebasis/Templates
So... das war schon alles, was Ihr aus dem Netz benötigt. Den Rest machen wir jetzt per Hand.
Im nächsten Kapitel zeige ich euch wie man sich ein einfaches Template schreibt. Natürlich hat DelphiGL.com auch bereits fertige Lösungen, die durchaus zu empfehlen sind und auch extra Features wie Vollbildrendering besitzen. ABER aus Erfahrung kann ich sagen: Man findet sich im eigenen Code viel einfacher zurecht. (Und die Extras kann man nachher immer noch einbauen.)
Das Template - Delphi fit für OpenGL machen
Bevor man wirklich loslegen kann, muss noch die runtergeladene DGLOpenGL.pas an den richtigen Ort gebracht werden. Gut wäre z.B., sie in das Verzeichnis "\lib" in Eurem Delphiverzeichnis zu legen. (Wenn ihr das DGLSDK verwendet, wurden die Suchpfade schon eingerichtet.)
Dann startet mal Delphi. Vor euch sollte jetzt ein leeres Projekt erscheinen. Das leere Formular kann gleich minimiert werden, denn jetzt wird erstmal hübsch gecodet.
Am Ende von Kapitel 1.3 findet ihr den Kopf der Template-Klasse. Dort seht ihr auch, welche Variablen von welchem Typ deklariert werden müssen. |
Initialisieren von OpenGL
Dieser Teil ließ früher dem OpenGL Anfänger die Haare nicht nur zu Berge stehen, sondern gleich ausfallen. Dank der DGLOpenGL.pas wurde das aber um Längen einfacher.
Zuerst einmal solltet Ihr die DGLOpenGL.pas in die uses-Klausel des interface-Teils der Unit1 schreiben.
Die eigentliche Initialisierung soll direkt beim Erstellen des Formulars gemacht werden. Deshalb kommt der folgende Quelltext ins OnCreate-Ereignis des Formulars.
procedure TForm1.FormCreate(Sender: TObject);
begin
DC:= GetDC(Handle);
if not InitOpenGL then Application.Terminate;
RC:= CreateRenderingContext( DC,
[opDoubleBuffered],
32,
24,
0,0,0,
0);
ActivateRenderingContext(DC, RC);
end;
Zeile 3: Hier wird der Gerätekontext (Device Context) von Formular Form1 abgefragt.
Zeile 4: Mit InitOpenGL wird OpenGL initialisiert. Wenn das nicht funktioniert, wird die gesamte Anwendung sofort beendet.
Zeile 5: Hier wird der Renderkontext erzeugt. Den braucht OpenGL zum Zeichnen auf das Formular. Mehr dazu lernt ihr im Tutorial_lektion1.
Zeile 11: Abschließend wird der Renderkontext aktiviert. OpenGL ist jetzt prinzipiell startbereit.
DC und RC sind Eigenschaften des Formulars. Siehe Definition des Templateformulars. |
Nach dieser durchaus simplen Initialisierung (man kann auch alles per Hand machen, was InitOpenGL macht!) steht OpenGL ziemlich nackt da. Soll heißen, alle OpenGL Eigenschaften/Zustände stehen auf den definierten Anfangswerten. Es kommt aber durchaus oft - eigentlich ständig - vor, dass bestimmte Einstellungen von OpenGL benutzt werden sollen. Deshalb schreiben wir uns noch eine kleine Zusatzprozedur: SetupGL
procedure TForm1.SetupGL;
begin
glClearColor(0.3, 0.4, 0.7, 0.0); //Hintergrundfarbe: Hier ein leichtes Blau
glEnable(GL_DEPTH_TEST); //Tiefentest aktivieren
glEnable(GL_CULL_FACE); //Backface Culling aktivieren
end;
Was hier passiert, wird durch die Kommentare bereits erklärt (Für mehr Infos siehe Tiefentest bzw. Backface Culling).
Die Hintergrundfarbe könnt ihr nach Belieben (mit glClearColor) einstellen. (Wenn ihr später einmal geschlossene Szenen rendern wollt, dann ist es für die Entwicklung günstig eine sehr schräge Farbe als Hintergrundfarbe einzustellen, so findet man leichter Fehler in der Szene. In OpenGL werden Farben immer als Mischung aus Rot, Grün und Blau angegeben. Dies wird in späteren Tutorials noch erklärt. Aber herumspielen an den Werten hilft hier auch schnell weiter. ;-) )
Außerdem hat man ja hin und wieder auch noch globale Variablen, die man initialisieren möchte. Da wir mit solchen Sachen unser schön aufgeräumtes FormCreate nicht zumüllen wollen, bietet sich ein Unterprogramm namens InitGlobals oder kurz Init an. (Wobei das jetzt nicht direkt etwas mit OpenGL zu tun hat.) Beide Unterprogramme (SetupGL und Init) sollten am Ende von FormCreate gerufen werden:
[...]
ActivateRenderingContext(DC, RC);
SetupGL;
Init; //Was man sonst so initialisieren will.
end;
Die Ereignisbehandlung
Für OpenGL sind vor allem die Ereignisse von Bedeutung, die an der Zeichenfläche von OpenGL herumwerkeln. Da OpenGL direkt auf das Formular (oder auch auf ein Panel) zeichnet, müssen Ereignisse, die diese Zeichenfläche ändern, behandelt werden. Dies wären das OnResize- und das OnDestroy-Ereignis.
Zuerst FormResize:
procedure TForm1.FormResize(Sender: TObject);
var tmpBool : Boolean;
begin
glViewport(0, 0, ClientWidth, ClientHeight);
glMatrixMode(GL_PROJECTION);
glLoadIdentity;
gluPerspective(45.0, ClientWidth/ClientHeight, NearClipping, FarClipping);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity;
IdleHandler(Sender, tmpBool);
end;
Außerdem müssen im const Teil die beiden Konstanten Near- bzw. FarClipping definiert werden. Diese geben die Entfernung für die Clippingebenen (Szenenbegrenzung) an und spielen beim Tiefentest eine gewisse Rolle.
NearClipping = 1;
FarClipping = 1000;
Zu FormResize:
Zeile 2: Diese Boolean-Variable wird in Zeile 11 verwendet und ist nur ein Dummy.
Zeile 4: Mittels glViewport sagt Ihr OpenGL, wie groß die OpenGL-Ausgabe werden soll. Genau diese Größe hatte sich ja durch das Resize verändert.
Zeile 5/9: Hier seht Ihr zwei der drei möglichen Matrixmodi. GL_PROJECTION wird benutzt, um nachfolgend die OpenGL-Ausgabe zu manipulieren, GL_MODELVIEW, um OpenGL mit Daten zu füttern.
Zeile 6: glLoadIdentity füllt die aktuelle Matrix mit der Identitätsmatrix.
Zeile 7: Hier wird eingestellt, wie der Betrachter die Welt sehen soll.
Zeile 11: Was der IdleHandler macht, kommt später im Abschnitt 1.3.3 (Zeichenroutine).
Nun noch schnell das FormDestroy:
procedure TForm1.FormDestroy(Sender: TObject);
begin
DeactivateRenderingContext;
DestroyRenderingContext(RC);
ReleaseDC(Handle, DC);
end;
Was es macht? Steht doch da: Den RenderingContext deaktivieren und freigeben.
Die Zeichenroutine
Das Herzstück unseres Templates fehlte bisher. Irgendwann muss der Grafikkarte ja auch gesagt werden, was sie denn überhaupt ausgeben soll. Das kommt jetzt: TForm1.Render
procedure TForm1.Render;
begin
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_PROJECTION);
glLoadIdentity;
gluPerspective(45.0, ClientWidth/ClientHeight, NearClipping, FarClipping);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity;
glTranslatef(0, 0, -5);
glBegin(GL_QUADS);
glColor3f(1, 0, 0); glVertex3f(0, 0, 0);
glColor3f(0, 1, 0); glVertex3f(1, 0, 0);
glColor3f(0, 0, 1); glVertex3f(1, 1, 0);
glColor3f(1, 1, 0); glVertex3f(0, 1, 0);
glEnd;
SwapBuffers(DC);
end;
Zeile 3: Dieser Aufruf sorgt dafür, dass Farbpuffer und Tiefenpuffer gelöscht werden. Wenn man das nicht macht, sieht man alles mögliche, nur nicht das, was Ihr rendern wollt. Probiert es ruhig mal ohne aus! Man wird dadurch nicht dümmer. Der Farbpuffer wird nicht einfach gelöscht sondern mit der ClearColor überschrieben, die ihr per glClearColor definiert habt. (siehe FormCreate-Methode)
Zeile 7: Hier wird wieder die Perspektive gesetzt. Dieser Aufruf und der bei FormResize müssen von den Parametern identisch sein. Sonst sieht die Ausgabe nach einem Resize ganz einfach anders aus. Wenn sich die Perspektive zwischen den Renderdurchgängen nicht ändert, kann dieser Befehl auch weggelassen werden.
Zeile 12: Dieser Aufruf verschiebt die "Kamera" (so etwas gibt eigentlich nicht, aber das nur am Rande. Mehr zu diesem Thema gibt es im Tutorial Matrix2) etwas nach hinten. Schließlich wollen wir das, was wir zeichnen, auch sehen. Alles, was zu nah ist, wird durch die Near-Clipping Plane abgeschnitten.
Zeile 14: glBegin/glEnd kapseln die eigentlichen Zeichenbefehle. Diese sorgen hier für ein hübsches buntes Viereck (Die ClearColor hatte ich aus ästhetischen Gründen für dieses Bild geändert. ;) ). Das soll für den ersten Test ausreichen. Wichtig ist in dem Zusammenhang noch Folgendes: OpenGL ist es egal, woher ein Befehl kommt. Alles wird ausgewertet und landet unter Umständen im Framebuffer. Ihr könnt also ein Unterprogramm, welches ein Unterprogramm, welches ein Unterprogramm ... , welches die OpenGL Befehle enthält, schreiben. Das interessiert OpenGL bzw. die Grafikkarte überhaupt nicht.
Zeile 21: SwapBuffers sorgt dafür, dass der Inhalt des Framebuffers auf dem Bildschirm erscheint. Ohne diesen Befehl seht Ihr gar nichts von OpenGL. (Interessanter Artikel dazu: Doppelpufferung)
So... ganz toll. Jetzt habt Ihr Eure Zeichenfunktion... und nun? Irgendwie müsst Ihr diese auch aufrufen. Das wäre aber zu einfach. Die Ausgabe ändert sich ja normalerweise (z.B. in Spielen). Deshalb muss die Zeichenfunktion immer wieder ausgegeben werden. Dazu gibt es zwei Möglichkeiten, die beide Ihre Vor- und Nachteile haben.
Argument | Timer | OnIdle |
---|---|---|
Maximale Framezahl erreichbar "Benchmark" |
nein | ja |
Framezahl steuerbar | ja | bedingt (/umständlich) |
Für flüssige Animationen nutzbar (Egoshooter) |
bedingt/schlecht | ja |
Für Menüs nutzbar | ja | ja |
Für einfache Animationen nutzbar (Strategiespiele) |
ja | ja |
Laptopfreundlich (Anti-Akku-Killer) | ja | NEIN! |
Wer einfache Anwendungen schreiben möchte, die auch mit 25 FPS (Bilder pro Sekunde) auskommen, und den Akku von Laptopusern schonen will, sollte die Timervariante nutzen. Wer die Potenziale der Grafikkarten voll ausnutzen möchte, sollte OnIdle verwenden.
Methode 1: Timer
Bei dieser Methode muss ein Timer (zu finden bei den Systemkomponenten) auf das Formular gezogen werden. Der Timer besitzt eine Eigenschaft names "Interval". Mit dieser Eigenschaft kann man einstellen, nach wie vielen Millisekunden das Ereignis OnTimer ausgelöst wird. Man kann "Interval" nicht beliebig verkleinern. Werte unter 25 können von dem Standardtimer, den Windows verwendet, nicht mehr korrekt erzeugt werden.
Der Inhalt von OnTimer könnte dieser sein:
procedure TForm1.Timer1Timer(Sender: TObject);
begin
inc(FrameCount);
Render;
If FrameCount = 20 then begin
ErrorHandler;
FrameCount := 0;
end;
end;
Tiefgreifende Erklärungen sind hier nicht notwendig. Was der ErrorHandler ist, wird nach der Methode 2 erklärt.
Methode 2: OnIdle
OnIdle ist ein besonderes Ereignis, welches das gesamte Programm betrifft. Wenn die Anwendung nichts zu tun hat, also faul ist (englisch idle), tritt das Ereignis ein.
Die Methode mit OnIdle kann gleich mit zum Auswerten der Framezahlen (Anzahl Bildwiederholungen pro Sekunde) benutzt werden (Framecounter). Der nachfolgende Code enthält selbigen bereits.
procedure TForm1.IdleHandler(Sender: TObject; var Done: Boolean);
begin
StartTime:= GetTickCount;
Render;
DrawTime:= GetTickCount - StartTime;
Inc(TimeCount, DrawTime);
Inc(FrameCount);
if TimeCount >= 1000 then begin
Frames:= FrameCount;
TimeCount:= TimeCount - 1000;
FrameCount:= 0;
Caption:= InttoStr(Frames) + 'FPS';
ErrorHandler;
end;
Done:= false;
end;
Zeile 3: Mittels GetTickCount wird die Systemzeit gemessen. Dies ist nicht nötig, um erfolgreich zu zeichnen, sondern dient ausschließlich der Berechnung der Framerate. Diese wiederum ist ein guter Performancemesser.
Zeile 4: Hier erfolgt der Aufruf unserer Zeichenroutine.
Zeile 9: Der hier angeordnete Block wird nur pro Sekunde einmal ausgeführt und sorgt dafür, dass die Framerate angezeigt wird. Außerdem wird der Errorhandler aufgerufen.
Zeile 14: Der Errorhandler wird im Anschluss beschrieben.
Zeile 17: Wenn Done nach der Ausführung false ist und das Programm wieder nichts zu tun hat, wird OnIdle erneut ausgeführt. Wenn Done = true ist wird OnIdle nur einmal ausgeführt.
Um auf das "Idle-Event" reagieren zu können, müsst ihr jetzt nur noch diese Funktion an das Event koppeln. Das macht ihr, indem ihr den nachfolgenden Code in die letzte Zeile eurer FormCreate-Methode schreibt.
Application.OnIdle := IdleHandler;
Wie ihr bald selbst feststellen werdet, sorgt diese Methode für eine hundertprozentige Prozessorauslastung (Und das ist schlecht für die Akkulaufzeiten von Notebooks). Umgehen könnt ihr dies mit einem kleinen Trick:
- Fügt vor dem "Done := false" (Zeile 17 des IdleHandlers) noch ein sleep(1) oder sleep(5) ein. Dadurch sinkt die Prozessorlast auf ca. 80%, was schon ein Fortschritt ist.
Noch ein Hinweis, weil im Forum öfters Probleme damit auftraten: OnIdle ist eine Funktion des Formulars und muss entsprechend auch in der Klassendeklaration eingetragen werden. Am Ende des Tutorials findet ihr die Klassendeklartion aus meinem Beispiel. Wenn ihr das vergesst meldet der Compiler "Undefinierter Bezeichner: 'IdleHandler'".
Der ErrorHandler - Fehler erkannt, Fehler gebannt
Der Errorhandler ist wieder eine sehr einfache Funktion, denn OpenGL bietet von Haus aus eine Möglichkeit, OpenGL-Fehler zu erkennen. Deshalb ist der ErrorHandler auch so klein:
procedure TForm1.ErrorHandler;
begin
Form1.Caption := gluErrorString(glGetError);
end;
Enttäuscht? Ihr könnt den ErrorHandler nach belieben auch komplexer machen. Zum Beispiel könnt ihr, anstatt die Fehler im Fenstertitel anzuzeigen, lieber den Fehler in ein Logfile schreiben. Ganz nebenbei: Falls kein Fehler auftritt, liefert glGetError GL_NO_ERROR.
Das fertige Templateformular
Eure Klasse TForm1 sollte also jetzt so oder so ähnlich aussehen:
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure IdleHandler(Sender: TObject; var Done: Boolean);
procedure FormResize(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private { Private-Deklarationen }
StartTime, TimeCount, FrameCount : Cardinal; //FrameCounter
Frames, DrawTime : Cardinal; //& Timebased Movement
procedure SetupGL;
procedure Render;
procedure ErrorHandler;
public { Public-Deklarationen }
DC : HDC; //Handle auf Zeichenfläche
RC : HGLRC;//Rendering Context
end;
Zeile 3: Wie man sieht, benutzt mein Template die zweite Methode (OnIdle).
Zeile 8: Was Timebased Movement ist, könnt ihr ja mal nachlesen.
Zeile 14/15: HDC und HGLRC sind Typen die von Windows zur Verfügung gestellt werden. Ihr findet sie in der Unit "Windows". (Diese Unit sollte bei einem neuen Projekt bereits durch Delphi eingebunden worden sein.)
So... fertig. Eigentlich seid Ihr jetzt soweit, von den Tutorialschreibern so richtig mit OpenGL-Wissen vollgepumpt zu werden. Das nachfolgende Kapitel könnt Ihr euch trotzdem ruhigen Gewissens durchlesen. Wer es liest, tappt vielleicht nicht gleich mit der ersten Frage im DGL-Forum ins berüchtigte Fettnäpfchen.
Tipps für den OpenGL Anfänger
Wenn man mit OpenGL anfängt, ist man meist total perplex und ein "Das war ja einfach!" huscht einem nicht nur einmal über die Lippen. Vor allem zu Beginn Eurer OpenGL-Karriere werdet Ihr viel lernen und dabei nur auf verhältnismäßig geringen Widerstand stoßen. Aber glaubt mir, es gibt ihn...
Häufig tauchen hochmotivierte OpenGL Anfänger im Forum auf und verkünden stolz, sie würden gerade an einer Engine arbeiten die "nur auf Doom 1 Niveau arbeiten soll".
Dazu gibt es häufig eine durchaus beachtliche Anzahl von Forenmitgliedern die dann ungefähr folgendes sagen: "Fang gar nicht erst damit an. Vergiss es, und komm in einem Jahr noch mal darauf zurück!" (Die Forensuche sollte euch einige dieser Threads zeigen)
Sind das böse Pessimisten, die Probleme nutzen um aufzugeben? Nein! Sie haben zumeist die Erfahrungen gemacht, die Ihr noch machen werdet. Deshalb will ich euch an dieser Stelle einweihen:
1. Je älter der Code ist, desto besser wird er.
Leider nein. Code und Wein unterscheiden sich hier leider grundlegend. Viele... die meisten... eigentlich alle von uns (die weniger als 3Jahre programmieren) bekommen Ausschlag, wenn sie sich ihren Code vom Vorjahr angucken. Erst wenn man so schätzungsweise 3 bis 5 Jahre mit OpenGL gearbeitet hat, hat man ein echtes Gefühl für den Code. Am Anfang hat man nämlich, ob man will oder nicht, die Tendenz, sich schon nach den ersten 3 Wochen Programmierarbeit den Code so zu zerschießen, dass die weitere Arbeit keinen Spaß mehr macht.
Ich selbst wollte jetzt ein Projekt weiterbearbeiten, welches ungefähr ein 3/4 Jahr alt ist... Ich hab den Code weggeworfen und das fast fertige Spiel neu angefangen. (Glaubt mir: Aus solchen Fehlern lernt man!)
2. Große Projekte = Großer Ruhm
Stimmt! ABER Ruhm gibt es in der Szene nur für beendete Projekte. Und dreimal dürft Ihr raten, was durch die im Punkt 1 angesprochenen Probleme meist nicht mit euren Projekten passiert...
Wenn Ihr OpenGL nur zur Visualisierung von z.B. wissenschaftlichen Ergebnissen benutzt, ist die Arbeit im Bezug auf OpenGL sehr übersichtlich. Wenn Ihr allerdings Spiele programmieren wollt, werdet Ihr schnell merken, dass Probleme auf euch zukommen werden, die euch beim Projektstart völlig unbekannt waren. Dies ist ein sicheres Zeichen dafür, dass doch noch etwas mehr Erfahrung nötig sein wird.
Und woher soll ich bitte Erfahrung nehmen?
Darin liegt der Trick:
Alle OpenGLer haben einmal "klein" angefangen. Berühmt-berüchtigt sind die 3DPong-Clone, die zahlreich im Internet anzufinden sind. Auch Tetris-, Memory- oder "Vier Gewinnt"-Clone sind solche stillen Zeugen eines großen Lernvorgangs. Das Besondere an solchen Spielen ist, dass die Spiellogik relativ einfach ist und euch damit nur wenig von der Visualisierung ablenkt.
Ihr denkt jetzt bestimmt "Was kann man denn schon in so nem Clon unterbringen?" VERDAMMT VIEL! Hier mal ein kleiner Auszug:
- 3D-Spieldarstellung
- 2D Menüführung (glOrtho)
- Blending
- Texturen
- Licht und Materialien (Ihr werdet Augen machen!)
- Selektion (Hin und wieder zum Haare raufen.)
- Kamerasteuerung /Bewegung durch die Szene (Da gibt es ganze Tutorials dazu)
Als Ansporn solltet Ihr euch am Anfang ein kleines Ziel setzen und sagen "Ich möchte den besten und schönsten XYZ-Clon im ganzen Internet schaffen!". So etwas trifft in der Szene auf wesentlich mehr Anerkennung, als wenn mal wieder ein Anfänger etwas von Engine und Doom X faselt.
Wenn Ihr ein oder zwei solcher Projekte abgeschlossen habt, und regelmäßig im Forum bzw. Wiki gelesen habt, werdet Ihr es schon merken, wenn Euer Traumprojekt endlich in Angriff genommen werden kann. (Ganz vergessen müsst ihr es nämlich doch nicht ;) )
Als kurze Zusammenfassung solltet Ihr euch merken:
- Ein fertiges Projekt bringt euch Ruhm (Seelenbalsam).
- Ein hübsches, fertiges Projekt bringt euch mehr Ruhm.
- Ein großes, fertiges, hübsches Projekt bringt euch noch mehr Ruhm.
- Ein abgebrochenes oder eingefrorenes Projekt bringt euch Frust.
- Ein großes, abgebrochenes Projekt bringt euch noch mehr Frust, denn der verschwendeten Zeit werdet Ihr nachtrauern.
3.Das es lang dauert ist egal. Ich interessiere mich halt dafür.
Diese Aussage gilt nur dann, wenn Ihr schon über ein Jahr an ein und demselben Projekt gearbeitet habt. Anfänglich ist das kein Problem. Aber wenn man lange an etwas arbeitet und die gemachten Änderungen sind nicht sichtbar (weil sie z.B. den eigentlichen Motor der Anwendung betreffen und nicht die Ausgabe), dann verliert man schon mal die Lust. Folge sind die berüchtigten "Erfolgsmeldungen" wie: "Das Projekt wurde von mir bis auf weiteres aufs Eis gelegt. Ich werde sicherlich später daran weiterarbeiten." Was stand gleich nochmal unter Punkt 1? - Das Endergebnis dürfte klar sein.
Tipps für den Erfolg
- Lernt eure Programmiersprache kennen.
- Lernt ObjektOrientierung. Nur weil ihr Klassen benutzt, heißt das nicht, dass ihr Objekt orientiert programmiert.
- Lernt eure Entwicklungsumgebung kennen. (Debugging! Dazu gibts hier ein Tutorial für Delphi.)
- Kleine Brötchen backen (Testprojekte) und Projekte zu Ende bringen!
Nachwort
Soviel zur Euphoriebremse. OpenGL ist toll. OpenGL ist die Lösung für Eure Traumanwendung. Aber die wird auch mit OpenGL nicht von heute auf morgen programmiert. Deshalb heißt es, Tutorials lesen, kleine Testanwendungen schreiben um Effekte zu testen und Projekte bearbeiten. Dann wird es auch etwas mit den Traumprojekten. Zudem kann man anfangs auch versuchen, in sehr kleinen Anwendungen einen Effekt auszuprobieren und diesen in Form eines kleinen Beispielprogramms oder eines Tutorials zu veröffenlichen. Somit lernt man nicht nur selbst etwas dazu...
Nach diesen unglaublich Weise klingenden Worten, die ich mit einem hoch ernsten Gesicht geschrieben habe, könnt ihr euch jetzt hochmotiviert an die restlichen Tutorials machen.
Bis bald im Forum
Euer
Flash (Kevin Fleischer)
PS: Feedback wird nicht nur gewünscht, sondern ausdrücklich gefordert. Deshalb: Ab mit deinen Meinungen und Ratschlägen ins Feedback-Forum!
|
||
Vorhergehendes Tutorial: - |
Nächstes Tutorial: Tutorial Lektion 1 |
|
Schreibt was ihr zu diesem Tutorial denkt ins Feedbackforum von DelphiGL.com. Lob, Verbesserungsvorschläge, Hinweise und Tutorialwünsche sind stets willkommen. |