Glbitmap loader: Unterschied zwischen den Versionen

Aus DGL Wiki
Wechseln zu: Navigation, Suche
(glBitmap - Texturenloader: 's durch s ersetzt.)
()
Zeile 1: Zeile 1:
{{unvollständig}}
 
 
= glBitmap - Texturenloader =
 
= glBitmap - Texturenloader =
  

Version vom 5. Juli 2005, 12:39 Uhr

glBitmap - Texturenloader

Warum das Ganze und wo kann ich sie finden?

Früher benutzte ich die glBMP, da sie so "schön" Objekt orientiert war. Leider hatte mein Grafiktreiber zu der Zeit einen Fehler bei der Auswahl des Texturformates. So führte es dazu, dass sobald ich eine Textur mit Alphakanal und dem Standard Texturformat an OpenGL übergab, er als Farbtiefe 16 Bit verwendete. Wenn ich ein Foto genommen hätte wäre mir das wahrscheinlich gar nicht aufgefallen aber dummerweise (oder glücklicherweise) hatte ich aber eine Textur mit einem weichem Farbverlauf. Wie zu erwarten war sah das natürlich alles ziemlich schrecklich aus. Bis dahin kann der Loader ja nichts dafür wenn in einem Treiber ein Fehler existiert. Nur das kuriose an dem Ganzen war, dass meine Textur in Wirklichkeit gar keinen Alphakanal haben durfte. Also habe ich mich auf die Suche nach der Ursache gemacht und bin auch prompt fündig geworden. Der Loader war der Meinung er müsse alle Texturen mit einem Alphakanal versehen ungeachtet der Tatsache ob sie nun einen hatten oder eben nicht. Das ist natürlich eine Sache mit der ich mich nicht abfinden wollte. Kurz darauf begann ich also eine Ableitung der glBMP Texturenklasse zu erstellen und dort musste ich mit Schrecken feststellen, dass die Rechte in der Klasse so ungünstig vergeben wurden, dass ich keine andere Wahl gehabt hätte als direkt die Unit zu editieren. Da ich ungern externe Quellen editiere habe ich für mich die andere Alternative entschieden. Was dabei rausgekommen ist sieht man ja jetzt.

Gefunden werden kann sie entweder im DGL-SDK oder auf der Webseite von Lossy eX.

Allgemeines und Features

Dieser Artikel geht nicht darauf ein was zu tun ist um in OpenGL eine Textur darstellen zu können. In ihm werde ich lediglich darauf eingehen was man tun kann und muss um mittels der glBitmap eine Textur laden zu können. Außerdem werden die verfügbaren Einstellungen und ihre Wirkungen erklärt. Für alles andere wäre es besser das Tutorial über Texturen durchzulesen.

Der wichtigste Unterschied zu den bestehenden Loadern bringe ich gleich einmal als erstes. Die glBitmap legt die Texturen anders im Speicher ab. Um zwar im speziellen meine ich damit die 2D Texturen. Bei bestehenden Loadern werden diese Texturen mit der untersten Zeile zu erst im Speicher abgelegt. In der glBitmap werden sie aber mit der obersten Zeile zu erst abgelegt. Man könnte auch sagen, dass sie bei den bestehenden Loadern auf dem Kopf stehen. Beide Möglichkeiten funktionieren und sind korrekt. Das Einzige was sich in diesem Zusammenhang ändert ist die Adressierung der Texturen. Sie müssen entsprechend geändert werden. Sollte das nicht möglich sein so besteht mit der Methode FlipVert die Möglichkeit die Textur zu spiegeln. Näheres in Abschnitt Texturen konfigurieren. Im Normalfall sollte das andere Verhalten nicht zu Problemen führen sondern die Arbeit damit vereinfachen und verständlicher gestalten.

Die glBitmap holt sich ihre Daten ausschließlich aus Streams. Das zugrundeliegende Format wird automatisch anhand des Inhaltes der Daten ausgewählt. Es besteht also kein Notwendigkeit daran, dass der Entwickler wissen muss um welches Format es sich in Wirklichkeit handelt.

unterstütze Formate

  • Windows Bitmaps (*.bmp) Wird Automatisch auf 24 Bit gesetzt sollte es kleiner sein.
  • JPEG (*.jpg)
  • TARGA (*.tga) komprimiert und unkomprimiert, 24 und 32 Bit. Ursprung unten links oder oben links
  • Portable Network Graphics (*.png) 24 Bit mit oder ohne Alphakanal. Es wird eine zusätzliche Bibliothek benötigt.

Es besteht auch die Möglichkeit die Bilddaten dynamisch generieren zu lassen oder sie aus einer Instanz der Klasse TBitmap zu laden. Diese Instanz muss dann aber als Pixelformat 24 oder 32 Bit haben.

Um PNGs laden zu können muss eine externe Bibliothek namens PNGImage eingebunden und das Define pngimage hinzugefügt werden. Alternativ dazu kann das Define auch in der glBitmap auskommentiert werden.

Texturen laden

Das Laden von Texturen kann auf 2 Weg geschehen. Bei Beiden ist aber zu beachten, dass OpenGL richtig initialisiert worden sein muss bevor man eine Textur erzeugen kann. Was noch sehr wichtig ist. Es sollte niemals eine Textur bei jedem Rendervorgang angelegt und freigegeben werden! Es ist viel effektiver, wenn man die Textur zu beginn lädt und so lange im Speicher behält bis sie nicht mehr benötigt wird. Wenn man sie erst später benötigt, dann kann man sie auch später noch nachladen aber auch dann sollte sie so lange wie nötig existieren.

Procedural

Hierbei genügt der Aufruf einer einzelnen Methode um eine Textur zu laden und diese an OpenGL zu übergeben. Allerdings auf Grund der Einfachheit steht nur das Laden aus Dateien oder Ressourcen zur Verfügung. Zusätzliche Funktionalität können dabei nicht benutzt werden. Generell empfehle ich immer die Objekt orientierte Variante.

var
  Texture: TGLuint;

LoadTexture('myTex\Wall.bmp', Texture, False); // Datei laden

Damit wird die Datei Wall.bmp geladen und die TextureID wird in die Variable Texture geschrieben. Wenn die Textur dann benötigt wird, dann muss man diese entsprechend mit glBindTexture binden und das benötigte Texturtarget mit glEnable aktivieren. Das Target ist üblicherweise GL_TEXTURE_2D. Wenn die Textur nicht mehr benötigt wird sollte sie mit glDeleteTextures auch wieder frei gegeben werden.

Objekt orientiert

Dieser Abschnitt beschäftigt sich ausschließlich mit den 2D Texturen. Aber grundsätzlich lässt sich alles hier erwähnte auch auf die 1D Texturen und die Cubemaps übertragen. Der Objekt orientierte Weg erfordert zwar eine wenig mehr Schreibarbeit aber nur so kann man den vollen Funktionsumfang der glBitmap genießen.

Als Erstes müssen wir eine Instanz der Texturenklasse definieren. Dies tun wir vorzugsweise als Member des Fensters auf dem wir zeichnen.

TForm1 = class(TForm)
  // Verschiene Elemente und Events des Fensters
private
  fTexture: TglBitmap2D; // Instanz unserer Textur
end;

Nun müssen wir der Textur noch Leben einhauchen.

procedure TForm1.FormShow(Sender: TObject);
begin
  // OpenGL Initialisieren

  fTexture := TglBitmap2D.Create; // Instanz der Klasse erstellen
  fTexture.LoadFromFile('myTex\Wall.bmp'); // Datei laden
  fTexture.GenTexture; // geladene Textur an OpenGL übergeben 
end;

Für Schreibfaule besteht auch noch die Möglichkeit in einem überladenen Konstruktor einem Dateinamen anzugeben.

  fTexture := TglBitmap2D.Create('myTex\Wall.bmp'); // Instanz erstellen und Datei laden
  fTexture.GenTexture; // geladene Textur an OpenGL übergeben 

In GenTexture wird im Normalfall immer überprüft ob die Textur eine gültige Größe hat. Sollte diese Überprüfung nicht gewünscht sein so kann man diese mit einem optionalen Parameter deaktivieren. Diese Überprüfung testet auch ob die Texturgröße eine Potenz von 2 ist (1, 2, 4, 8, 16 usw.) und wenn nicht ob solche Texturen von dem Rendercontex unterstützt werden.

  fTexture.GenTexture(False); // Es wird keine Prüfung der Texturgröße vorgenommen. 

Wenn kein Fehler aufgetreten ist und alles so funktioniert hat wie es sein sollte, dann kann die Textur benutzt werden.

  fTexture.Bind; // Textur und entsprechendes Target wird aktiviert.

Sollte es nicht gewünscht werden, dass auch gleichzeitig das Target aktiviert wird so muss Bind mit dem Parameter False aufgerufen werden. Damit wird nur noch die Textur gebunden.

Sollte man die Textur einmal nicht mehr benutzen wollen so genügt es entweder eine andere Textur zu binden oder Unbind von der Texturenklasse aufzurufen. Damit wird die Textur mit der TexturID 0 gebunden und das Target deaktiviert. Auch hier existiert wieder der Parameter der es verhindert, dass das Target deaktiviert wird.

Wenn die Anwendung beendet wird müssen wir auch hier unsere Textur wieder frei geben. Dies geschieht so.

  fTexture.Free; // Textur wieder frei geben.

Hierbei ist zu beachten, dass dies nach Möglichkeit vor dem Löschen des Rendercontex geschehen sollte, da so die Textur zu einem sinnvollem Zeitpunkt gelöscht werden kann. Wenn der Rendercontex nicht mehr existiert, so sollte die Textur auch nicht mehr existieren und wenn man dann noch einmal versucht sie zu löschen so könnte es zu Problemen führen. Bisher ist mir kein Fall bekannt aber ausschließen kann man es auch nicht. Also zu erst immer die Texturen frei geben und anschließend des Rendercontex löschen.

Das leidige Thema Alphakanal

Es gibt zwei Möglichkeiten einen Alphakanal in eine Textur zu integrieren. Der einfachste und schnellste Weg ist, wenn sich dieser Kanal von Beginn an in die Textur befindet. Dazu wird ein Grafikprogramm benötigt welches so etwas unterstützt und ein Dateiformat welches das Speichern eines Alphakanals ermöglicht. Ein solches Programm ist zum Beispiel Gimp. Also mögliche Dateiformate kämen TGAs und PNGs in Frage. Alle anderen Formate unterstützen keinen Alphakanal. Sollte man sich für diesen Weg entscheiden so genügt es dann ganz normal die Textur zu laden und zu benutzen und schon besitzen wir einen funktionierenden Alphakanal. Ich persönlich empfehle bis auf wenige Ausnahmen immer diesen Weg.

Nichts desto trotz habe ich ja gesagt, dass es noch eine Möglichkeit gibt. Bei diesem Weg haben wir Alphakanal und RGB Daten in zwei getrennten Bildern vorliegen. Aber wie bekommen wir das jetzt in ein Bild? Dafür bietet die glBitmap die Möglichkeit einen Alphakanal aus einem separaten Bild zu laden. Zu Beginn benötigen wir jedoch wieder eine Instanz der Klasse und es müssen schon normale Bilddaten geladen worden sein. Und ganz Wichtig damit es überhaupt funktioniert. Der Aufruf muss vor GenTexture stattfinden, da sonst die Textur bereits an OpenGL übergeben wurde.

fTexture.AddAlphaFromFile('myTex\Wall_alpha.bmp'); // Alphakanal aus Datei laden

Das war alles nun sind wir im Besitz einer Textur mit einem Alphakanal. Es besteht unter anderem auch die Möglichkeit den Alphakanal aus verschiedenen anderen Quellen zu laden (Resourcen, Streams, TBitmap). Zusätzlich dazu kann man den Kanal aber auch dynamisch erstellen. Dafür existiert die Methode AddAlphaFromFunc. Die Findigen unter euch habe aber bestimmt schon herausgefunden, dass man bei alle Methoden solch eine Function übergeben kann. Näheres dazu findet ihr im Abschnitt e = mc²

Etwas was häufiger gebraucht wird habe ich von Hause aus schon implementiert. Und zwar das Erstellen eines Alphakanals anhand einer Farbe.

fTexture.AddAlphaFromColorKey(Red, Green, Blue, Deviation); // Alphakanal anhand einer Farbe hinzufügen

Dabei wird nur auf vollkommen durchlässig und nicht durchlässig unterschieden. Alle Pixel die die angegebene Farbe besitzen werden dabei als 0 (nicht Sichtbar) in den Alphakanal geschrieben alle anderen erhalten 255 (Sichtbar). Die Deviation bestimmt eine Abweichung um der die Farben abweichen dürfen um trotzdem noch als Transparent erkannt zu werden. Speziell für JPEGs ist das nicht unwichtig, da die Bilder beim Komprimieren leicht abgewandelt werden.

Damit die OpenGL Primitiven jetzt transparent dargestellt werden muss der Alphatest oder das Blending aktiviert sein. Sonst nützt einem der Alphakanal nicht sonderlich viel.

Entfernen kann man einen Alphakanal natürlich auch. Dazu genügt der Aufruf von RemoveAlpha. Dieser muss natürlich nicht mit der glBitmap erstellt worden sein. Er kann auch aus einer Datei geladen worden sein.

fTexture.RemoveAlpha; // entfernt den Alphakanal

Texturen konfigurieren

Hier beginnt jetzt der Teil der für die Meisten recht wichtig sein dürfte. Bisher haben wir ja nur Daten geladen und diese an OpenGL übergeben. Nun wollen wir aber ein wenig an den Einstellungen rumschrauben und sehen was wir daraus machen können.

Folgende Einstellungsmöglichkeiten verändern ausschließlich nur das Verhalten der glBitmap. Sie haben keinen Einfluss auf die resultierende Textur.

DeleteTextureOnFree ist vom Typ Boolean und gibt an ob beim Freigeben der Klasse die Texture auch freigegeben wird oder ob weiter existieren darf. Standard wird sie mit freigegeben.

FreeDataAfterGenTexture ist vom Typ Boolean und gibt an ob die Bilddaten nach dem Erzeugen der Textur aus dem Hauptspeicher entfernt werden sollen oder nicht. Im Normalfall werden sie das auch.

Target ist vom Typ Cardinal und bestimmt das OpenGL Target. Wird von den Klassen auf den Standardwert gesetzt und muss nur dann auf einen anderen Wert gesetzt werden, wenn ein anderes Target verwendet werden soll. Zum Beispiel bei der Verwendung von Texture_rectangle. Es sollte aber immer direkt nach dem erstellen gesetzt werden, da die Textur sonst unbrauchbar gemacht werden könnte.

Format ist ein Aufzählungstyp und er erlaubt 4 Werte.

tfDefault Dabei wird das Standardformat des Grafikkartentreibers benutzt. Je nach eingestellter Qualität kann dies 16 oder 32 Bit sein.</td>
tf16Bit Hier wird die Textur in 16Bit abgelegt.
tf32Bit Hier wird die Textur in 32Bit abgelegt.
tfCompressed Bei diesem Format wird zu erst geschaut ob eines der folgenden Kriterien erfüllt ist. Extension GL_ARB_texture_compression, OpenGL Version 1.3 oder die Extension GL_EXT_texture_compression_s3tc. Sollten alle 3 Möglichkeiten nicht funktionieren so wird das Standardformat vom Grafikkartentreiber benutzt.

MipMap ist ebenfalls ein Aufzählungstyp und er erlaubt 3 Werte.

mmNone Wie man vermuten könnte werden hierbei keine Mipmaps erstellt.
mmMipmap Ähnlich wie bei komprimierten Texturen werden zu erst verschiedene neue Möglichkeiten überprüft. Ab OpenGL Version 1.4 oder mit der Extension GL_SGIS_generate_mipmap unterstützt die Grafikkarte ein generieren der Mipmaps. Das ist natürlich wesentlich schneller als der Weg ausschließlich über die Software. Sollte es keinen anderen Weg geben so werden die Mipmaps mittels der GLU generiert.
mmMipmapGlu In diesem Falle werden die Mipmaps ausschließlich mit der GLU erstellt.

Mithilfe der Methoden SetFilter und SetWrap können das Filterverhalten und das Beschränken der Texturkoordinaten angepasst werden. Eine genaue Auflistung möglicher Werte findet man beim Artikel zu glTexParameter. Das Aufrufen der Methoden ist überall dort möglich wo auch gTexParameter gültig ist. Mögliche Fehlerhafte Werte werden automatisch auf die vorhandene Textur angepasst. So wird bei SetFilter niemals ein MipMap bei die Filterung eingestellt wenn keine MipMaps erzeugt wurden. Das würde sonst dazu führen, dass die Textur nicht dargestellt werden könnte.

FlipVert und FlipHorz sind Methoden die nichts anderes machen als das Bild entlang der entsprechenden Richtung (Vertikal und Horizontal) zu spiegeln. Dies wäre auch mit der Funktionsschnittstelle möglich aber besonders bei FlipVert wäre es unpraktisch da wir so nicht die komplette Zeile am Stück kopieren könnten, sondern nur Pixel für Pixel.

Zum Invertieren des Bildes genügt es die Methode Invert aufzurufen. Sie bekommt zwei boolsche Parameter mit denen man steuern kann was man invertieren möchte. Einen für RGB und einen für den Alphakanal.

Die Methode ToNormalMap wurde implementiert um aus einer 2D Textur eine NormalMap zu erzeugen. Er funktioniert ähnlich wie das NormalMapPlugin für Gimp. Die Methode bekommt 3 Parameter. Der Erste gibt an mit welcher Methode die Normalmap berechnet werden soll. Mögliche Werte hierfür sind

nm4Samples</td> Hierbei werden die jeweils geradlinigen benachbarten Pixel zu gleichen Teilen in die Berechnung einbezogen. (Oben, Unten Recht und Links). Diese Methode ist die einfachste, schnellste und empfindlichste gegenüber Farbvariationen.
nmSobel Mit dieser Methode werden fließen benachbarten Pixel zu gleichen Teilen in die Berechnung ein.
nm3x3 Bei diese Methode fließen auch alle benachbarten Pixel in die Berechnung ein. Die diagonalen Pixel werden Doppelt gewertet. Die NormalMap wirkt so ruhiger und nicht so anfällig gegen kleine Farbveränderungen.
nm5x5 Diese Methode benutzt eine Matrix von 5*5 Pixeln um Ihre Berechnungen durch zu führen. Je weiter die Punkte vom Mittelpunkt entfernt sind so geringer wird deren Einfluss.

Der zweite Parameter gibt eine Skalierung an. Mögliche Werte hierfür liegen im Bereich von -100 bis 100. Bei einer Skalierung von 0 würde sich die Normalmap Wohlgefallen auflösen, da sie dann praktisch eine gerade Fläche darstellen würde. Die Textur dürfte dann außergewöhnlich gleichfarbig blau werden.
Mit dem dritten und letzten Parameter kann man die Berechnung der Normalmap von den RGB Werten auf den Alphakanal verschieben. Die Berechnung zieht sich dann nicht mehr die RGB Werte sondern den Alphakanal zu Rate. Der genaue Nutzen davon liegt zwar momentan selber vor mir verborgen aber wer weiß wozu man so etwas später noch gebrauchen kann. ;-)

e = mc²

Hierbei handelt es sich um eine Möglichkeit die Texturdaten zu manipulieren. Die Schnittstelle wurde so gestaltet, dass sie sehr flexibel und typisiert ist. Sprich, dass man immer genau weiß worum es sich handelt und welche Felder unterstützt werden. Eine einfache Handhabung und eine gute Ausführungsgeschwindigkeit waren natürlich auch sehr wichtige Punkte dabei. Finden kann man die Funktionsschnittstelle beim Hinzufügen des Alphakanals oder immer wenn man möchte durch die Methode AddFunc. Es gibt außerdem noch eine Reihe von Funktionen, die intern die Schnittstelle benutzen. Das wären zum Beispiel die Funktionen Invert, AddAlphaFromColokey, ToNormalMap und FillWithColor. Die Klasse TglBitmapNormalMap generiert ihre Texturen zum Beispiel mit der Schnittstelle.

Die Arbeitsweise der Funktionsschnittstelle ist recht einfach. Es wird nichts anderes gemacht als 1 Mal Zeile für Zeile den Pointer eines jeden Pixels an eine Funktion zu Übergeben und darauf zu vertrauen, dass sie das Richtige tut. Um die Ausführung zu Beschleunigen wird ausschließlich mit Pointern gearbeitet. Die Funktionen bekommen die Instanz des Objektes, die Größe, die Position, das Quellpixel, das Zielpixel und einen Pointer auf benutzerdefinierte Daten übergeben. Das ist natürlich eine ganze Menge Holz was da an Daten übergeben aber das ist alles notwendig um die gewünschte Flexibilität erreichen zu können.

Betrachten wir das ganze mal an einem Beispiel. Und zwar dem Aufruf von Invert. Zu erst einmal der Aufruf der Funktion.

if ((UseRGB) or (UseAlpha)) 
  then AddFunc(glBitmapInvertFunc, False, Pointer(Integer(UseAlpha and HasAlpha) shl 1 or Integer(UseRGB)));

In Zeile 1 handelt es sich lediglich um eine kleine Sicherheit. Es bringt ja nichts, wenn man eine Funktion ausführen möchte die keinen Nutzen hat. Zeile 2 Ruft die Methode AddFunc auf. Diese Methode bekommt als erstes unsere Funktion übergeben. Zu deren Aufbau komme ich gleich. Als Zweites bekommt AddFunc gesagt ob sie eine Kopie des Bildes erstellen soll. Für pixelübergreifende Funktionen ist dies unerlässlich, da man sich sonst seine noch benötigten Daten überschreiben würde. Der dritte Wert ist ein Pointer der zusätzliche Informationen hält. In diesem Falle handelt es sich lediglich um ein Bitset bei dem Bit 2 den Alphakanal und Bit 1 die RGB Werte repräsentiert. Es könnte sich dabei aber auch um einen Pointer auf ein Record handeln.

procedure glBitmapInvertFunc(Sender : TglBitmap; const Position, Size: TglBitmapPixelPosition; 
  const Source, Dest: TglBitmapPixelData; const Data: Pointer);
begin
  if (Integer(Data) and $1 > 0) then begin
    Dest.ptRed^   := not Dest.ptRed^;
    Dest.ptGreen^ := not Dest.ptGreen^;
    Dest.ptBlue^  := not Dest.ptBlue^;
  end;

  if (Integer(Data) and $2 > 0) then begin
    Dest.ptAlpha^ := not Dest.ptAlpha^;
  end;
end;

Position und Size beinhalten die Aktuelle Position des Pixels und die Größe des Bildes. Das Record TglBitmapPixelPosition beinhält 4 Werte. Fields, X, Y, Z. Fields beschreibt welche Felder überhaupt zur Verfügung stehen. Mögliche Werte Dafür sind ffX, ffY und ffZ. Wofür X, Y und Z stehen erkläre ich jetzt mal nicht, dass sollte jedem klar sein. Bei einem 2D Bitmap sind üblicherweise die Felder X und Y mit leben gefüllt. Das Feld Z liegt brach.

Source und Dest beinhalten die Pixeldaten. Die Records sind ziemlich genau so aufgebaut wie das für die Position. Es gibt einen Wert Fields. Mögliche Werte für ihn sind ffRed, ffGreen, ffBlue und ffAlpha. Diese Felder sind anders als bei bei Position keine richtigen Werte sondern Pointer die Direkt auf die Farbwerte in den Bildern zeigen. Es gibt deswegen Source und Dest, da es ja die Möglichkeit gibt eine Kopie des Bildes zu erstellen. Sollte ohne Kopie gearbeitet werden so sind Source und Dest gleich. Bei hinzufügen eines Alphakanals bezieht sich Source auf die Pixeldaten im dem Quellbild und Dest wie gewohnt auf die Daten im Zielbild.

In dem obigen Beispiel werden die Bits des Parameters Data ausgewertet und die entsprechenden Kanäle negiert. Der Komplexibilität sind kaum Grenzen gesetzt. Alle Wissensdurstigen unter euch sollten sich dann auch mal die Methode ToNormalMap anschauen. Dort werden zwei Funktionen aufgerufen. In der Ersten werden Daten gesammelt mit denen in der Zweiten die Normalmap berechnet wird. Oder auch die Klasse TglBitmapNormalMap. In dieser wird eine generelle Berechnungsroutine aufgerufen. Diese bekommt aber für jede Fläche der Map eine andere interne Methode übergeben. Mit dieser werden dann die Werte für jedes Pixel gefüllt.

Noch ein Wort für alle Geschwindigkeitsfanatiker. Ich hatte ein mal spaßeshalber die Methode FlipHorz nachgebaut und die Geschwindigkeit gemessen. Das Ergebnis hat mich selber sehr angenehm überrascht. Die Hardcodierte super optimierte Methode benötigte bei 300 Aufrufen, auf ein 512x512 Pixel großes Bild ca. 5 Sekunden. Die Methode mit dem Funktionsaufruf benötigte Lediglich 300 ms länger. Ich selber hätte eher mit dem Doppelten der Zeit gerechnet.

Cubemaps mit glBitmap

Das einzige worin sich CubeMaps von 2D Texturen unterscheidet ist das Laden. Aber das ist auch nicht viel komplizierter als bei 2D Texturen. Die Handhabung mit den Texturen (Binden, Frei geben) ist wie bei 2D Texturen weswegen ich das auch nicht noch einmal extra wiederholen werde.

Procedural

Wie auch bei den 2D Texturen haben wir die Auswahl zwischen zwei möglichen Wegen. Der procedurale Weg unterscheidet sich lediglich in der Anzahl der angegebenen Texturen. Das sind diesmal natürlich ein paar mehr.

var
  Texture: TGLuint;

// Cubemaps laden
LoadCubeMap('xpos.bmp', 'xneg.bmp', 'ypos.bmp', 'yneg.bmp', 'zpos.bmp', 'zneg.bmp', Texture, False);

Die ersten 6 Parameter geben die jeweils 6 Texturen an. Die Namen der Parameter geben auch gleichzeitig deren Ziel an. Also immer schön auf die Reihenfolge achten. In dem Siebten wird die Textur ID geschrieben und der Achte gibt an ob aus Resourcen geladen werden soll oder nicht.

Objekt orientiert

Der Objekt orientierte Weg gestaltet sich dieses mal ein wenig schreibintensiver. Es müssen ja auch schließlich 6 Texturen geladen werden. Aber das kann man auch praktischerweise in eine Schleife packen. Das praktische daran ist, dass wir alles mit nur einer Klasseninstanz lösen können (und auch müssen).

fCubeMap := TglBitmapCubeMap.Create; // Instanz der Klasse erstellen
fCubeMap.LoadFromFile('xpos.jpg'); // Datei Laden

Die Textur wird normal angelegt und es wird wie gewöhnt ein Bild in den Speicher der Klasse geladen. Dieses Bild verhält sich wie jedes andere Bild. Es kann also wie gewöhnt manipuliert werden. Zum Beispiel durch die Funktionsschnittstelle.

fCubeMap.GenerateCubeMap(GL_TEXTURE_CUBE_MAP_POSITIVE_X); // Einzelnen Teil übergeben

Wenn die Textur also bereit zum Übergeben ist, dann müssen wir GenerateCubeMap mit dem entsprechenden Teil der Cubemap aufrufen. In diesem Falle ist es die positive X Achse. Das müssen wir dann natürlich noch mit allen anderen Maps machen. Also Bild laden, evtl bearbeiten und dann an OpenGL übergeben. Die Reihenfolge der Maps spielt hierbei keine Rolle. Wichtig ist nur, dass sie die selbe Auflösung und das selbe Format haben. Die Methode GenTexture steht bei dieser Klasse nicht zur Verfügung, da beim ersten Aufruf von GenerateCubeMap automatisch die OpenGL Textur erzeugt wird. Die CubeMap ist nur dann einsetzbar, wenn alle Maps erfolgreich mit Daten versorgt wurden.

Beim Binden der CubeMap existiert ein zusätzlicher Parameter mit dem man die Aktivierung der automatische Generation der Texturkoordinaten deaktivieren kann. Standard ist dieses eingeschalten. Ähnlich wie beim Binden einer Textur. Dort kann man verhindern, dass das Target aktiviert wird.

Normalmaps mit glBitmap

NormalMaps sind nichts anderes als Spezielle Cubemaps. Sie dienen dazu um Per Pixel Normale auch auf Systemen ohne Shaderhardware zu ermöglichen. Benutzt werden kann das zum Beispiel bei Bumpmapping mit GL_ARB_texture_env_dot3.

Procedural

Wie auch bei den 2D Texturen und Cubemaps haben wir die Auswahl zwischen zwei möglichen Wegen. Da diese Texturen dynamisch generiert werden fällt die Anzahl der Parameter ziemlich gering aus. Sie besteht lediglich aus einer Texturgröße. In diesem Falle haben die resultierenden Texturen eine Größe von 32x32 Pixeln. Der zweite Parameter ist ein altbekannter von uns. Er ermöglicht es uns eine TexturID zu empfangen. Auf die Handhabung mit dieser ID gehe ich nicht noch einmal ein, da das oberhalb schon oft genug besprochen wurde.

var
  Texture: TGLuint;

// Normalmaps erzeugen
LoadNormalMap(32, Texture);

Objekt orientiert

Dieser Weg ist etwa genau so schreibintensiv wie der Procedurale. Hierbei wird eine Normalmap erstellt bei der einzelnen Texturen eine Größe von 32x32 Pixeln haben.

fNormalMap := TglBitmapNormalMap.Create; // Instanz der Klasse erstellen
fNormalMap.GenerateNormalMap(32); // Normalmap erzeugen

Erstaunlich aber wahr. Das war bereits alles. Von nun an kann die Textur wie jede andere CubeMap auch verwendet werden.

Bekannte Probleme und Einschränkungen

Vor einer Weile wurde im Forum die Frage gestellt warum die glBitmap beim Laden eines BMPs einen "Stream Read Error" produziert. Da das Bitmap nun mal ein sehr einfaches Format ist hat mich das auch sehr stark verwundert. Nach langem hin und her Rätseln kam ich dann dennoch darauf was das Problem war. Es lag daran, dass das Bitmap mit MS Paint abgespeichert wurde. Dieser war aber der Meinung er müsse im Bitmapheader eine Größe angeben die 2 Bytes größer war als die eigentlichen Daten. Diese 2 Bytes wurden auch aufgefüllt aber leider ist das TBitmap, welches ich zum Laden verwende, nicht so tolerant um darüber hinweg zu sehen. Und so bekommt man einen Fehler beim Laden. Den selben Fehler würde man auch bekommen wenn man das Bild in einem TImage anzeigen wollte.

Abhelfen kann man dies in dem man ein alternatives Grafikprogramm (Gimp) verwendet oder das Bild einmal mit dem Bildbetrachter IrfanView öffnet und abspeichert.

Es muss nicht bei jedem mit MS Paint abgespeicherten Bild auftreten.