Glbitmap loader
Inhaltsverzeichnis
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. Aber Vorsicht. Beide Quellen beinhalten noch nicht die neuste Version der glBitmap. Mitunter können einige Features noch nicht unterstützt werden oder sie haben andere Parameter.
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 siegeln. 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) 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, dass die Bilddaten dynamisch generiert oder mittels einer Instanz von TBitmap vorliegen. Diese Instanz muss dann aber als Pixelformat 24 oder 32 Bit haben.
Um PNG's 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 wird und entsprechendes Target gleich 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 TGA's und PNG's 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 JPEG's 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.
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. |
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.
e = mc²
{ Funktionsschnittstelle, Arbeitsweise, Möglichkeiten, Beispiele }
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 LoadTexture('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. Aus Übersichtsgründen erkläre ich nur das Laden eines Teiles der Cubemap. Der Rest sollte ja selbsterklärend sein.
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. 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.
Beim Binden der CubeMap existiert ein zusätzlicher Parameter mit dem man die automatische Generation der Texturkoordinaten aktivieren kann. Standard ist dieses eingeschalten wodurch ein zusätzlicher Aufruf entfällt.
Bekannte Probleme und Einschränkungen
Vor einer Weile wurde im Forum die Frage gestellt warum die glBitmap beim Laden eines BMP's 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 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 über 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.