2D-Anwendungen mit OpenGL und FreeBASIC

<< Zusammen einfach unschlagbar! >>
Das Logo von FreeBASIC Das Logo von OpenGL

Inhaltsverzeichnis:

  1. Vorwort
  2. OpenGL und FreeBASIC
    1. Allgemeines
    2. Vorteile
    3. Nachteile
  3. Erste Gehversuche mit OpenGL
    1. Einbinden der Grafiklib für Windows und Linux
    2. Konfiguration der OpenGL-Einstellungen für den 2D-Betrieb
    3. Die Formen und Farben in OpenGL
  4. Texturen
    1. Allgemeine Informationen
    2. Unsere Texturenstruktur
    3. Laden von Grafiken
    4. Darstellung von Grafiken
  5. Blending
    1. Was ist das ?
    2. Praktische Anwendung und Beispiele
  6. Nützliche Techniken
    1. Skalieren und Rotieren
    2. Tilemaps
    3. Clipping
    4. Spiegeleffekte
    5. Displaylisten
    6. Schrift
    7. Lichtquellen
    8. Schatten
  7. Schlusswort

  1. 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
  2. OpenGL und FreeBASIC
    1. 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
      Wie unschwer aus diesem Zitat zu erkennen ist, beschäftigt sich OpenGL hauptsächlich mit 3D-relevanten Dingen. Wozu also OpenGL für 2D verwenden? Man kann doch einfach die Grafiklib fbgfx2 benutzen, die in FreeBASIC enthalten ist! Ganz einfach: OpenGL ist hardwarebeschleunigt! Das bedeutet, dass sämtliche Zeichenfunktionen von der Grafikkarte hoch-optimiert wurden und daher rasend schnell sind. Zusammen mit dem sehr einfachen Syntax von FreeBASIC werden auch Anfänger es einfach haben, Programme oder Spiele zu entwerfen, die die Leistung der neuesten Grafikkarten auch wirklich ausnutzen können.
    2. Vorteile
      Hier einige Dinge, die für die Verwendung von OpenGL (in Verbindung mit FreeBASIC) sprechen:
      1. Ausnutzung der Kapazität der Grafikarte
      2. CPU-Entlastung und damit bessere Performance
      3. zahlreiche Tutorials und Dokumentation im Internet
      4. einfache API (OpenGL) und einfacher Syntax (FreeBASIC)
      5. zahlreiche neue und hardwarebeschleunigte Zeichenfunktionen
      6. einfache Bildschirminitialisierung dank FreeBASIC
      7. Plattformunabhängigkeit
    3. Nachteile
      Wo Licht scheint, musst natürlich auch Schatten fallen. Auch Rosen haben Dornen:
      1. Starke Bindung an die Grafikkarte
      2. Umdenken für den FB-Programmierer (Keine Sorge! Alles ist machbar!)
      3. Anpassungen für die Grafiken (dazu im Abschnitt: Texturen mehr)
  3. Erste Gehversuche mit OpenGL
    1. 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.
    2. 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
      1. 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.
      2. depth ist die Bildtiefe in Bit. Empfohlen ist hierbei 32 anzugeben, da bei manchen Grafikkarten 16bit einfach langsamer ist.
      3. 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äuterung
      In 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äuterung
      Damit 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
      1. 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).
      2. Mit glflush werden sämtliche OpenGL-Anweisungen ausgeführt.
      3. 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.
      4. 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.
    3. Die Formen und Farben in OpenGL
      Objekte:
      OpenGL bietet eine große Bandbreite an Objekten die man zeichnen kann. Da wären beispielsweise:
      1. Punkte
      2. Linien
      3. Diverse Dreiecke
      4. Vierecke (für uns am wichtigsten)
      5. und Polygone (Vielecke)
      Jedes dieser Objekte bedarf eines Mindestsatz an Punkten, damit diese Objekte überhaupt gebildet werden. Wo ich für eine Linie zwei Punkte brauche, benötige ich logischerweise für ein Viereck gleich ganze vier Punkte um dieses darzustellen. Nur, wie kann ich OpenGL sagen, was er zeichnen soll? Lies dir erstmal diesen kurzen Codeausschnitt durch, damit ich dir das Schema erklären kann:
      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
      Soweit so gut! Wenn man also ein Viereck darstellen will, dann schreibt man einfach glBegin GL_QUADS und eine Zeile weiter unten glEnd. Innerhalb dieses "Blockes" müssen nun die Koordinaten angegeben werden:
      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.
      Mit glLoadIdentity stellen wir sozusagen unsere Matrix wieder her. Praktisch ein "Reset", was für uns bedeutet, dass unser "Cursor" wieder an der Stelle (0,0) ist.

      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:
      1. glColor3f: Die Rot-, Blau-, Grün-Werte liegen zwischen 1 und 0 (Fließkommazahlen)
      2. glColor4f: Die Rot-, Blau-, Grün- und Alpha-Werte liegen zwischen 1 und 0 (Fließkommazahlen)
      3. glColor3ub: Die Rot-, Blau-, Grün-Werte liegen zwischen 255 und 0 (UBYTE)
      4. glColor4ub: Die Rot-, Blau-, Grün- und Alpha-Werte liegen zwischen 255 und 0 (UBYTE)
      Ich empfehle übrigens die letzte beiden Varianten, da diese sich einfach gewohnter für den FreeBASIC-Programmierer sind. Es steht natürlich jeden frei, was er bevorzugt. Die Alpha-Werte werden nur im 32Bit-Modus interpretiert.

      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.
      Kleiner Tipp am Rande: Wenn ihr den Alphachannel nutzen wollt, müsst ihr einen bestimmten Blendmodus aktivieren und 32bit aktivieren, damit Stellen teiltransparent wirken. Nähers dazu könnt ihr im Kapitel Blending lesen.
  4. Texturen
    1. 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
      Wir haben übrigens auch die Möglichkeit spezielle Filter für unsere Texturen anzuwenden, die schon aus diversen 3D-Spielen bekannt sind.
    2. 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
      1. Handle ist eine Nummer mit der die Textur identifiziert wird.
      2. Width, Height sind die Maßangaben
      3. TextureFilter darüber geben wir Filter-Optionen. Benötigen wir aber momentan noch nicht.
      4. 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.
      5. bpp heißt Bit-Per-Pixel und beschreibt wie hoch die Farbtiefe ist.
      6. textype gibt Auskunft darüber, ob der Alphachannel genutzt wird oder nicht. Mögliche Wert sind hier GL_RGB und GL_RGBA
      Um nun diese Struktur nutzen zu können, müssen wir nur eine Variable mit diesem Datentyp deklarieren:
      DIM Textur AS GFXType
      
    3. 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
      
      1. Bei filename übermitteln wir den Pfad zu unserer Textur.
      2. texture ist unsere Variable in welcher sämtliche Texturedaten geladen werden.
      3. Mit flags können wir bestimmte Optionen und Filter für die Textur aktivieren. Mögliche Werte für flags sind:
        1. 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:
          Unterschiede zwischen GL_NEAREST und GL_LINEAR
        2. 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:
          Beispielbild ohne und mit Mipmaps
      4. 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)
      Puh! Das waren schon viele Hintergrundinformationen! :) Die Anwendung hingegen geht kinderleicht:
      #include "inc/2d_opengl.bi"
      
      dim Textur as GFXType
      call load_texture("grassflaeche.bmp", Textur)
      
      Und voilá: Unsere Texture wurde geladen!
    4. 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".
      Damit nun OpenGL weiß, von welcher Grafikdatei wir die Texturkoordinaten angeben, müssen wir dies vorher festlegen:
      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:
      Dieses ist nun kleiner (mit 50x50 Pixel) als die eigentliche Texturgröße mit 128x128 Pixeln (ergo der minfilter wird aktiv). Um dies zu verhindern; also um immer in der "richtigen" Größe die Textur auszugeben nutzen wir einfach die Daten aus unserer Texturestruktur aus:
      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:
      Und nun ein letzter Tipp zu diesem Kapitel: Es ist natürlich möglich auch Vierecke, die mit einer Textur versehen worden sind, mit (glColor) einzufärben! Viel Spaß beim Ausprobieren ! ;)
  5. Blending
    1. 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)
      Die dritte und somit letzte Spalte enthält zur jeweiligen Konstante die Formel. Die Buchstaben R,G,B,A entsprechen den Rot-, Grün-, Blau- und Alpha-Werten. Das Kürzel d steht für "Destination", also der Zielpixel und s steht für "Source", also den Pixel der darauf gezeichnet werden soll. Der mathematische Hintergrund kann dahingehend nützlich sein, da man dadurch genau weiß, was rauskommen muss. ür Praktiker gibt es natürlich immer noch die Option, einfach mal die ganzen Modi Kombinationen auszuprobieren! ;-)

      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
    2. 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:
      1. Pfeiltasten - Springen zu den verschiedenen Optionen
      2. 1, 2, 3 - zwischen den Texturen springen
      3. Enter - Werte ändern
      4. ESC - Beenden
      5. B - Blendmodus an/aus
      6. D - Tiefentest an/aus
  6. 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
    1. 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:
      1. Pfeiltaste Hoch - Objekt vergrößern
      2. Pfeiltaste Runter - Objekt verkleinern
      3. Pfeiltaste Rechts - Drehungsgrad erhöhen
      4. Pfeiltaste Links - Drehungsgrad senken
      5. 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)
      
    2. 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....
      1. Höhe, Breite der Textur
      2. Höhe, Breite des Tiles
      3. und die Index-Nummer des bestimmten Tiles, was wir zeichnen wollen
      Höhe und Breite der Textur sind fest im GFXType verankert. Die Daten der Tiles können wir so definieren:
      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)
      
      1. index ist eine Nummer mit der das Tile identifiziert wird.
      2. w, h sind die Maßangaben für das Tile
      3. x, y, z sind die Koordinaten, wo das Objekt gezeichnet werden soll.
      Soweit so gut. Nun gilt es, die korrekten Texturkoordinaten zu berechnen:
      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).
    3. 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
      Wie man unschwer erkennen kann, haben wir im Gegensatz zu den Tilemaps unterschiedlich große Grafiken innerhalb enthalten. Wir wissen bereits, zwischen welchen Punkten sich die Grafik befinden:
      1. die leere Schildanzeige zwischen (0,0) und (254, 46)
      2. die volle Schildanzeige zwischen (0,46) und (254, 92)
      3. die leere Schiffsanzeige zwischen (0,92) und (254, 138)
      4. die volle Schiffsanzeige zwischen (0,138) und (254, 184)
      5. die einsatzbereite Rakete zwischen (0,207) und (24, 254)
      6. die nicht einsatzbereite Rakete zwischen (25,207) und (49, 254)
      Auch hier entwerfen wir ein kleines SUB, was für uns die zukünftige Arbeit abenehmen wird:
      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)
      
      1. x, y, z sind die Koordinaten, wo das Objekt gezeichnet werden soll.
      2. offset_(x1,y1) und offset_(x2,y2) sind unsere beiden Koordinaten zwischen denen unser Ausschnitt liegt.
      Die Größe unseres zu zeichnendes Quads ist demzufolge variabel und abhängig von unseren Texturkoordinaten.
      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:
      1. Pfeiltaste Hoch - Rakete hinzufügen
      2. Pfeiltaste Runter - Rakete entfernen
      3. Pfeiltaste Rechts - Reparatur
      4. Pfeiltaste Links - Schaden auf das Schiff
      5. Escape - Beenden
    4. 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:
      1. Pfeiltasten - Damit bewegen wir unser Pferd
      2. Escape - Beenden
    5. 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
      1. Geschwindigkeit
      Nachteile
      1. hoher Speicherverbrauch
      2. 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:
      1. GL_COMPILE - Sämtliche Befehle werden nur kompiliert und für die Liste aufbereitet.
      2. 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.
    6. 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 laden
      Um 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:
      Man kann natürlich auch seinen eigenen Schriftsatz erstellen, bloß muss für diesen die Datei mit den enthaltenen Buchstabendaten angepasst werden.
      Text darstellen
      Dafü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 ermitteln
      Es 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öschen
      SUB 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:
      1. Taste R - Reset aller Textpositionen und -Farben
      2. Escape - Beenden
    7. 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:
      Diese Textur können wir einfach mit dem Tiefentest glDepthFunc(GL_ALWAYS) und dem Blendingmodus glBlendFunc(GL_SRC_ALPHA, GL_DST_ALPHA) anwenden.
      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:
      1. Taste R - Reset aller Lichtpositionen und -Farben
      2. Escape - Beenden
      Im aktuellen Status machen wir eigentlich nichts anderes, als den Untergrund farbig zu machen. Mit Licht hat dies noch wenig zu tun. Im folgenden Beispiel nutzen wir unsere Lichter um eine dunkle Umgebung zu erhellen:
      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:
      1. Taste R - Reset aller Lichtpositionen und -Farben
      2. Escape - Beenden
    8. 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:
      1. Pfeiltasten - Pferd bewegen
      2. 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! ;-)
  7. 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 Grunewald
    Weiterführende Links:
    Allgemein:
    Schatten & Licht:
    FreeBASIC:

    Kontakt:
    E-Mail: Paul.Grunewald [AT] gmail.com
    www: www.storm-master.de
Datum Änderung
28.07.2006 Erste Version ist fertig.
30.07.2006 Diverse Korrekturen (Dank an Mao und Bernd)
08.09.2006 Weitere Korrekturen (Dank an Jojo und Dagere)
26.03.2007 Funktionierende PNG-Ladefunktion integriert.
24.04.2007 Die Library samt Beispiele sind für FreeBASIC 0.17b lauffähig. (Dank an h4tt3n)
29.03.2009 Links angepasst. (Dank an Sebastian)