Spielwiese/Tutorial 4/grey

Aus DGL Wiki
Wechseln zu: Navigation, Suche

Texturen - Wie kommt die Tapete an die Wand?

Vorwort

Willkommen in der Welt der Bilder! In diesem Tutorial wirst du erfahren, wie man auf ein Polygon (Dreieck, Viereck usw) eine Textur zeichnen kann. Angefangen mit dem Laden über das einfache Platzieren kommen wir schlussendlich dann dazu, wie man nur einzelne Bereiche einer Textur darstellen kann (dies ist das sog. UV-Mapping).

Wofür denn überhaupt Texturieren? Nach den letzen Tutorials werden sicher einige den Wunsch verspürt haben mehr als nur abstrakte Kunst mit Farben und Farbverläufen darzustellen - dem Wunsch soll hiermit nachgekommen werden :D

Genug geschwafelt, fangen wir mal endlich an.

Crash-Kurs im Tapezieren

Vorbereitung

Nun, am Besten fangen wir mit einem möglichst praktischen Beispiel an: Stell dir vor dein Polygon sei deine Wand und die Textur ist deine Tapete. Wer sich nun denkt "Yeah, ich hab ne 12 Megapixel Cam - das gibt geile Texturen", dem kann ich nur zustimmen und dabei den Kopf schütteln. Klar ist so nen Bild ne geile Textur - wenn du nur eine Textur verwenden willst, dann geht das vielleicht noch, aber sobald du einen eigenen Level erstellen möchtest, wird dir auffallen wie gigantisch auch der Speicherverbrauch und wie schlecht die Performance davon ist. Also heißt es erstmal zurecht schneiden. Was Texturen angeht ist OpenGL leider wählerisch - es nimmt erstmal nur Texturen im Format 2^n x 2^n also z.B. 16x16, 64x64 aber auch 16x1024 oder 32x64 usw.

Mit unserem überdimensionierten Tapetenmesser haben wir nun unsere Tapete (Textur) in die richtige Form gebracht - Jetzt gilt es sie mit Kleister zu versehen (Laden) und auf den Tapeziertisch (Grafikspeicher) zu legen, damit wir sie dann auch schnell an die Wand bekommen.


Laden der Textur

Okay, wir haben unsere Textur jetzt zurecht geschnitten und auf unserer Festplatte abgelegt - doch wie kommt sie jetzt von da in den Grafikspeicher? Wer mal mit den Standard I/O-Methoden von Delphi rumgespielt hat weiß, dass es nicht immer einfach ist an die gewünschten Daten in einer Datei zu kommen - daher will ich jetzt auch niemanden damit quälen, denn wir haben ja die SDL, welche bereits fertige Methoden zum Einlesen von Bilddaten besitzt. Nehmen wir uns jetzt einfach ein frisches ungebrauchtes easySDL Template und spielen damit etwas rum. Als erstes machen wir bekannt, das wir eine neue Prozedur haben (und Erstellen in dem Zug gleich auch die Variable in der die Textur gespeichert werden soll):

type
  TMyProgram=class(TEasySDL)
    texture : gluInt;              //Hier wird die Textur gespeichert  
    procedure DrawScene;override;  //Hier wird die Scene gezeichnet
    procedure LoadTexture;         //Hier wird die Texur geladen
  end;



Und jetzt laden wir die Textur:

procedure TMyProgram.LoadTexture;
var
  tex : PSDL_Surface;              //Zwischenspeicher für die Textur
begin
  tex := IMG_Load('./wall.jpg'); //Laden der Textur
  if assigned(tex) then
  begin
    glGenTextures(1, @texture);   {1}      
    glBindTexture(GL_TEXTURE_2D, texture); {2}

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);{3}
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    
    glTexImage2D(GL_TEXTURE_2D, 0, 3, tex^.w, tex^.h,0, GL_RGB, GL_UNSIGNED_BYTE, tex^.pixels); {4}
    // Achtung! Einige Bildformate erwarten statt GL_RGB, GL_BGR. Diese Konstante fehlt in den Standard-Headern

    SDL_FreeSurface(tex);
  end;
end;

Obwohl ich dich jetzt nicht für den DAU halte erkläre ich grade mal den Programmablauf in Worten:

  1. Zwischenspeicher für die Textur erstellen
  2. Bilddatei in den Zwischenspeicher laden
  3. OpenGL sagen, das wir eine Textur erstellen möchten (1), wobei der erste Parameter für die Anzahl der zu erstellenden Texturen steht und der Zweite für die Adresse der Variable.
  4. Dann teilen wir OpenGL mit (2), dass sich von nun an alle Änderungen und Anweisungen, die sich auf Texturen beziehen auf die Textur texure beziehen, sowie das diese eine 2D-Textur ist.
  5. Dann setzen wir noch ein paar Texturfilter (3) (näheres darüber gibts im Tutorial Blending).
  6. Jetzt endlich wird die Textur in den Grafikspeicher eingeladen. Der erste Parameter steht für den Typ der Textur. Die Dimension des Typs muss hier mit der des Befehls übereinstimmen (glTexImage2D erlaubt also nur GL_TEXTURE_2D). Der zweite Parameter gibt die Nummer des Level of Detail (LoD) an. Für den Anfang reicht hier der Level 0. Der dritte Parameter gibt an, wie viele Farbkomponenten in dem Bild enthalten sind (1-4). Die zwei folgenden Parameter übermitteln OpenGL die Breite und die Höhe des Bildes. Der sechste Parameter gibt die Breite des Rahmens an. Im siebenten Parameter wird das Format verlangt, in welcher Reihenfolge die einzelnen Farbkomponenten gespeichert sind. Der Typ, der einzelnen Farbwerte muss im 8. Parameter angegeben werden. Letztendlich müssen im 9. Parameter nur noch die Bildpunkte selbst übergeben werden.


Welches Farbformat dein Bildformat verwendet kannst du meist auf Wikipedia nachschlagen, dennoch will ich hier mal die am häufigsten genutzten Auflisten:


Dateiformat  Farbformat
BMPGL_BGR / GL_BGRA
JPG/JPEGGL_RGB
PNGGL_RGB / GL_RGBA


Sollte jetzt irgendjemand den Drang verspüren eine ausführlichere Übersicht zu erstellen, so sei er dazu herzlich eingeladen.

Tapeten an die Wand!

So, jetzt ist die Textur endlich da wo wir sie haben wollen, doch was nun? - Ganz einfach wir nehmen die Textur und kleben sie auf ein Viereck. Doch bevor wir anfangen sollen wir uns erst einmal überlegen, wie das mit dem Auflegen der Textur genau funktioniert.

Wofür steht eigentlich UV? In OpenGL (bzw. in allem was mit 3D und Texturen zu tun hat) nutzt man UV-Mapping um einen bestimmten Abschnitt einer Textur auf ein Polygon abzubilden. "U" steht für die Höhe der Textur und "V" für die Breite (jeweils in Prozent). Es ist total egal wie groß eine Textur ist, ihre Kantenlänge, da sie nur einen Wertebereich von 0 bis 1 haben kann. Das heißt, wenn eine Textur 256x256 groß ist und eine andere 512x512, so haben beide eine maximale Größe von 1 bzw 1 ist immer die volle Kantenlänge (man kann es quasi als Multiplikator mit der eigentlichen Breite sehen). Wir brauchen also das UV-Mapping nicht verändern, selbst wenn ein Objekt eine neue Textur mit anderer Größe erhält (sofern die Seitenverhältnisse gleich bleiben).

Die obere linke Ecke der Textur trägt die Texturkoordinaten von (0 / 0) (d.h. u = 0 und v = 0), die untere linke Ecke (0 / 1), die untere rechte Ecke (1 / 1) und schließlich die obere rechte Ecke die Koordinaten (1 / 0). Das heißt alles was wir machen müssen um auf unser Objekt eine Textur zu kleben ist dem jeweiligen Eckpunkt unseres Quadrates die entsprechende Texturkoordinate zuzuweisen. Wobei in diesem Sinne korrekt in Anführungszeichen stehen sollte. Es gibt kein falsches UV-Mapping. Man kann tolle Sachen mit diesen Textur-Koordinaten machen und so z.B. auch eine Textur auf einem Objekt spiegeln. Dafür müssten wir in unserem Beispiel nur die linken und rechten UV-Mapping vertauschen und z.B. für den zweiten Punkt die Koordinaten von (0 / 1) setzen und dafür beim dritten (1 / 1). Genauso würden wir auch die unteren vertauschen. Die UV-Koordinaten, wie sie oben angegeben sind bewirken nur, dass die Textur, so wie sie in der Datei vorkommt auch auf das Objekt geklebt wird. Selbstverständlich ist es auch möglich eine Textur gekachelt aufzukleben, nämlich indem Ihr Texturkoordinaten > 1 vergebt. Ebenso ist es möglich nur Teile einer Textur zu verwenden. Spielt ruhig ein wenig damit herum und schaut Euch an was passiert! Auf einige tolle Spielereien kommen wir zum Schluss noch mal zurück.
Nach so viel Vorwissen und Theorie kommen wir nun endlich zur Praxis!
Wie fast überall muss erstmal wieder etwas aktiviert werden - und, wer hätte es erraten - es ist die Texturierung, die wir mit glEnable() einschalten müssen, doch am besten erkläre ich das mal an folgendem Beispiel:

procedure TMyProgram.DrawScene;
begin
  glClear(GL_COLOR_BUFFER_BIT OR GL_DEPTH_BUFFER_BIT);  //Buffer löschen
  glLoadIdentity;
  gltranslatef(0,0,-5);  //Scene in den Bildschirm hinein verschieben, damit wir überhaupt etwas zu sehen bekommen.

  glEnable(GL_TEXTURE_2D);                  //Texturierung aktivieren (1)
  glBindTexture(GL_TEXTURE_2D,texture);     //Hier sagen wir wieder bescheid auf welche Textur sich folgene Anweisungen beziehen.(2)
  glBegin(GL_QUADS);
    glTexCoord2f(0,0); glVertex3f(-1,-1,0); //Links oben(3)
    glTexCoord2f(0,1); glVertex3f(-1,1,0);  //Links unten
    glTexCoord2f(1,1); glVertex3f(1,1,0);   //Rechts unten
    glTexCoord2f(1,0); glVertex3f(1,-1,0);  //Rechts oben
  glEnd;
  glDisable(GL_TEXTURE_2D); //Texturierung deaktivieren (1)
end;



  • (1)Als erstes müssen wir die Texturierung aktivieren, damit OpenGL weis, was wir vorhaben zu tun. Auf alle folgenden Polygone wird jetzt die ausgewählte Textur gelegt - mit glDisable(GL_TEXTURE_2D) beenden wir den Texturmodus, würden wir das nicht machen würde die Textur mit den zuvor definierten Koordinaten auf jedes neue Polygon gezeichnet.
  • (2) Das kennen wir schon vom Laden der Textur!
  • (3) Die Textur wird so auf dem Viereck abgebildet, wie sie in der Datei gespeichert ist.(Erklärung im voranstehenden Abschnitt)


Die Texturmatrix

Nen Wasserfall gefällig?


Was haben wir den bisher über Matrizen gelernt? - Na das man toll damit spielen kann :D Alle Bewegungen die wir in OpenGL erzeugen sind ja eigentlich Verschiebungen der Modelview-Matrix!
Also wie ist das jetzt mit der Texturmatrix? Wer ahnts schon ? Es ist genau so! Wir können einfach über den Translate Befehl die Texturmatrix verschieben (und somit die Textur auf dem Polygon!). Folgender Code macht das ganze etwas klarer:

procedure TMyProgram.UpdateWorld(Seconds: TGLFloat);
begin
  x:=x+1*Seconds;
end;
glMatrixMode(GL_TEXTURE);  //Die Texturmatrix aktivieren
  glLoadIdentity;          //Zurücksetzen
  glTranslatef(x,0,0);     //Verschieben auf der X-Achse
glMatrixMode(GL_MODELVIEW);//Wieder zur Modelview-Matrix springen


Würden wir X jedes Mal um 1 Einheit erhöhen, so würde diese Operation ohne Effekt bleiben, da wir die Textur immer um ihre ganze Größe nach links projizieren würden. Würden wir X z. B. bei jedem Vorgang um 0.01 erhöhen, so würde die Textur sich langsam von rechts nach links bewegen.
Wer das jetzt nicht verstanden hat, der sollte sich vielleicht nochmal Tutorial 3 vornehmen! Verstanden? - Wie wär es denn dann z.B. mit einem kleinen Wasserfall?



// bisherige kritik:

öhmm.... was heißt 'if assigned(tex)'? gucken obs jetz auch belegt is? also so wie if (string<>)

'4. Dann teilen wir OpenGL mit (2)' reihnfolge?! 1-2-3-4-wieder 2? ich glaub ich raff die materie nich so ganz, ohne die tuts davor^^ lass die nummerierung 1-6 weg und mach nur die die du in { } klammern hast, das verwirrt nur und du kannst ja auch absätze machen ohne sie durchzunummerieren.

'6. Jetzt endlich wird die Textur in den Grafikspeicher eingeladen.' da würd ich {4} davor schreiben, damit ma weiß wo du grad bist

//Kritik ende




Vorhergehendes Tutorial:
Tutorial Lektion 3
Nächstes Tutorial:
Tutorial Lektion 5

Schreibt was ihr zu diesem Tutorial denkt ins Feedbackforum von DelphiGL.com.
Lob, Verbesserungsvorschläge, Hinweise und Tutorialwünsche sind stets willkommen.