- Vorwort
Hallo liebe wissbegierigen Programmierer/innen,
vor euch seht ihr nun das von mir geschriebene kleine Tutorial, was an schon etwas mit FB/QB erfahrene Programmierer gerichtet ist, welche noch keinen blassen Schimmer von OpenGL haben. Mir erging es dabei ähnlich und ich musste mir erst selber durch zahlreiche Tutorials anderer Programmiersprachen die ganzen Techniken von OpenGL aneignen. Ich möchte euch nun mit meiner Hilfe einen besseren Einstieg für diese tolle API bieten. Ich werde versuchen, euch die langweilige Theorie zu ersparen, um damit direkt auf die wichtigen Dinge zu kommen, die für die Praxis relevant sind.Stormy aka Paul Grunewald -
OpenGL und FreeBASIC
-
Allgemeines
Es folgt die obligatorische Erläuterung von OpenGL:
OpenGL (Open Graphics Library) ist eine Spezifikation für ein plattform- und programmiersprachenunabhängiges API (Application Programming Interface) zur Entwicklung von 3D-Computergrafik. Der OpenGL-Standard beschreibt etwa 250 Befehle, die die Darstellung komplexer 3D-Szenen in Echtzeit erlauben.
Wikipedia - Vorteile
Hier einige Dinge, die für die Verwendung von OpenGL (in Verbindung mit FreeBASIC) sprechen:
- Ausnutzung der Kapazität der Grafikarte
- CPU-Entlastung und damit bessere Performance
- zahlreiche Tutorials und Dokumentation im Internet
- einfache API (OpenGL) und einfacher Syntax (FreeBASIC)
- zahlreiche neue und hardwarebeschleunigte Zeichenfunktionen
- einfache Bildschirminitialisierung dank FreeBASIC
- Plattformunabhängigkeit
- Nachteile
Wo Licht scheint, musst natürlich auch Schatten fallen. Auch Rosen haben Dornen:
- Starke Bindung an die Grafikkarte
- Umdenken für den FB-Programmierer (Keine Sorge! Alles ist machbar!)
- Anpassungen für die Grafiken (dazu im Abschnitt: Texturen mehr)
-
Allgemeines
-
Erste Gehversuche mit OpenGL
-
Einbinden der Grafiklib für Windows und Linux
Da dies ein sehr praxisorientiertes Tutorial ist, möchte ich euch gleich verständlich machen, wie man OpenGL für sein FreeBASIC-Anwendung verfügbar macht.Lösung für Windows und Linux:
#include once "GL/gl.bi" #include once "GL/glu.bi"
Und schon haben wir die Pforte zur Hardwarebeschleunigung betreten ! ;-) gl.bi beinhaltet die Funktions-Deklarationen ( bzw. die Prototypen der OpenGL-Funktionen) und glu.bi ist eine Utility-Lib, die auch einige brauchbare Funktionen bietet, welche einem die Arbeit erleichtern. -
Konfiguration der OpenGL-Einstellungen für den 2D-Betrieb
Nun folgt das wichtigste aus diesem Kapitel überhaupt: Die Bildschirm-Initialisierung. In anderen Sprachen wie C/C++ ist dies eine wirkliche Qual, wenn man nicht gerade SDL verwendet. Aber mit FreeBASIC kann man dies einfach und effektiv mit dem Befehl screenres lösen:
screenres width,height,depth,,&h2 OR fullscreen
Zur Erläuterung-
width, height geben Ausschlag über die Größe des Bilschirmes und werden in Pixel angegeben. Man kann für sie
beispielsweise 800 und 600 angeben, um einen Bildschirm mit den Maßen 800x600 zu erstellen.
-
depth ist die Bildtiefe in Bit. Empfohlen ist hierbei 32 anzugeben, da bei manchen Grafikkarten 16bit einfach
langsamer ist.
-
Wenn bei fullscreen &h1 bzw. 1 steht, wird der Vollbildmodus gestartet. Dies bringt in der Regel höhere FPS-
Zahlen als im Fenstermodus.
Wir dürfen nicht vergessen, dass OpenGL nach wie vor eine 3D-API bleibt und wir dementsprechende Änderungen vornehmen müssen:glMatrixMode(GL_PROJECTION) glLoadIdentity
Zur ErläuterungIn OpenGL gibt es verschiedene Matrizen, die zur Darstellung von Objekten dienen. In unserem Falle (2D) nutzen wir die sogenannte Projektionsmatrix dazu. Würden wir mit 3D-Objekten arbeiten, würde diese Matrix zur korrekten Verzerrung der Objekte führen, wenn man angenommen von einem schrägen Blinkwinkel auf das Objekt sieht. Das interessiert uns allerdings erstmal weniger, da wir uns momentan nur mit 2D beschäftigen. :-) glLoadIdentity dient im übrigen dazu, dass die ursprüngliche Matrix wiederherzustellen. Das klingt sicherlich ungeheuer kompliziert, aber es vereinfacht sich später vieles.
Anders als bei QuickBASIC oder FreeBASIC ist die Koordinatenachse nicht links oben, sondern links unten. Diese Skizze für den Schirm mit den Maßen 800x600 soll dies veranschaulichen:
Mit folgenden Befehlen sagen wir, dass sich OpenGL wie in FreeBASIC verhalten soll:glViewport(0,0,width,height) glOrtho(0,width,height,0,-128,128)
Zur ErläuterungDamit legen wir die Dimension der Koordinatenachse fest. Die letzten beiden Parameter bei glOrtho schränken die z-Achse ein. Wenn eine Z-Koordinate nicht innerhalb dieses Bereiches liegt, wird das dazugehörige Objekt nicht gezeichnet.
Nun folgt ein kleiner Trick, um die Performance unserer OpenGL-Anwendungeen zu verbessern. Wie wir wissen bewegen wir uns ja eigentlich in unseren 3D-Welten. In dieser werden alle Objekte gerendert, welche an die Grafikkarte geschickt werden. Bei diesen Objekten gibt es allerdings immer eine Rückseite, die der Benutzer nicht sehen kann. Mit folgenden Befehlen können wir der API sagen, dass in Zukunft die Rückseite nicht gerendert werden soll:glMatrixMode(GL_MODELVIEW) glEnable(GL_CULL_FACE) glCullFace(GL_BACK)
Dies bringt uns zwar nur eine kleine prozentuale Steigerung für die Performance, aber immerhin!
Jetzt stellen wir noch ein, dass wir standardmäßig 2D-Texturen nutzen wollen:glEnable GL_TEXTURE_2D glLoadIdentity
Von besonderer Wichtigkeit sind die folgenden Befehle:1. Der Tiefentest:glEnable(GL_DEPTH_TEST) glDepthFunc(GL_LESS)
Mit diesen Befehlen legen wir fest, dass Objekte die höher liegen auch gezeichnet werden und Objekte die Tiefer liegen einfach verdeckt werden. Das ist bei OpenGL nämlich ungemein praktisch, da wir uns der z-Koordinate bedienen können. So können wir beispielsweise festlegen, dass ein Sternenhimmel unter den Asteroiden und Raumschiffen gezeichnet wird. Der Hintergrund hätte hier die z-Koordinate 1 und die ganzen Objekte (Asteroiden, Raumschiffe) die z-Koordinate 2. Man kann dies natürlich beliebig variieren, wenn man zum Beispiel ganz oben noch ein Menüleiste anzeigen will, was den aktuellen Punktestand verrät.2. Der Alphatest:glEnable(GL_ALPHA_TEST) glAlphaFunc(GL_GREATER, 0.1)
Da wir später Texturen mit einem Alphachannel nutzen werden, sollten wir unbedingt diese Unterstützung aktivieren.Zusammenfassung:In diesem Abschnitt haben wir gelernt, wie man OpenGL in seinem Projekt einbindet und für 2D-relevante Anwendungen konfiguriert. Hier eine Kurzfassung, mit der ihr euer eigenes Projekt (über copy&paste) beginnen könnt:Initialisierung:' Einbindung von OpenGL #include once "GL/gl.bi" #include once "GL/glu.bi" ' Festlegung der Konstanten, die für den Bildschirm wichtig sind const scrnX = 640 const scrnY = 480 const depth = 32 const fullscreen = &h0 ' Vollbildmodus ( &h0 = aus, &h1 = an ) screenres scrnX,scrnY,depth,,&h2 OR fullscreen ' Konfiguration von OpenGL glMatrixMode(GL_PROJECTION) ' Matrix definieren glLoadIdentity glViewport(0,0,scrnX,scrnY) ' Achse festlegen glOrtho(0,scrnX,scrnY,0,-128,128) glMatrixMode(GL_MODELVIEW) ' Deaktivierung des Rendern der Rückseiten glEnable(GL_CULL_FACE) glCullFace(GL_BACK) glEnable GL_TEXTURE_2D ' Texturen aktivieren glLoadIdentity glEnable(GL_DEPTH_TEST) ' Tiefentest glDepthFunc(GL_LESS) glEnable(GL_ALPHA_TEST) ' Alphatest glAlphaFunc(GL_GREATER, 0.1)
Hauptschleife:do glClear GL_COLOR_BUFFER_BIT OR GL_DEPTH_BUFFER_BIT ' ' !! Hier kommen die Zeichenbefehle rein !! ' glFlush ' Verarbeitung der Befehle flip screensync loop until multikey(&h01) ' Verlasse die Schleife sobald Escape gedrückt wird
Zur Erklärung- glClear wird dazu genutzt den Colorbuffer (das, was wir auf dem Screen an farbigen Pixeln sehen) und den Tiefenpuffer zu löschen (sozusagen das "CLS" für OpenGL).
- Mit glflush werden sämtliche OpenGL-Anweisungen ausgeführt.
- flip kopiert den Inhalt der nicht sichtbaren zweiten Bilschirmseite auf die erste, die der Benutzer sehen kann. Es dient dazu, die von der Grafikkarte verarbeiteten Objekte anzuzeigen.
- screensync ist ein weiterer hauseigener FB-Befehl wie flip und bewirkt, dass solange gewartet wird, bis eine Bildschirmsynchronisation hergestellt ist. Kurz: Die FPS-Zahlen bleiben damit stabil und sind nur noch von der Bildwiederholungsrate abhängig.
-
width, height geben Ausschlag über die Größe des Bilschirmes und werden in Pixel angegeben. Man kann für sie
beispielsweise 800 und 600 angeben, um einen Bildschirm mit den Maßen 800x600 zu erstellen.
-
Die Formen und Farben in OpenGL
Objekte:OpenGL bietet eine große Bandbreite an Objekten die man zeichnen kann. Da wären beispielsweise:
- Punkte
- Linien
- Diverse Dreiecke
- Vierecke (für uns am wichtigsten)
- und Polygone (Vielecke)
glBegin [Objekt] [Koordinate 1] ... bis ... [Koordinate n] glEnd
In [Objekt] gebe ich das jeweilige Objekt an, was ich zeichnen kann. Dies geschieht über gewisse Konstanten, die in den OpenGL-Headerdateien enthalten sind. Ich habe hier eine kleine Liste vorbereitet, welche alle wichtigen Objekte zusammenfasst:Objekt Konstante (in FB) Mindestens benötigte Koordinaten Punkte GL_POINTS 1 Linien GL_LINES 2 Dreiecke GL_TRIANGLES 3 Vierecke GL_QUADS 4 Vieleck GL_POLYGON 3 glVertex3f x, y, z
Mit glVertex bestimme ich einen festen Punkt. Die Endung 3f gibt übrigens Auskunft, wieviele Parameter in welchen Datentyp erwartet werden. In diesem Falle sind es 3 Parameter (x, y, z) mit dem Datentyp float, was dem Typ single und double entspricht. Anderes Beispiel ist glVertex2i: Man gibt nur die x- und y- Koordinate mit zwei Integer-Werten an.Und hier ein fertiges Beispiel für ein Viereck:glBegin GL_QUADS glVertex2i 0, 50 '' LINKS UNTEN (1. Koordinate) glVertex2i 50, 50 '' RECHTS UNTEN (2. Koordinate) glVertex2i 50, 0 '' RECHTS OBEN (3. Koordinate) glVertex2i 0, 0 '' LINKS OBEN (4. Koordinate) glEnd
In diesem speziellen Falle handelt es sich um ein Quadrat mit der Seitenlänge von 50 Pixeln. Diesen Code kannst du übrigens in unsere Hauptschleife unter den Kommentar Zeichenbefehle einfügen. Hier dazu zwei Illustrationen:Unser Quadrat ist nun in der linken oberen Ecke (0,0). Es ist alles noch ziemlich unspektakulär! ;-) Die Grafik verdeutlicht in welcher Reihenfolge die Koordinaten folgen. Verschieben von Objekten:Das ganze wirkt etwas starr. Man müsste bei jeder Verschiebung in x und y immer Koordinaten x+50,y+50 berechnen, wenn dieses Quadrat in seiner Größe doch gleich bleibt. Abhilfe dafür schafft dieser tolle Befehl:glTranslatef x,y,z
Damit wird das zu zeichnende Objekt an die Stelle x,y,z verschoben. Füge einfach glTranslatef 100,100,0 vor glBegin GL_QUAD und voilá: Unser Quadrat wurde an die Stelle (100, 100, 0) verschoben! Mittels glTranslatef kann man auch einfach Objekte überlagern, wie zum Beispiel im folgenden Code.Überlagerung von Objekten:' Quadrat A glTranslatef 100,100,0 ' z = 0 glBegin GL_QUADS glVertex2i 0, 50 '' LINKS UNTEN (1. Koordinate) glVertex2i 50, 50 '' RECHTS UNTEN (2. Koordinate) glVertex2i 50, 0 '' RECHTS OBEN (3. Koordinate) glVertex2i 0, 0 '' LINKS OBEN (4. Koordinate) glEnd glLoadIdentity ' Quadrat B glTranslatef 125,125,1 ' z = 1 glBegin GL_QUADS glVertex2i 0, 50 '' LINKS UNTEN (1. Koordinate) glVertex2i 50, 50 '' RECHTS UNTEN (2. Koordinate) glVertex2i 50, 0 '' RECHTS OBEN (3. Koordinate) glVertex2i 0, 0 '' LINKS OBEN (4. Koordinate) glEnd glLoadIdentity
Zugegeben: Man erkennt nicht, welches Quadrat welches überdeckt. Um dies zu erkennen, geben wir den Quadraten einfach unterschiedliche Farben. Schon besser. Quadrat B ist blau und oberhalb. Doch wie wurde das gemacht? Das werdet ihr im nächsten Abschnitt erfahren.
Noch ein Hinweis: Ohne dem Befehl glLoadIdentity nach dem ersten Quadrat (A) würde sich die nächste Positionsanweisung mit glTranslatef 125,125,1 relativ verhalten. Sprich das neue Quadrat wird abhängig von der letzten Positions- Änderung verschoben. Das hieße, dass das zweite Quadrat (B) genau 125 in x- und 125 Pixel in y-Richtung vom ersten Quadrat verschoben wurde.Ein Beispiel dazu, was das ganze anschaulich macht:glTranslatef 100,100,0 ' verschiebe von (0, 0) auf (100, 100) ''glLoadIdentity ' Keine(!) Wiederherstellung auf (0, 0) glTranslatef 25,25,0 ' verschiebe von (100, 100) auf (125, 125)
Farben:Mit diesem Befehl kann man die Farbe bestimmen mit der gezeichnet werden soll:glColor red, green, blue[, alpha]
Natürlich gibt es diesen Befehl in verschiedenen Varianten:- glColor3f: Die Rot-, Blau-, Grün-Werte liegen zwischen 1 und 0 (Fließkommazahlen)
- glColor4f: Die Rot-, Blau-, Grün- und Alpha-Werte liegen zwischen 1 und 0 (Fließkommazahlen)
- glColor3ub: Die Rot-, Blau-, Grün-Werte liegen zwischen 255 und 0 (UBYTE)
- glColor4ub: Die Rot-, Blau-, Grün- und Alpha-Werte liegen zwischen 255 und 0 (UBYTE)
Um nun die Quadrate farbig zu machen, schreibt einfach glColor mit den jeweiligen Parametern vor glBegin:glTranslatef 100,100,0 ' z = 0 glColor3ub 255,0,0 ' rotes Quadrat (A) glBegin GL_QUADS ... glTranslatef 125,125,1 ' z = 1 glColor3ub 0,0,255 ' blaues Quadrat (B) glBegin GL_QUADS ...
Färben der Eckpunkte:Es ist auch möglich einzelne Eckpunkte mit einer bestimmten Farbe zu färben:glTranslatef 100,100,0 glBegin GL_TRIANGLES glColor3ub 255,0,0 '' rot glVertex2i 0, 50 '' LINKS UNTEN (1. Koordinate) glColor3ub 0,255,0 '' grün glVertex2i 100, 50 '' RECHTS UNTEN (2. Koordinate) glColor3ub 0,0,255 '' blau glVertex2i 50, 0 '' MITTE OBEN (3. Koordinate) glEnd
Nur noch ein Blick auf den dazugehörigen Screenshot und dies müsste verständlich genug sein:Man kann gut erkennen, von welchen Punkten die Farben ausgehen.
-
Einbinden der Grafiklib für Windows und Linux
-
Texturen
-
Allgemeine Informationen
Um es von der praktischen Seite zu sehen, sind Texturen nichts anderes als Bilder, die man auf ein Objekt "legt". Texturen werden wir ähnlich behandeln wie die Grafiken die man in FreeBASIC mittels PUT setzen kann. Allerdings gibt es einige Kompromisse, die wir bei der Behandlung mit Texturen eingehen müssen. So dürfen Texturen nur quadratisch sein und eine Seitenlänge einer 2er-Potenz besitzen. Beispiele wären also 32x32, 64x64, 128x128, 256x256, etc.Beispiele:
Grasstextur mit den Maßen 128x128 Kieselsteintextur mit den Maßen 256x256 Pixel -
Unsere Texturenstruktur
Um eine Textur eindeutig zu beschreiben, benötigen wir eine neue Struktur, die für uns alle wichtigen Informationen speichert.
TYPE GFXType Handle as gluint Width as integer Height as integer TextureFilter as integer imageData as ubyte ptr bpp as uinteger textype as uinteger END TYPE
Zur Erklärung- Handle ist eine Nummer mit der die Textur identifiziert wird.
- Width, Height sind die Maßangaben
- TextureFilter darüber geben wir Filter-Optionen. Benötigen wir aber momentan noch nicht.
- Mit imageData beschreiben wir einen Speicherblock, in dem unsere Bilddaten enthalten sind. Dies verhält sich ähnlich wie den Buffern, die man mit PUT zeichnen lassen kann.
- bpp heißt Bit-Per-Pixel und beschreibt wie hoch die Farbtiefe ist.
- textype gibt Auskunft darüber, ob der Alphachannel genutzt wird oder nicht. Mögliche Wert sind hier GL_RGB und GL_RGBA
DIM Textur AS GFXType
-
Laden von Grafiken
Für das Laden der Grafiken habe ich schon eine kleine Sammlung an Texturladeroutinen vorbereitet (2d_opengl.zip). Damit könnt ihr Grafiken im Format BMP, TGA und PNG laden. BMP solltet ihr nur verwenden, wenn ihr den Alphachannel nicht nutzen wollt. TGA und PNG dagegen bieten einen Alphachannel und sind sogar komprimiert, was ungemein praktisch ist, da eure Projekte später nicht soviel Festplattenspeicher wegnehmen und sich besser im Internet verteilen lassen.Um die fertige Textureladeroutine zu integrieren, schreibt folgendes in euren Quelltext:
#include "inc/2d_opengl.bi"
Nun steht euch die Funktion load_texture zur Verfügung:function load_texture(filename AS STRING, byref texture AS GFXType, flags AS INTEGER = 0, killdata AS INTEGER = 0) AS INTEGER
- Bei filename übermitteln wir den Pfad zu unserer Textur.
- texture ist unsere Variable in welcher sämtliche Texturedaten geladen werden.
- Mit flags können wir bestimmte Optionen und Filter für die Textur aktivieren.
Mögliche Werte für flags sind:
- TEX_NOFILTER - Diese Option beeinflusst den sogenannten Magfilter (zu deutsch:
"Vergrößerungsfilter"). Wir sprechen von Vergrößerung, wenn das Objekt größer ist als seine eigene Textur - erst
dann wird dieser Filter aktiv. Wenn man nun bei
flags TEX_NOFILTER übergibt, wird GL_NEAREST aktiviert, was für das schnelle
Anzeigen der Texturen ausreichend ist. Wenn nicht übermittelt, dann wird GL_LINEAR aktiviert, was dazu
führt, dass die Grafik "weicher" und somit schöner wirkt.
Unterschiede zwischen GL_NEAREST und GL_LINEAR:
- TEX_MIPMAP - Mipmaps sind vorgefertigte, verkleinerte Grafiken, die zum Einsatz kommen,
wenn das Objekt verkleinert wird. Mit mipmaps hat man zudem einen Geschwindigkeitsvorteil und die Grafiken sehen
weicher aus. Also gut, wenn man oft bestimmte Texturen verkleinert darstellt.
Beispielbild ohne und mit Mipmaps:
- TEX_NOFILTER - Diese Option beeinflusst den sogenannten Magfilter (zu deutsch:
"Vergrößerungsfilter"). Wir sprechen von Vergrößerung, wenn das Objekt größer ist als seine eigene Textur - erst
dann wird dieser Filter aktiv. Wenn man nun bei
flags TEX_NOFILTER übergibt, wird GL_NEAREST aktiviert, was für das schnelle
Anzeigen der Texturen ausreichend ist. Wenn nicht übermittelt, dann wird GL_LINEAR aktiviert, was dazu
führt, dass die Grafik "weicher" und somit schöner wirkt.
- Mittels Killdata kann man den temporär angelegten Buffer, welcher die Pixeldaten enthält
löschen. Man sollte ihn nur erhalten lassen, wenn man diesen Buffer ändern möchte. Wenn er gelöscht wird, hat man mehr
Speicher zur Verfügung. Die Textur bleibt in jedem Falle für die Grafikkarte erhalten.
(0 = Buffer löschen, 1 = Buffer erhalten)
#include "inc/2d_opengl.bi" dim Textur as GFXType call load_texture("grassflaeche.bmp", Textur)
Und voilá: Unsere Texture wurde geladen! -
Darstellung von Grafiken
Nun kommen wir zum interessanten Teil. Wir wollen nun erstmals unsere Grafiken darstellen. Wie wir uns sicher erinnern, haben wir in OpenGL Objekte zur Verfügung. Über jene Objekte werden nun unsere Texturen legen.Unser ursprüngliches Viereck
glTranslatef 100,100,0 ' z = 0 glBegin GL_QUADS glVertex2i 0, 50 '' LINKS UNTEN (1. Koordinate) glVertex2i 50, 50 '' RECHTS UNTEN (2. Koordinate) glVertex2i 50, 0 '' RECHTS OBEN (3. Koordinate) glVertex2i 0, 0 '' LINKS OBEN (4. Koordinate) glEnd
Um auf dieses Viereck nun unsere Texture zu legen, müssen wir zum einen die Texturkoordinaten festlegen, sprich die Stellen angeben, die auch gezeichnet werden sollen. Dies geht über den Befehl:glTexCoord2f x, y
Allerdings zählt hier wieder ein anderes Koordinatensystem. Es zählt von links unten (0, 0) und geht bis (1, 1). Alles dazwischen sind demzufolge gebrochene Zahlen (0.1, 0.2 ... 0.9).Dieses Bild verdeutlicht, wie die Koordinaten angeordnet sind:
Damit können wir bestimmen, welchen Ausschnitt wir von der Textur haben möchten. Wenn man angenommen eine Texture mit mehreren einzelnen Grafiken haben, wollen wir schließlich nur ausgewählt Ausschnitte wählen, zB.: bei einer Grafikdatei die 10 Frames eines sich drehenden Asteroiden gespeichert enthalten hat.
In diesem Bild nehmen wir einen Ausschnitt über die Koordinaten (0,0), (1,0), (0,1) und (1,1)! Dass man Ausschnitte nehmen kann, ist wirklich vorteilhaft wie man bei diesem Beispiel sehen kann. Wir holen uns aus dieser Grafikdatei, in welcher ein Schriftsatz gespeichert ist, einfach den Buchstaben "A". glBindTexture GL_TEXTURE_2D, Handle
Handle ist die Nummer die unsere Textur eindeutig identifiziert. Wir werden hier das Handle angeben, welches in unserer Texturenstruktur fest verankert ist. Nun müssen wir die Texturkoordinaten nur noch an die korrekten Koordinaten unseres Objektes binden.Zusammengefasst sieht dies nun so aus:#include "inc/2d_opengl.bi" ' Textureladeroutinen einbinden dim Textur as GFXType ' Textur definieren call load_texture("grassflaeche.bmp", Textur) ' Grasstextur laden ' Eigentlicher Zeichenteil: glBindTexture GL_TEXTURE_2D, Textur.Handle ' Texture binden glTranslatef 100,100,0 ' z = 0 glBegin GL_QUADS glTexCoord2f 0, 0 glVertex2i 0, 50 '' LINKS UNTEN (1. Koordinate) glTexCoord2f 1, 0 glVertex2i 50, 50 '' RECHTS UNTEN (2. Koordinate) glTexCoord2f 1, 1 glVertex2i 50, 0 '' RECHTS OBEN (3. Koordinate) glTexCoord2f 0, 1 glVertex2i 0, 0 '' LINKS OBEN (4. Koordinate) glEnd glLoadIdentity glBindTexture GL_TEXTURE_2D, 0 ' Texture freigeben
Und unser optisches Endprodukt:glBegin GL_QUADS glVertex2i 0, textur.height '' LINKS UNTEN (1. Koordinate) glVertex2i textur.width, textur.height '' RECHTS UNTEN (2. Koordinate) glVertex2i textur.width, 0 '' RECHTS OBEN (3. Koordinate) glVertex2i 0, 0 '' LINKS OBEN (4. Koordinate) glEnd
textur.width und textur.height wurden automatisch während der Laderoutine gesetzt. Zwischen den Objekt-Koordinaten müssen einfach die Texturkoordinaten folgen (siehe oben) und wir haben stets die korrekte Größe.
Dadurch, dass wir unserer Objektkoordinaten beliebig setzen können, ist es uns nun auch möglich eine Textur beliebig zu skalieren ohne dabei Performanceverluste hinzunehmen zu müssen! Noch ein weiterer Hinweis: Wenn man bei glTextCoord2f Werte über 1 angibt, wird die Textur innerhalb des Objektes wiederholt. Angenommen man beim oberen 2 statt 1 an, werden wir viermal die Textur sehen.Das würde dann so aussehen:
-
Allgemeine Informationen
-
Blending
-
Was ist das ?
Unter dem Begriff "Blending" in der OpenGL-Welt verstehen wir das "Zeichen-Verhalten" der Objekte - also wie soll das Objekt gezeichnet werden. OpenGL prüft dabei, auf was für einen Hintergrund gezeichnet wird und wie die neuen Pixeldaten auf den Frame- und Tiefenbuffer gezeichnet werden sollen. Dabei bietet OpenGL sehr viele Blendingmöglichkeiten an! Und wenn ich sage viele, dann meine ich auch wirklich VIELE! Wichtigstes Feature, was uns begegnen wird, ist das "Alphablending". Damit kann man beispielsweise teil-transparente Vierecke über den Bildschirm fliegen lassen und Texturen mit Alphachannel voll ausnutzen.
Ganz wichtig: Zwar ist es möglich mit Blending transparente Objekte darzustellen, bloß hat dies den Nachteil, dass wir unsere Objekte selber nach der Tiefe sortieren müssen. Sprich wir müssen vorher wissen, welche Objekte welche Objekte überdecken und müssen dementsprechend erst diese in bestimmter Reihenfolge zeichnen lassen.Um den Blendingmodus zu aktivieren, bedarf es den folgenden Befehls:glEnable(GL_BLEND)
Und um einen bestimmte Blendfunktion auszuwählen, müssen wir glBlendFunc nutzen:glBlendFunc( source , destination )
Für source und destination kann man verschiedene Konstanten, welche aus den OpenGL-Headern stammen, verwenden, die für die verschiedenen Blending-Modi führen.Konstante Berücksichtigte Faktoren Berechneter Blend-Faktor GL_ZERO source or destination (0, 0, 0, 0) GL_ONE source or destination (1, 1, 1, 1) GL_DST_COLOR source (Rd, Gd, Bd, Ad) GL_SRC_COLOR destination (Rs, Gs, Bs, As) GL_ONE_MINUS_DST_COLOR source (1, 1, 1, 1)-(Rd, Gd, Bd, Ad) GL_ONE_MINUS_SRC_COLOR destination (1, 1, 1, 1)-(Rs, Gs, Bs, As) GL_SRC_ALPHA source or destination (As, As, As, As) GL_ONE_MINUS_SRC_ALPHA source or destination (1, 1, 1, 1)-(As, As, As, As) GL_DST_ALPHA source or destination (Ad, Ad, Ad, Ad) GL_ONE_MINUS_DST_ALPHA source or destination (1, 1, 1, 1)-(Ad, Ad, Ad, Ad) GL_SRC_ALPHA_SATURATE source (f, f, f, 1); f=min(As, 1-Ad)
Ich zeige euch am Besten gleich den - zumindest für mich - wichtigsten Blendmodus:glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
Damit könnt ihr Texturen zeichnen und dabei deren Alpha-Channel ausnutzen.Damit wäre es also möglich, folgendes zu unternehmen:Wir nehmen Textur #1 als Hintergrund... ...und legen Textur #2 auf diese. Dank unseres Blendmodus können wir nun diese Texturen ohne Probleme überlagern. Man kann sogar von der Textur #2 den Alphachannel nachträglich wie hier mit glColor4f 255, 255, 255, 128 ändern -
Praktische Anwendung und Beispiele
Ich habe ein kleines Programm entwickelt, was zur experimentellen Spielerei dienen soll. Damit kann man durch einfaches Ausprobieren (indem man die Werte ändert), verschiedene Ergebnisse herbeirufen. Dabei werden viele eigene Programmzeilen erspart und man kann zum Beispiel im Voraus prüfen, welchen Blendfunktionen man für sein Projekt nutzen sollte.Screenshot:
Download: blending.zip
Steuerung:- Pfeiltasten - Springen zu den verschiedenen Optionen
- 1, 2, 3 - zwischen den Texturen springen
- Enter - Werte ändern
- ESC - Beenden
- B - Blendmodus an/aus
- D - Tiefentest an/aus
-
Was ist das ?
-
Nützliche Techniken
Hinweis:Nun folgt eine kleine Sammlung an nützlichen Techniken. Unter anderem sind altbewährte Dinge vertreten, die man schon vorher von dem einen oder anderen 2D-Spiel kannte. Ohne OpenGL kann man die nun folgenden natürlich auch umsetzen. Ich habe dies beispielsweise bei meinem RPG so gemacht. Dies wurde allerdings so CPU-lastig, dass ich umdenken musste. So kam ich nach etwas Einarbeitungszeit in OpenGL schnell zur Erkenntnis, dass diese Effekte weitaus einfacherer und performanter umgesetzt werden können, als im reinen Softwaremodus.
Während meiner Arbeit, ist eine kleine Funktionssammlung hervorgegangen, die euch zur freien Verfügung steht: 2d_opengl.zip. Diese enthält Laderoutinen, eine Funktion zum Initialisieren von OpenGL, aber auch einige fertige Funktionen, die aber in diesem Kapitel nochmal detailiert erklärt werden.
Alle Beispiele können auch hier runtergeladen werden, so dass ihr diese parallel zum Tutorial ausführen könnt: examples.zip-
Skalieren und Rotieren
Erstmal um diese zwei Begriffe zu erklären: Skalieren ist das Vergrößern und Verkleinern von Objekten. Rotieren ist das Drehen von Objekten um einen bestimmten Grad.Skalieren:Um ein Objekt skalieren zu können, bedarf es einen einzigen Befehles:
glscalef x, y, z
Um also ein 2D-Objekt um das Doppelte vergrößert darzustellen, schreibt man also:glscalef 2, 2, 1
Und um die Hälfte verkleinert:glscalef .5, .5, 1
Nachdem das Objekt gezeichnet worden ist, sollte ein glLoadIdentity folgen, um den Ursprungsstatus wiederherzuerstellen.Rotieren:Unter OpenGL ist es möglich die gesamte Matrix zu rotieren. Der Befehl hierfür lautet:glrotatef degree, x, y, z
Mit degree geben wir an, um wieviel wir die Matrix drehen wollen. x, y und z sind der Vektor um den sich die Punkte drehen. Da wir uns nur auf 2D-Welten bewegen, sollten wir hier nur z = 1 und für x und y = 0 nutzen.Beispiel: (example_scal_rot.bas)Ich habe beide Techniken der Rotation und Skalierung in einem Beispiel zusammengefasst. Hier erstmal der Sourcecode. Weiter unten folgt dann ein Screenshot und eine Erklärung zum Source:#include "inc/2d_opengl.bi" ' Festlegung der Konstanten, die für den Bildschirm wichtig sind const scrnX = 640 const scrnY = 480 const depth = 32 const fullscreen = &h0 ' Vollbildmodus ( &h0 = aus, &h1 = an ) DIM AS INTEGER midX, midY DIM AS INTEGER w = 64, h = 64, change DIM AS SINGLE rot = 0, scal = 1 DIM AS GFXType texture ' Finde Mittelpunkt midX = scrnX / 2 midY = scrnY / 2 ' Initialisiere den Schirm Init_ogl2D(scrnX, scrnY, depth, fullscreen) glEnable GL_TEXTURE_2D ' Texturen erlauben. load_texture ("resources/examples/eagle.tga", texture) ' Lade eine Textur glBindTexture GL_TEXTURE_2D, texture.handle ' und aktiviere sie. WindowTitle "[ Rotation & Scale ] scale factor: "_ + STR$(scal) + " rotation degree: " + STR$(rot) do glCls glColor3f 1,1,1 glTranslatef midX,midY,0 glscalef scal, scal, 1 glrotatef rot, 0, 0, 1 glBegin GL_QUADS glTexCoord2f 0.0, 0.0 ' links unten glVertex2i -w/2, h/2 glTexCoord2f 1.0, 0.0 ' rechts unten glVertex2i w/2, h/2 glTexCoord2f 1.0, 1.0 ' rechts oben glVertex2i w/2, -h/2 glTexCoord2f 0.0, 1.0 ' links oben glVertex2i -w/2, -h/2 glEnd glLoadIdentity glFlush flip screensync change = 0 IF MULTIKEY(SC_UP) THEN scal+=.05: change = 1 IF MULTIKEY(SC_DOWN) THEN scal-=.05: change = 1: IF scal <= .1 THEN scal=.05 IF MULTIKEY(SC_RIGHT) THEN rot+=5: change = 1 IF MULTIKEY(SC_LEFT) THEN rot-=5: change = 1 IF change = 1 THEN WindowTitle "[ Rotation & Scale ] scale factor: "_ + STR$(scal) + " rotation degree: " + STR$(rot) END IF loop until multikey(SC_ESCAPE) ' Verlasse die Schleife sobald Escape gedrückt wird
Screenshot:Steuerung:- Pfeiltaste Hoch - Objekt vergrößern
- Pfeiltaste Runter - Objekt verkleinern
- Pfeiltaste Rechts - Drehungsgrad erhöhen
- Pfeiltaste Links - Drehungsgrad senken
- Escape - Beenden
Erklärung zum Source:In diesem Beispielsource können wir ein Quad, welches vorher mit einer Textur belegt wurde, nach belieben vergrößern, verkleinern und drehen. Neu in diesem Source ist der Befehl Init_ogl2D. Dieser ist einfach eine Abkürzung für die lange Initalisierung für den 2D-Bildschirm und braucht nur die nötigsten Parameter, wie man sehen kann. Auch wurde glCls eingefügt, um den Umstieg auf OpenGL zu erleichern. ;-)
Der aber nun wirklich interessante Teil beginnt innerhalb der Schleife. Wir platzieren unser Quad in die Mitte, die wir zuvor ausgerechnet haben (MidX, MidY). Die Eckpunkte des Quads umgeben diesen Mittelpunkt. Wenn wir weiter oben nochmal anschauen, wie wir das Quad gezeichnet haben, wird uns auffallen, dass wir nur den linken oberen Punkt angegeben haben. Hier aber der Mittelpunkt! Für die Rotation nehmen wir deswegen den Mittelpunkt, da dieser gleich der Punkt ist, um den wir das Quad drehen wollen.
Extrem wichtig ist, dass wir glTranslatef vor glScalef und glRotatef verwenden! Wir wollen ja den Mittelpunkt genau mit unserer "2D-Matrix" angeben. Wenn wir allerdings vorher unsere Matrix skalieren und rotieren, wird dieser Punkt nicht tatsächlich in der Mitte dargestellt, wie wir es eigentlich wollten.
Noch ein letzter Hinweis: Gerade in diesem Beispiel werden die min- und magfilter von OpenGL angesprochen. Versucht doch bei load_texture eine der folgenden Zeilen zu verwenden:load_texture ("resources/examples/eagle.tga", texture, TEX_NOFILTER) load_texture ("resources/examples/eagle.tga", texture, TEX_MIPMAP) load_texture ("resources/examples/eagle.tga", texture, TEX_MIPMAP OR TEX_NOFILTER)
-
Tilemaps
Tilemaps sind eine gängige Form, mehrere kleinere Grafiken gleicher Größe in einer großen Grafik zu speichern. Man hat hierbei eine bestimmte Anzahl an Tiles in horizontaler und vertikaler Richtung. Ein Tile nennt man ein einzelnes kleines Bild. Diese Anzahl der Tiles in unserer ist logischerweise von der Größe der Textur und des Tiles selber abhängig. Tilemaps dienen dazu, um beispielsweise Karten mit sich immer wiederholenden Elementen (wie Grasflächen, Steinen, etc.) zu zeichnen. Tilemaps können auch dazu benutzt werden, um Animationen (auch Sprites genannt) zu realisieren.Beispiel für eine Tilemap:
Diese Tilemap stammt aus einem meiner Spiele. Diese wurde in ihrer Größe verdoppelt. Ein Tile hat die Größe von 32x32 Pixeln. Die Textur hat die Maße 256x256 Pixel. Das macht pro Reihe 8 Tiles und pro Spalte auch 8 Tiles. Insgesamt also 64 Tiles. Um die Anzahl der möglichen Tiles zu errechnen, teilt man die Länge der Textur durch die Länge eines Tiles (256/32=8). Und hier das ganze durchnummeriert: Man kann also jedes Tiles über eine eindeutige Nummer identifizieren. Das Problem ist nun, die Texturkoordinaten für das Tile über jene Tile-Nummer zu errechnen. Das Problem:Leider können wir nicht, die Koordinaten unseres Tiles in Pixel angeben. Wir müssen unsere Koordinaten immer innerhalb des Bereiches von 0 bis 1 angeben. Dies wird problematisch, wenn wir wirklich pixelgenaue Angaben machen wollen. Wir werden aber auch Herr von diesem Problem. Rechts seht ihr nochmal die Anordnung unserer Texturkoordinaten. Darüber können wir unsere Koordinaten für unsere Tiles berechnen.
Die Lösung:Wir lösen dieses Problem mal mit dem mathematischen Ansatz. Also gegeben ist....- Höhe, Breite der Textur
- Höhe, Breite des Tiles
- und die Index-Nummer des bestimmten Tiles, was wir zeichnen wollen
const tile_width = 32 ' Breite const tile_height = 32 ' Höhe
Da wir später öfters einzelne Tiles aus einer Textur nehmen wollen, ist es ratsam gleich ein SUB zu entwerfen, was unseren Code beherbergt:sub PutTile (ByVal texture as GFXType, ByVal index as integer, ByVal w as integer, ByVal h as integer, ByVal x as integer, ByVal y as integer, ByVal z as integer = 0)
- index ist eine Nummer mit der das Tile identifiziert wird.
- w, h sind die Maßangaben für das Tile
- x, y, z sind die Koordinaten, wo das Objekt gezeichnet werden soll.
dim as double cx, cy, rw, rh, horiz, vert index = index - 1 horiz = fix(texture.width/w) '' Anzahl der horizontalen Tiles vert = fix(texture.height/h) '' Anzahl der vertikalen Tiles cx = (index mod horiz)/horiz '' X-Position unseres Tiles cy = (index\vert)/vert '' Y-Position unseres Tiles rw = 1/horiz '' Breite eines Tiles (in Texturkoordinatenform) rh = 1/vert '' Höhe eines Tiles (in Texturkoordinatenform)
Das dürfte jetzt ziemlich verwirrend sein. Das ist eben Mathematik! Allerdings ist diese recht schlüssig, wenn wir einfach mal ein Beispiel durchrechnen: Wir nehmen dafür einfach mal das Tile mit der Nummer 11. Im SUB ziehen wir 1 ab, da SUB-intern das erste Tile mit 0 beginnt. Zuerst werden erstmal die Nummer der Tiles in horizontaler und vertikaler Richtung berechnet. Mit unseren Werten kommen wir auf horiz = 8 und vert = 8. Der wichtigste ist nun die Koordinaten unseres Tiles zu berechnen. Also cx = (10 mod 8)/32 ergibt 2/32 = 1/16. Für cy ergibt sich (10 \ 8) / 8 = 1/8. Mit der Position (X: 1/16, Y: 1/8) können wir nun unsere 4 Texturkoordinaten berechnen:glBindTexture GL_TEXTURE_2D, texture.handle glTranslatef x,y,z glBegin GL_QUADS glTexCoord2f cx,1-cy-rh glVertex2i 0, h '' LINKS UNTEN glTexCoord2f cx+rw,1-cy-rh glVertex2i w, h '' RECHTS UNTEN glTexCoord2f cx+rw,1-cy glVertex2i w, 0 '' RECHTS OBEN glTexCoord2f cx,1-cy glVertex2i 0, 0 '' LINKS OBEN glEnd end sub
Selbst für den Fall, dass ihr das nicht 100% verstanden habt, wie das funktioniert hat, solltet ihr nicht verzweifeln! Bei einem Auto muss man auch nicht wissen, wie alles funktioniert. Man muss wissen, wie man es fährt! Solange ihr also dieses SUB PutTile mit richtigen Parametern füttert, dürfte nichts schiefgehen ! Wer es dennoch verstanden hat, kann diese Passage getrost ignorieren! ;)Beispiel: (example_tilemap.bas)Ich habe natürlich auch ein Beispiel parat, wo das ganze in Aktion seht:#include "inc/2d_opengl.bi" ' Festlegung der Konstanten, die für den Bildschirm wichtig sind const scrnX = 640 const scrnY = 480 const depth = 32 const fullscreen = &h0 ' Vollbildmodus ( &h0 = aus, &h1 = an ) ' Tiles const tile_width = 32 ' Breite const tile_height = 32 ' Höhe DIM AS GFXType texture ' Initialisiere den Schirm Init_ogl2D(scrnX, scrnY, depth, fullscreen) glEnable GL_TEXTURE_2D ' Texturen erlauben. load_texture ("resources/examples/tilemap.tga", texture) ' Lade eine Textur WindowTitle "[ Tilemap ]" ' Karte festlegen DIM MyMap( 1 to 8, 1 to 8 ) as integer => _ { { 0, 0, 0, 0, 0, 0, 0, 0 }, _ { 0, 0, 0, 0, 0, 0, 7, 0 }, _ {41,42,42,42,42,45, 0, 0 }, _ {49,50,50,50,50,53, 0, 0 }, _ {49,54,55,63,50,53, 0, 4 }, _ {57,62,59,60,58,61, 5, 3 }, _ { 0, 0, 0, 0, 0, 0, 0, 0 }, _ { 2, 2, 0, 0, 0,12,13, 0 } } DIM AS INTEGER x, y do glCls glColor3f 1,1,1 FOR y = 1 TO 8 FOR x = 1 TO 8 IF MyMap(y, x) <> 0 THEN PutTile (texture,_ MyMap(y, x),_ tile_width,_ tile_height,_ (x*tile_width),_ (y*tile_height),_ 0) END IF NEXT x NEXT y glFlush flip screensync loop until multikey(SC_ESCAPE) ' Verlasse die Schleife sobald Escape gedrückt wird
Screenshot:Es steht natürlich jedem frei diese Technik zu erweitern (zum Beispiel durch mehrere Schichten von Tiles). -
Clipping
Unter Clipping versteht man Teilausschnitte von Grafiken darzustellen. Es wird unter anderem auch rendern genannt. Ähnlich wie bei unserer Tilemap wollen wir einen Auschnitt zeichnen. Diesmal allerdings nicht ein Tile mit fester Größe, sondern mit variabler Länge und Breite.Wo findet das Anwendung?Angenommen man will für sein Weltraum-Spiel eine Oberfläche basteln, in dem links unten die Stabilitätsanzeige des Schiffs und daneben die Anzeige für die Schildstärke steht. Und links oben nochmal eine Anzeige wieviel Raketen man hat. Man könnte jetzt natürlich jede einzelne Grafik für sich in je eine Datei packen. Bei der Grafik müssen wir auch immer achten, dass wir die Länge der Grafik gleich einer 2er-Potenz setzen. Meißt sind jedoch unsere Grafik nicht so groß, um den ganzen Platz zu füllen. Viel eleganter lässt es sich lösen, wenn wir eine große "Container-Datei" haben und nur noch unsere einzelnen Bilder einfügen müssen.Beispiel für ein kleines Weltraumspiel
- die leere Schildanzeige zwischen (0,0) und (254, 46)
- die volle Schildanzeige zwischen (0,46) und (254, 92)
- die leere Schiffsanzeige zwischen (0,92) und (254, 138)
- die volle Schiffsanzeige zwischen (0,138) und (254, 184)
- die einsatzbereite Rakete zwischen (0,207) und (24, 254)
- die nicht einsatzbereite Rakete zwischen (25,207) und (49, 254)
sub PutClip (ByVal texture as gfxtype, ByVal x as integer, ByVal y as integer, ByVal z as integer = 0, ByVal offset_x1 as integer, ByVal offset_y1 as integer, ByVal offset_x2 as integer, ByVal offset_y2 as integer)
- x, y, z sind die Koordinaten, wo das Objekt gezeichnet werden soll.
- offset_(x1,y1) und offset_(x2,y2) sind unsere beiden Koordinaten zwischen denen unser Ausschnitt liegt.
dim as integer quad_width, quad_height quad_width = abs(offset_x1 - offset_x2) quad_height = abs(offset_y1 - offset_y2)
Analog wie schon bei der Tilemap werden wir auch hier einen Ausschnitt zeichnen. Das Prinzip ist dasselbe:glBindTexture GL_TEXTURE_2D, texture.handle glTranslatef x,y,z glBegin GL_QUADS glTexCoord2f offset_x1/texture.width, 1-offset_y2/texture.height glVertex2f 0, quad_height glTexCoord2f offset_x2/texture.width, 1-offset_y2/texture.height glVertex2f quad_width, quad_height glTexCoord2f offset_x2/texture.width, 1-offset_y1/texture.height glVertex2f quad_width, 0 glTexCoord2f offset_x1/texture.width, 1-offset_y1/texture.height glVertex2f 0, 0 glEnd glLoadIdentity end sub
Beispiel (example_clipping.bas)#include "inc/2d_opengl.bi" ' Festlegung der Konstanten, die für den Bildschirm wichtig sind const scrnX = 640 const scrnY = 480 const depth = 32 const fullscreen = &h0 ' Vollbildmodus ( &h0 = aus, &h1 = an ) DIM AS GFXType texture ' Initialisiere den Schirm Init_ogl2D(scrnX, scrnY, depth, fullscreen) glEnable GL_TEXTURE_2D ' Texturen erlauben. load_texture ("resources/examples/space_hud.tga", texture) ' Lade eine Textur WindowTitle "[ Clipping ]" TYPE Player health_current AS INTEGER health_max AS INTEGER shield_current AS INTEGER shield_max AS INTEGER rockets_current AS INTEGER rockets_max AS INTEGER END TYPE DIM MyPlayer AS Player With MyPlayer .health_current = 100 .health_max = 100 .shield_current = 50 .shield_max = 250 .rockets_current = 2 .rockets_max = 5 End With TYPE CoordsTex AS INTEGER X1, Y1, X2, Y2 END TYPE DIM AS CoordsTex health_empty, health_full, shield_empty, shield_full,_ rockets_active, rockets_inactive With shield_empty .X1 = 0: .Y1 = 0: .X2 = 254: .Y2 = 46 End With With shield_full .X1 = 0: .Y1 = 46: .X2 = 254: .Y2 = 92 End With With health_empty .X1 = 0: .Y1 = 92: .X2 = 254: .Y2 = 138 End With With health_full .X1 = 0: .Y1 = 138: .X2 = 254: .Y2 = 184 End With With rockets_active .X1 = 0: .Y1 = 207 : .X2 = 24: .Y2 = 254 End With With rockets_inactive .X1 = 25: .Y1 = 207: .X2 = 49: .Y2 = 254 End With DIM r AS INTEGER do glCls glColor3f 1,1,1 With health_empty PutClip (texture, 20, 400, 0, .X1, .Y1, .X2, .Y2) End With With health_full PutClip (texture, 20, 400, 1, .X1, .Y1, .X1 + _ (255*(MyPlayer.health_current/MyPlayer.health_max)), .Y2) End With With shield_empty PutClip (texture, 280, 400, 0, .X1, .Y1, .X2, .Y2) End With With shield_full PutClip (texture, 280, 400, 1, .X1, .Y1, .X1 + _ (255*(MyPlayer.shield_current/MyPlayer.shield_max)), .Y2) End With FOR r = 1 TO MyPlayer.rockets_max IF MyPlayer.rockets_current >= r THEN With rockets_active PutClip (texture, 10+(r-1)*50, 10, 0, .X1, .Y1, .X2, .Y2) End With ELSE With rockets_inactive PutClip (texture, 10+(r-1)*50, 10, 0, .X1, .Y1, .X2, .Y2) End With END IF NEXT r IF MULTIKEY(SC_UP) THEN IF MyPlayer.rockets_current < MyPlayer.rockets_max THEN MyPlayer.rockets_current += 1 END IF IF MULTIKEY(SC_DOWN) THEN IF MyPlayer.rockets_current > 0 THEN MyPlayer.rockets_current -= 1 END IF IF MULTIKEY(SC_RIGHT) THEN IF MyPlayer.health_current < MyPlayer.health_max THEN MyPlayer.health_current+=1 ELSE IF MyPlayer.shield_current < MyPlayer.shield_max THEN MyPlayer.shield_current+=1 END IF END IF END IF IF MULTIKEY(SC_LEFT) THEN IF MyPlayer.shield_current > 0 THEN MyPlayer.shield_current-=1 ELSE IF MyPlayer.health_current > 0 THEN MyPlayer.health_current-=1 END IF END IF END IF glFlush flip screensync loop until multikey(SC_ESCAPE) ' Verlasse die Schleife sobald Escape gedrückt wird
Screenshot:Steuerung:- Pfeiltaste Hoch - Rakete hinzufügen
- Pfeiltaste Runter - Rakete entfernen
- Pfeiltaste Rechts - Reparatur
- Pfeiltaste Links - Schaden auf das Schiff
- Escape - Beenden
-
Spiegeleffekte
Eins vorweg: Spiegeleffekte lassen sich sehr einfach realisieren! Zumindest kommt es darauf an, wie detailreich man diese Spiegelungen erstellen möchte. Wir werden einfache Spiegelungen ohne Brüche oder Wellenverzerrungen vornehmen.Theorie:Wir müssen erstmal klarstellen, wie unser Spiegeleffekt aussehen soll. Im Grunde genommen ist es nichts anderes als eine halb-transparente 180-Grad-Spiegelung eines Objektes. Das problematische dabei ist, diese Spiegelung auch wirklich genau auf unsere spiegelnde Oberfläche zu bringen. Sprich kein Landflächen soll unser Objekt spiegeln, sondern nur die klare Wasseroberfläche. Um dieses Problem zu lösen, bietet uns OpenGL durch die Kombination aus Blending und dem Tiefentest einen guten Ausweg.
Wir geben OpenGL vor, dass wir nur auf eine bestimmte Ebene halb-transparent zeichnen wollen. Die Wasserfläche kommt auf eine bestimmte Ebene, die ungleich der der restlichen Flächen wie Gras, Stein, etc. ist. Dies geschieht über den Befehl:glDepthTest(GL_EQUAL)
Das ist der Schlüssel zur Lösung. Die Drehung des Objektes realisieren wir, indem wir einfach die Texturkoordinaten vertauschen. Dies habe ich schon im Sub PutTextureVert vorbereitet. Damit haben wir eigentlich alles geklärt. Schauen wir uns folgendes kleines Beispiel an, in welchem das ganze angewandt wird:Beispiel (example_mirror.bas)#include "inc/2d_opengl.bi" ' Festlegung der Konstanten, die für den Bildschirm wichtig sind const scrnX = 640 const scrnY = 480 const depth = 32 const fullscreen = &h0 ' Vollbildmodus ( &h0 = aus, &h1 = an ) DIM AS GFXType texture ' Initialisiere den Schirm Init_ogl2D(scrnX, scrnY, depth, fullscreen) glEnable GL_TEXTURE_2D ' Texturen erlauben. load_texture ("resources/examples/horse.tga", texture) ' Lade eine Textur WindowTitle "[ Mirror ]" DIM AS INTEGER x, y x = 150 y = 0 do glCls ' Zeichne Untergrund glDepthfunc (GL_LESS) ' Standard-Tiefentest glDisable (GL_BLEND) ' Kein Blend-Modus glBindTexture GL_TEXTURE_2D, 0 ' Löse Textur, da wir farbige Objekte ' ohne Textur zeichnen wollen. glColor3ub 19,160,0 ' Grüne Wiese PutBox (1, 200, 640, 460, 1) glColor3ub 0,16,160 ' Wasserfläche PutBox (150, 230, 480, 460, 2) glColor3ub 255,255,255 PutTexture (texture, x, y, 10) ' Figur ' Zeichne Spiegelung glDepthfunc (GL_EQUAL) ' Tiefentest: Gleiche Ebene glEnable (GL_BLEND) ' Unser schöner Blendmodus für Transparenz glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) glColor4f 1,1,1,.5 ' Zeichne verkehrte Figur PutTextureVert (texture, x, y+texture.height, 2) IF MULTIKEY(SC_LEFT) THEN x-=1 IF MULTIKEY(SC_RIGHT) THEN x+=1 IF MULTIKEY(SC_UP) THEN y-=1 IF MULTIKEY(SC_DOWN) THEN y+=1 glFlush flip screensync loop until multikey(SC_ESCAPE) ' Verlasse die Schleife sobald Escape gedrückt wird
Screenshot:Steuerung:- Pfeiltasten - Damit bewegen wir unser Pferd
- Escape - Beenden
-
Displaylisten
Nun kommen wir zu einer recht interessanten Technik, welche uns OpenGL von Haus aus anbietet. Displaylisten sind im Grunde nichts anderes als eine "Aufnahme" unserer OpenGL-Befehle in einer Liste. Wann auch immer wir diese Liste aufrufen, werden sämtliche in ihr enthaltenen Befehle ausgeführt. Dies ist ungemein praktisch bei Objekten, welche in ihrer Form nicht verändert werden. Der große Vorteil auch ist der Geschwindigkeitszuwachs, wenn man Displaylisten benutzt. Das Prinzip dahinter ist, dass sämtliche Befehle, Statechanges und Vertexdaten innerhalb der Liste vom Grafikkartentreiber optimiert werden und im Grafikkartenspeicher abgelegt werden. Man muss nun nicht mehr sämtliche Befehle neu an die Grafikkarte senden, sondern nur noch einen Listenabruf tätigen.Vorteile
- Geschwindigkeit
Nachteile- hoher Speicherverbrauch
- nur für statische Objekte geeignet
Wie verwende ich Displaylisten ?Displaylisten sind sehr einfach zu nutzen. Man muss dazu folgende Befehle verwenden:dim GL_list as uninteger ' unser Listen-Handle definieren glNewList GL_list, GL_COMPILE ' Starte neue Liste (Beginne Aufnahme) '' Hier folgen die OpenGL-Befehle '' glEndList ' Ende der Aufnahme glCallList GL_list ' Liste aufrufen
glNewList erwartet im ersten Parameter das Handle und im zweiten Parameter die "Art der Aufnahme". Möglich ist hierbei:- GL_COMPILE - Sämtliche Befehle werden nur kompiliert und für die Liste aufbereitet.
- GL_COMPILE_AND_EXECUTE - Nachdem die Befehle in einer Liste kompiliert worden sind, wird diese Liste auch aufgerufen.
Es ist übrigens nicht möglich, Displaylisten zu koppeln, da dies zu einer Endlosschleife führen wird. Ein Beispiel für Displaylisten spare ich mir an dieser Stelle, da jeder sich da etwas einfallen lassen kann. ;-) Ich persönlich finde es ganz gut, um ein "Standbild" zu erstellen. Man nimmt einmal den Bildschirm auf, zeichnet ihn darauffolgend immer und schon hat man sein Standbild. Eignet sich ziemlich gut, um für sein Spiel einen Pause-Bildschirm zu entwerfen. Gemeinhin sollte man Displaylisten aber nur dafür nutzen, sich immer wiederkehrende starre Objekte zu zeichnen. -
Schrift
Großes Problem bisher war die Tatsache, dass wir keine Schriften in OpenGL verwenden konnten. Wir müssen uns selber etwas dazu einfallen lassen, um diese Lücke zu schließen. Ich möchte euch kurz mein entwickeltes Schriftsystem vorstellen, welches zwar nicht perfekt, aber dennoch ganz gut anwendbar ist.
Wichtig sind erstmal folgende zwei Strukturen:TYPE FontChar Width AS INTEGER Height AS INTEGER Left AS INTEGER Right AS INTEGER END TYPE TYPE FontSystem Char(0 TO 255) AS FontChar listbase AS UINTEGER texture AS UINTEGER Spacing AS INTEGER Fixed AS UBYTE END TYPE
Mit dem TYPE FontSystem könnt ihr eure Schrift initalisieren:DIM MyFont AS FontSystem
Nun habt ihr Zugriff auf folgende SUBs und FUNCTIONs:Schriftsatz ladenUm einen Schriftsatz zu laden bedarf es zwei Dateien. Eine, die die Pixeldaten der Schrift enthält und eine, die für jeden Buchstaben die Breite, Höhe und den Abstand beinhaltet. Die Grafikdatei ist 256x256 Pixel groß und hat 256 Zeichen, welche einzeln 16x16 Pixel groß sind.SUB FontLoad (ByRef Font AS FontSystem, ByVal SourceFile AS STRING, ByVal DataFile AS STRING)
Beispiel für eine Schriftdatei:Text darstellenDafür gibt es zwei Wege. Nehmt glPrint, um schon eine vorgefertige Schriftdarstellung zu haben. FontPrint unterscheidet sich ein wenig in seinen Parametern und lässt Spielraum für Änderungen in Bezug auf Blending, Tiefentest, Farben, etc. zu.SUB glPrint (ByRef Font AS FontSystem, ByVal text AS STRING, ByVal x AS INTEGER, BYVal y AS INTEGER, ByVal z AS INTEGER, ByVal R AS INTEGER, ByVal G AS INTEGER, ByVal B AS INTEGER, ByVal A AS INTEGER = 255) SUB FontPrint (ByRef Font AS FontSystem, ByVal Text AS STRING, ByVal X AS INTEGER, ByVal Y AS INTEGER, ByVal Z AS INTEGER = 0, ByVal scale AS SINGLE = 1)
Schriftbreite ermittelnEs ist natürlich für manche Gelegenheiten wichtig, die genaue Breite eines Textes in Pixeln zu ermitteln, um damit Beschränkungen in Textboxen zu realisieren oder um den Text genau mittig darzustellen.FUNCTION FontGetWidth (ByRef Font AS FontSystem, ByVal Text AS STRING) AS INTEGER
Schriftsatz löschenSUB DestroyFont (ByRef Font AS FontSystem)
Das waren nun alle Features, die dieses Schriftroutine euch bieten kann. Nun folgt ein kleines Beispiel, welches diese Schriftroutine anwendet:Beispiel (example_font.bas)#include "inc/2d_opengl.bi" ' Festlegung der Konstanten, die für den Bildschirm wichtig sind const scrnX = 640 const scrnY = 480 const depth = 32 const fullscreen = &h0 ' Vollbildmodus ( &h0 = aus, &h1 = an ) ' Initialisiere den Schirm Init_ogl2D(scrnX, scrnY, depth, fullscreen) WindowTitle "[ Font ]" DIM MyFont AS FontSystem MyFont.Spacing = 2 FontLoad(MyFont, "resources/examples/standard.tga","resources/examples/standard.dat") TYPE BouncingText AS STRING Text AS INTEGER R, G, B AS INTEGER posX, posY AS INTEGER dirX, dirY AS INTEGER w END TYPE DIM MyText(1 TO 5) AS BouncingText DIM AS INTEGER e, x, y, r RANDOMIZE TIMER FOR e = 1 TO 5 With MyText(e) .text = "Example text #" + STR$(e) .w = FontGetWidth(MyFont, .text) .posX = INT(RND * scrnX - .w) + 1 .posY = INT(RND * scrnY - 16) + 1 .dirX = INT(RND * 6) - 3 .dirY = INT(RND * 6) - 3 .R = INT(RND * 128) + 128 .G = INT(RND * 128) + 128 .B = INT(RND * 128) + 128 End With NEXT e do glCls FOR x = 0 TO fix(scrnX / 16) FOR y = 0 TO fix(scrnY / 16) r = INT(RND * 50)+40 glPrint (MyFont, STR$(INT(RND * 2)), x*16,y*16,0,r,r,r) NEXT y NEXT x FOR e = 1 TO 5 With MyText(e) glPrint (MyFont, .text, .posX, .posY, 1, .R, .G, .B) IF .posX + .w >= scrnX THEN .dirX = .dirX*-1 IF .posY + 16 >= scrnY THEN .dirY = .dirY*-1 IF .posX <= 0 THEN .dirX = .dirX*-1 IF .posY <= 0 THEN .dirY = .dirY*-1 .posX += .dirX .posY += .dirY IF MULTIKEY(SC_R) THEN .posX = INT(RND * scrnX - .w) + 1 .posY = INT(RND * scrnY - 16) + 1 .dirX = INT(RND * 6) - 3 .dirY = INT(RND * 6) - 3 .R = INT(RND * 128) + 128 .G = INT(RND * 128) + 128 .B = INT(RND * 128) + 128 END IF End With Next e glFlush flip screensync loop until multikey(SC_ESCAPE) ' Verlasse die Schleife sobald Escape gedrückt wird DestroyFont(MyFont)
Screenshot:In diesem Beispiel springen 5 Texte quer durch den Bildschirm. Steuerung:- Taste R - Reset aller Textpositionen und -Farben
- Escape - Beenden
-
Lichtquellen
Eins vorweg: Wir werden keine ultrarealistischen Lichteffekte erstellen. Wir nehmen lediglich eine einfache schwarz-weiße Textur, färben diese ein und erzeugen somit unser Licht. Eine typische Textur dafür, wäre also folgende:So sieht eine typische Lichttextur aus:Beispiel: (example_light.bas)
#include "inc/2d_opengl.bi" ' Festlegung der Konstanten, die für den Bildschirm wichtig sind const scrnX = 640 const scrnY = 480 const depth = 32 const fullscreen = &h0 ' Vollbildmodus ( &h0 = aus, &h1 = an ) ' Initialisiere den Schirm Init_ogl2D(scrnX, scrnY, depth, fullscreen) WindowTitle "[ Light ]" TYPE Light AS INTEGER R, G, B, A AS SINGLE X, Y, dirX, dirY AS INTEGER w END TYPE DIM MyLight(1 TO 5) AS Light RANDOMIZE TIMER FOR e = 1 TO 5 With MyLight(e) .w = INT(RND * 256) + 128 .X = INT(RND * scrnX) + 1 .Y = INT(RND * scrnY) + 1 .R = INT(RND * 128) + 128 .G = INT(RND * 128) + 128 .B = INT(RND * 128) + 128 .A = INT(RND * 128) + 128 .dirX = INT(RND * 6) - 3 .dirY = INT(RND * 6) - 3 End With NEXT e DIM AS GFXType light_texture, background_texture DIM AS INTEGER x, y glEnable GL_TEXTURE_2D ' Texturen erlauben. load_texture ("resources/examples/light.tga", light_texture) ' Lade Lichttextur load_texture ("resources/examples/brick.tga", background_texture)' Lade Hintergrund glEnable GL_BLEND do glCls glcolor4f .5,.5,.5,1 glDepthFunc (GL_LESS) glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) FOR x = 0 TO fix(scrnX / background_texture.width) FOR y = 0 TO fix(scrnY / background_texture.height) PutTexture (background_texture,_ x * background_texture.width,_ y * background_texture.height, 0) NEXT y NEXT x glDepthFunc (GL_ALWAYS) glBlendFunc (GL_SRC_ALPHA, GL_DST_ALPHA) FOR e = 1 TO 5 With MyLight(e) glcolor4ub .R, .G, .B, 255 glBindTexture GL_TEXTURE_2D, light_texture.handle DrawTexQuad (.X, .Y, 1, .w, .w) IF .X + .w/2 >= scrnX THEN .dirX = .dirX*-1 IF .Y + .w/2 >= scrnY THEN .dirY = .dirY*-1 IF .X + .w/2 <= 0 THEN .dirX = .dirX*-1 IF .Y + .w/2 <= 0 THEN .dirY = .dirY*-1 .X += .dirX .Y += .dirY IF MULTIKEY(SC_R) THEN .X = INT(RND * scrnX) + 1 .Y = INT(RND * scrnY) + 1 .R = INT(RND * 128) + 128 .G = INT(RND * 128) + 128 .B = INT(RND * 128) + 128 .A = INT(RND * 128) + 128 .w = INT(RND * 256) + 128 .dirX = INT(RND * 6) - 3 .dirY = INT(RND * 6) - 3 END IF End With Next e glFlush flip screensync loop until multikey(SC_ESCAPE) ' Verlasse die Schleife sobald Escape gedrückt wird
Screenshot:Ähnlich wie im Schriftbeispiel fliegen auch hier die Objekte über den Bildschirm ! ;) Steuerung:- Taste R - Reset aller Lichtpositionen und -Farben
- Escape - Beenden
Beispiel: (example_light2.bas)#include "inc/2d_opengl.bi" ' Festlegung der Konstanten, die für den Bildschirm wichtig sind const scrnX = 640 const scrnY = 480 const depth = 32 const fullscreen = &h0 ' Vollbildmodus ( &h0 = aus, &h1 = an ) ' Initialisiere den Schirm Init_ogl2D(scrnX, scrnY, depth, fullscreen) WindowTitle "[ Light #2 ]" TYPE Light AS INTEGER R, G, B, A AS SINGLE X, Y, dirX, dirY AS INTEGER w END TYPE DIM MyLight(1 TO 5) AS Light RANDOMIZE TIMER FOR e = 1 TO 5 With MyLight(e) .w = INT(RND * 256) + 128 .X = INT(RND * scrnX) + 1 .Y = INT(RND * scrnY) + 1 .R = INT(RND * 128) + 128 .G = INT(RND * 128) + 128 .B = INT(RND * 128) + 128 .A = INT(RND * 128) + 128 .dirX = INT(RND * 6) - 3 .dirY = INT(RND * 6) - 3 End With NEXT e DIM AS GFXType light_texture, background_texture DIM AS INTEGER x, y glEnable GL_TEXTURE_2D ' Texturen erlauben. load_texture ("resources/examples/light.tga", light_texture) ' Lade Lichttextur load_texture ("resources/examples/brick.tga", background_texture)' Lade Hintergrund glEnable GL_BLEND do glCls glcolor4f .5,.5,.5,1 glDepthFunc (GL_LESS) glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) FOR x = 0 TO fix(scrnX / background_texture.width) FOR y = 0 TO fix(scrnY / background_texture.height) PutTexture (background_texture,_ x * background_texture.width,_ y * background_texture.height, 0) NEXT y NEXT x glDepthFunc (GL_ALWAYS) glBlendFunc (GL_SRC_ALPHA, GL_SRC_ALPHA) FOR e = 1 TO 5 With MyLight(e) glcolor4ub .R, .G, .B, 255 glBindTexture GL_TEXTURE_2D, light_texture.handle DrawTexQuad (.X, .Y, 2, .w, .w) IF .X + .w/2 >= scrnX THEN .dirX = .dirX*-1 IF .Y + .w/2 >= scrnY THEN .dirY = .dirY*-1 IF .X + .w/2 <= 0 THEN .dirX = .dirX*-1 IF .Y + .w/2 <= 0 THEN .dirY = .dirY*-1 .X += .dirX .Y += .dirY IF MULTIKEY(SC_R) THEN .X = INT(RND * scrnX) + 1 .Y = INT(RND * scrnY) + 1 .R = INT(RND * 128) + 128 .G = INT(RND * 128) + 128 .B = INT(RND * 128) + 128 .A = INT(RND * 128) + 128 .w = INT(RND * 256) + 128 .dirX = INT(RND * 6) - 3 .dirY = INT(RND * 6) - 3 END IF End With Next e glDepthFunc(GL_LESS) glBlendFunc GL_DST_COLOR, GL_DST_COLOR glcolor4f 0,0,0,1: glBindTexture GL_TEXTURE_2D, 0 PutBox (0,0,scrnX,scrnY,3) glFlush flip screensync loop until multikey(SC_ESCAPE) ' Verlasse die Schleife sobald Escape gedrückt wird
Screenshot:Steuerung:- Taste R - Reset aller Lichtpositionen und -Farben
- Escape - Beenden
-
Schatten
Da dies ein Anfängertutorial ist, wagen wir uns nicht an komplexe Schatteneffekte. Nach diesem Tutorial werde ich aber auf weiterführende Links verweisen, in welchen diverse Techniken dafür erklärt werden. Aber nun zurück zu unseren Umsetzung von Schatten in der 2D-Welt.Wie setzen wir unseren 2D-Schatten um?Wir nehmen uns einen Schatten vor, welcher wirklich einfach umzusetzen ist: Wir zeichnen erst die Spielfigur und dann rechts unterhalb dieselbe Spielfigur, bloß mit dem Unterschied, dass diese vollkommen verdunkelt ist. Da der Schatten nach rechts unten fällt, kommt das "Licht" von links oben. Unsere "Lichtquelle" existiert allerdings nicht wirklich und wir sind somit darauf angewiesen unseren Schatten zu simulieren. Man könnte dies über OpenGL-Mittel realisieren, aber wir machen dies nicht, da der Aufwand ungerechtfertigt wäre.Beispiel: (example_shadow.bas)
#include "inc/2d_opengl.bi" ' Festlegung der Konstanten, die für den Bildschirm wichtig sind const scrnX = 640 const scrnY = 480 const depth = 32 const fullscreen = &h0 ' Vollbildmodus ( &h0 = aus, &h1 = an ) DIM AS GFXType texture, background_texture ' Initialisiere den Schirm Init_ogl2D(scrnX, scrnY, depth, fullscreen) glEnable GL_TEXTURE_2D ' Texturen erlauben. load_texture ("resources/examples/horse.tga", texture) ' Pferd load_texture ("resources/examples/grass.tga", background_texture)' Hintergrund glEnable GL_BLEND ' Blending erlauben WindowTitle "[ Shadow ]" DIM AS INTEGER x, y, bx, by x = 150 y = 0 do glCls ' Zeichne Untergrund FOR bx = 0 TO fix(scrnX / background_texture.width) FOR by = 0 TO fix(scrnY / background_texture.height) PutTexture (background_texture,_ bx * background_texture.width,_ by * background_texture.height, 0) NEXT by NEXT bx ' Schattenabbild glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) glColor4ub 0,0,0,128 glBindTexture GL_TEXTURE_2D, texture.handle DrawTexQuad (x+5, y+5, 2, texture.width, texture.height) ' Original glColor3ub 255,255,255 glBindTexture GL_TEXTURE_2D, texture.handle DrawTexQuad (x, y, 3, texture.width, texture.height) 'PutTexture (texture, x, y, 10) ' Figur IF MULTIKEY(SC_LEFT) THEN x-=1 IF MULTIKEY(SC_RIGHT) THEN x+=1 IF MULTIKEY(SC_UP) THEN y-=1 IF MULTIKEY(SC_DOWN) THEN y+=1 glFlush flip screensync loop until multikey(SC_ESCAPE) ' Verlasse die Schleife sobald Escape gedrückt wird
Screenshot:Steuerung:- Pfeiltasten - Pferd bewegen
- Escape - Beenden
Darstellung von Höhe:Wenn man für sein Spiel angenommen Raumschiffe nehmen möchte, die über die Erde fliegen und dazu einen größeren Abstand zum Boden haben, kann man diesen Eindruck von der großen Höhe dadurch erreichen, indem man den Schatten weiter entfernt und kleiner als das eigentliche Raumschiff darstellt. Im Bild rechts wurde dies anhand unseres FreeBASIC-Maskottchens demonstriert! ;-)
-
Skalieren und Rotieren
-
Schlusswort
Du hast dich tatsächlich bis zum Schlusswort durchgearbeitet! Gratuliere! :-) Ich hoffe, dass ich dir mit diesem Tutorial helfen konnte. Ich habe versucht, einen kleinen Einblick in die Welt von OpenGL zu gewähren. Wenn man sich intensiver mit OpenGL beschäftigt, kann man sehr viel mehr interessante Dinge für sich entdecken. Es ist weitaus mehr möglich als hier gezeigt wurde. Ich lege euch an's Herz, nicht mit diesem Tutorial aufzuhören sich mit OpenGL zu beschäftigen. Das Internet bietet zahlreiche Anleitungen, Beschreibungen, Wikis und derlei, die euch viel mehr erzählen können. Dieses Tutorial sollte bloß ein Anfang für euch sein!Stormy aka Paul GrunewaldWeiterführende Links:Allgemein:
- http://opengl.org/ - DIE OpenGL-Seite überhaupt. Hier spielt sich die ganze Treiben der OpenGL-Community ab. Die Seite birgt sehr viele Informationen und Tutorials. Insgesamt ein gigantisches Wissensarchiv.
- http://wiki.delphigl.com/ - Eine Seite die auf verständliche Weise sämtliche Funktionen von OpenGL erklärt. Besonders die Tutorials sind sehr interessant. Als Nachschlagewerk für OpenGL-Anweisungen ist diese genauso gut zu haben - zu mal alles in deutsch verfasst ist. Ich empfehle euch vorallem das 2D-Tutorial zu lesen, welches speziell für mich hilfreich gewesen ist.
- http://www.codeworx.org/ - Hier wurden die bekannten NeHe-Tutorials ins deutsche übersetzt. Die in FreeBASIC übersetzten Codes findet ihr in eurem FB-Ordner unter \examples\GL.
- http://pixwiki.bafsoft.com/ - interessantes Internet-Magazin, welches in ihren Ausgaben verschiedene Dinge veranschaulicht.
Schatten & Licht:FreeBASIC:- http://www.freebasic.net/ - Die internationale Seite zum FreeBASIC-Compiler.
- http://www.freebasic-portal.de/ - Das deutsche Pendant zur oben genannten Seite.
- http://www.qb-forum.de/ - Das offizielle deutsche QBASIC-/FreeBASIC-Forum.
Kontakt:E-Mail: Paul.Grunewald [AT] gmail.com
www: www.storm-master.de