10 Bitmaps und Texturen

10.1 Bitmaps

OpenGL unterscheidet in erster Linie zwischen Bitmaps (= 1 Bit pro Pixel) und Images (= mehrere Farben pro Pixel) Bitmaps werden üblicherweise als Fonts oder dergleichen verwendet, und verwenden einige einfache Befehle:

glRasterPos{234}{sifd}{v} (TYPE x, TYPE y, TYPE z, TYPE w)
// Position festlegen, an der die nächste Bitmap ausgegeben wird

glBitmap   (Glsizei width, Glsizei height, Glfloat xbo, Glfloat ybo,
 		Glfloat xbi, Glfloat ybi, const Glubyte *bitmap);
// Ausgeben der Bitmap [bitmap]
Der Befehl glBitmap gibt die übergebene Bitmap an der durch glRasterPos festgelegten Position + xbo in x-Richtung und ybo in der y-Richtung aus. Danach wird die Aktuelle Rasterposition um xbi und ybi erhöht.

Die Position die mit glRasterPos festgelegt wird, wird genauso wie alle anderen Koordinaten transformiert. Die Breite und Höhe der Bitmap bleibt aber unangetastet. (D.h. mehrere Bitmaps in großer Entfernung rücken durch die Projektion zusammen, werden aber nicht verkleinert).

Bitmaps sind im Speicher als unsigned Bytes abgelegt. Darum haben Bitmaps im Speicher in der X-Auflösung immer ein Vielfaches von 8. Der Parameter width von glBitmap muß aber nicht ein vielfaches von 8 sein.

10.2 Images

Images sind sehr ähnlich zu Bitmaps, wird jeder Pixel nicht nur durch ein einzelnes Bit sondern durch mehrere Bytes beschrieben. Es sind also ganz stinknormale Bilder (Pixelarrays) wie wir sie aus der Computergraphik bestens kennen. Images können einfach als Bilder ausgegeben werden, aber auch als Texturen verwendet werden. Darum werden wir uns etwas näher mit ihnen befassen.

OpenGL hat zwei Grundlegende Befehle die mit Images operieren:

glReadPixels (GLint x, GLint y, Glsizei width, Glsizei height, GLenum format,
  		  GLenum type, Glvoid *pixels);
// Liest Pixeldaten aus dem Framebuffer in konventionellen Speicher

glDrawPixels (Glsizei width, Glsizei height, GLenum format,
 		  GLenum type, const Glvoid *pixels);
// Schreibt Pixeldaten an der aktuellen Position in den Framebuffer

Format kann dabei folgende Werte annehmen:

Type kann folgende Werte annehmen:

Den Zoom-Faktor bei der Image Ausgabe läßt sich mit dem Befehl glPixelZoom (GLfloat zoomx, GLfloat zoomy) festlegen. Es sind auch nicht negative Werte zulässig. Images werden aber nicht gefiltert. Pixel werden entweder weg-gelassen, oder doppelt gezeichnet.

Es sei der Vollständigkeit halber noch erwähnt, daß es mehr oder weniger komplizierte Kommandos gibt, die festlegen in welcher Form die Image Daten im Speicher liegen (rgb, bgr, gbr, etc. ) und ob sie vor der Ausgabe noch behandelt wer-den sollen ( r, g, b, a - scale, r, g, b, a - bias u.ä. ) oder gemappt werden sollen ( map r to a, etc.)

Mit all diesen Optionen und der Möglichkeit Images in jedem beliebigen Datentyp anzulegen, kann man wirklich jedes Bildformat egal ob auf einer Intel-Plattform generiert, in little oder big Endian, oder sonstwas direkt verarbeiten.

10.3 Texturen & Texturemapping

Bevor man ein Objekt texturieren kann, muß man zuerst die Textur angeben, die man verwenden will. Eine Textur ist im Grunde ein Image wie wir es vorhin kennengelernt haben.

Der Befehl dazu lautet:

glTexImage2D (GLenum target, GLint level, GLint components,
              GLsizei width, GLsizei height, GLint border,
              GLenum format, GLenum type, const GLvoid *pixels);

Bedeutung der Parameter:

target für spätere Erweiterungen vorgesehen, muß auf GL_TEXTURE_2D gesetzt werden.

Level gibt den level (bei der Verwendung von MipMaps) an

components Eine Zahl zwischen 1 und 4 die ein Parameter für Modulation & Blending ist.

width, height Breite, Höhe. Beide Werte müssen von der Form 2m+2*border sein.

Border Breite des Randes in Pixel

format, type Die selbe Bedeutung wie bei einem Image

pixels Die Image Daten

OpenGL ist ja eine State machine, also muß man jedesmal wenn man zwischen Texturen umschalten will, einen Aufruf von glTexImage2d absetzen. Alle Polygone werden danach mit dieser Textur versehen.

10.3.1 Texturrand

Wie man sieht, müssen die Dimensionen einer Textur 2er Potenzen sein (vom Rand abgesehen). Mit dem Rand hat es folgendes auf sich: Je nach OpenGL Implementierung gibt es ein Größenlimit der Texturen, (mindestens 64x64 bzw. 66x66 mit Rand) darum kommt es mitunter vor, daß man Texturen aufteilen und aus mehreren kleinen zusammenstük-keln muß. Wenn die Texturen aber gefiltert werden, würde man die Kanten, bei der die Teiltexturen aufeinanderstoßen sehen. Darum gibt’s die Möglichkeit der Textur einen (üblicherweise 1 Pixel breiten) Rand zu geben, in den dann die erste Pixelreihe bzw. -zeile kopiert wird. Der Rand wird nicht ausgegeben, beim Filtern aber berücksichtigt.

10.3.2 MipMapping & Filtering

OpenGL unterstützt die Verwendung von MipMaps. Dabei muß man von jeder Textur mehrerer Exemplare, die vorge-filtert sind angeben. Die erste Textur hat die Originalgröße, die zweite Textur ist in der Breite und Höhe halbiert, hat also nur mehr ein viertel der Größe, usw. Bis man schließlich eine Textur hat, die nur noch 1 Pixel groß ist.

Der Sinn des MipMappings ist folgender: Beim Texturieren einer Fläche, muß für jeden Pixel am Bildschirm entschieden werden, welcher Pixel aus der Textur verwendet wird.

Ist die Fläche am Bildschirm kleiner als die Textur im Speicher, müssen Pixel übersprungen bzw. ausgelassen werden. Dadurch kommt es zu Aliasing Effekten. Um das zu verhindern, verwendet man MipMaps. Man hat nun jede Textur in verschiedenen Größen (die eben nicht nur durch auslassen von Pixeln, sondern durch eine Filteroperation (z.B. Arithme-tisches Mittel) entstanden sind ) und entscheidet beim Texturieren aus welcher Texturgröße man den nächsten Pixel nimmt. So kommt man zu sehr guten Ergebnissen.

Hat man nun eine Textur in allen benötigten Größen, kann man diese durch wiederholte Aufrufe von glTexImage2D mit aufsteigenden Werten von level angeben.

Man kann aber auch einen sehr bequemen Befehl aus der Utility Library verwenden:

int gluBuild2Dmipmaps (GLenum target, GLint components,
                       Glint width, GLint height,
                       GLenum format, GLenum type, void* data);

Die Parameter haben die selbe Bedeutung wie bei glTexImage2D. Der Befehl baut sich aus dem angegebenen Bild alle MipMap - Stufen und ruft intern mehrmals glTexImage2D auf.

Um die Methode fürs Texturfiltering anzugeben steht folgender Befehl zur Verfügung:

void glTexParameter{if}{v} (GLenum target, GLenum pname, TYPE param);

target soll den Wert GL_TEXTURE_2D haben.

Zulässige Werte für pname bzw. param:

pname param
GL_TEXTURE_MAG_FILTER (Vergrößerungsfilter) GL_NEAREST
GL_LINEAR
GL_TEXTURE_MIN_FILTER (Verkleinerungsfilter) GL_NEAREST, GL_LINEAR, GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_LINEAR
GL_TEXTURE_WRAP_S GL_CLAMPGL_REPEAT
GL_TEXTURE_WRAP_T GL_CLAMPGL_REPEAT

‘Nearest’ bedeutet hier immer, daß kein Filtering vorgenommen wird, Linear ist eine lineare Filterung.

Die Parameter WRAP_S und WRAP_T geben an, was passieren soll wenn, das Ende der Textur in X- bzw. Y- Richtung erreicht ist. Bei REPEAT wird die Textur solange wiederholt bis der Rand des Polygons erreicht ist, bei CLAMP wird die letzte Texturzeile bzw. -spalte wiederholt:

TexturRepeatClamp

10.3.3 Zuweisen von Texturkoordinaten

Nachdem wir nun all das über Texturen wissen, wäre es natürlich mal ganz interessant zu wissen, wie man überhaupt die Texturkoordinaten festlegt.
Dadurch wird kontrolliert wie / wo die Textur auf der Fläche liegt.

void glTexCoord{1234}{sifd}{v}(TYPE coords);

Dieses Kommando wird genauso wie der Befehl glColor pro Vertex zwischen glBegin und glEnd eingesetzt.

glBegin(GL_TRIANGLES);
	glTexCoord2f(0.0f, 0.0f);
	glVertex2f(100.0f, 50.0f);
glTexCoord2f(0.0f, 1.0f);
	glVertex2f(450.0f, 400.0f);
	glTexCoord2f(1.0f, 1.0f);
	glVertex2f(450.0f, 50.0f);
glEnd();

Die Werte für die Koordinaten können beliebig sein. 0.0 / 0.0 ist die linke rechte obere Ecke der Textur,. 1.0 / 1.0 ist die rechte untere Ecke. Gibt man größere Werte als 1.0 an, wird die Textur entweder wiederholt oder geclampt.