Spielwiese/Tutorial 4/grey: Unterschied zwischen den Versionen

Aus DGL Wiki
Wechseln zu: Navigation, Suche
(Erstes Upload (noch rech wirr))
K (Der Ausdruck ''<pascal>(.*?)</pascal>'' wurde ersetzt mit ''<source lang="pascal">$1</source>''.)
 
(7 dazwischenliegende Versionen von 3 Benutzern werden nicht angezeigt)
Zeile 3: Zeile 3:
 
==Vorwort==
 
==Vorwort==
 
Willkommen in der Welt der Bilder!
 
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  
+
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).
  
Laden über das einfache Platzieren kommen wir schlussendlich dann dazu, wie man nur einzelne Bereiche einer Textur darstellen kann
+
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
 
 
(dies ist das sog. UV-Mapping).
 
 
 
Wofür denn überhaupt Texturieren? Ohne ist doch viel schöner ... oder etwa nicht? Nach den letzen Tutorials werden sich vielleicht
 
 
 
einige den Wunsch verspürt haben mehr als nur abstrakte Kunst mit Farben und Farbverläufen darzustellen - dem Wunsch wird hiermit  
 
 
 
nachgekommen :D !
 
  
 
Genug geschwafelt, fangen wir mal endlich an.
 
Genug geschwafelt, fangen wir mal endlich an.
Zeile 20: Zeile 12:
  
 
===Vorbereitung===
 
===Vorbereitung===
In dem Ursprünglichen Tutorial hatte Phobeus hatte Tapeten zur Verbildlichung der Materie genutzt und da mir das soweit ganz gut  
+
<!--In dem Ursprünglichen Tutorial hatte Phobeus hatte Tapeten zur Verbildlichung der Materie genutzt und da mir das soweit ganz gut gefällt will ich das auch beibehalten.-->
 
+
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.
gefällt will ich das auch beibehalten.
+
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. <!-- (Ausnahmen sind möglich, werden in diesem Tutorial aber nicht behandelt) -->
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 denk - Yeah ich hab ne 12 Megapixel Cam - das gibt geile Texturen, dem kann ich nur zustimmen und dabei den  
 
 
 
Kopfschü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
+
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.
 
 
Performance davon ist.
 
Also heißt es erstmal zurecht schneiden. Was Texturen angeht ist OpenGl leider wählerisch - es nimmt nur Texturen im Format 2^n x 2^n
 
also z.B. 16x16, 64x64 aber auch 16x1024 oder 32x64 usw. (Ausnahmen sind möglich, werden in diesem Tutorial aber nicht behandelt)
 
 
 
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===
 
===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  
+
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.  
 
 
Grafikspeicher? Wer mal mit den standart 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.
 
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  
+
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):
  
gespeichert werden soll).
+
<source lang="pascal">
 
 
<pascal>
 
 
type
 
type
 
   TMyProgram=class(TEasySDL)
 
   TMyProgram=class(TEasySDL)
Zeile 62: Zeile 31:
 
     procedure LoadTexture;        //Hier wird die Texur geladen
 
     procedure LoadTexture;        //Hier wird die Texur geladen
 
   end;
 
   end;
</pascal>
+
</source>
 
<br>
 
<br>
 
<br>
 
<br>
 
Und jetzt laden wir die Textur:
 
Und jetzt laden wir die Textur:
 
<br>
 
<br>
<pascal>
+
<source lang="pascal">
 
procedure TMyProgram.LoadTexture;
 
procedure TMyProgram.LoadTexture;
 
var
 
var
 
   tex : PSDL_Surface;              //Zwischenspeicher für die Textur
 
   tex : PSDL_Surface;              //Zwischenspeicher für die Textur
 
begin
 
begin
   tex := IMG_Load('./../data/gfx/wall.jpg'); //Laden der Textur
+
   tex := IMG_Load('./wall.jpg'); //Laden der Textur
 
   if assigned(tex) then
 
   if assigned(tex) then
 
   begin
 
   begin
Zeile 88: Zeile 57:
 
   end;
 
   end;
 
end;
 
end;
</pascal>
+
</source>
  
Obwohl ich dich jetzt nicht für den DAU halte erkläre ich grade mal den Programablauf in Worten: <br>
+
Obwohl ich dich jetzt nicht für den DAU halte erkläre ich grade mal den Programmablauf in Worten:  
1.Zwischenspeicher erstellen  
+
# Zwischenspeicher für die Textur erstellen
2. Bilddatei in den Zwischenspeicher laden  
+
# 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  
+
# 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.
 +
# 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.
 +
# Dann setzen wir noch ein paar Texturfilter (3) (näheres darüber gibts im Tutorial Blending).
 +
# 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.
  
und der Zweite für die Adresse der Variable (näheres über Pointer gibt es z.B. auf delphi-treff.de)
 
4. Dann teilen wir OpenGL mit, dass sich von nun an alle Änderungen und Anweisungen, die sich auf Texturen beziehen auf die Textur
 
  
"Tex" beziehen.
+
Welches Farbformat dein Bildformat verwendet kannst du meist auf Wikipedia nachschlagen, dennoch will ich hier mal die am häufigsten genutzten Auflisten:
5. Dann setzen wir noch ein paar Texturfilter (näheres darüber gibts im Tutorial Blending)
 
6. Jetzt endlich wird die Textur in den Grafikspeicher eingeladen
 
  
  
 +
<table>
 +
<tr>
 +
<th>Dateiformat&nbsp;&nbsp;</th><th>Farbformat</th>
 +
</tr>
 +
<tr>
 +
<td>BMP</td><td>GL_BGR / GL_BGRA</td>
 +
</tr>
 +
<tr>
 +
<td>JPG/JPEG</td><td>GL_RGB</td>
 +
</tr>
 +
<tr>
 +
<td>PNG</td><td>GL_RGB / GL_RGBA</td>
 +
</tr>
 +
</table>
 +
<br>
 +
Sollte jetzt irgendjemand den Drang verspüren eine ausführlichere Übersicht zu erstellen, so sei er dazu herzlich eingeladen. <!--und möge dies dann doch bitte im Forum (oder gleich im Wiki) posten.-->
  
 +
===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.
  
    // Im Zweifelsfall einfach mal selbst Recherchieren u
+
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).
  
[[Bild:Tutimg_lektion4_nonetex.png|thumb|256px|left|Leeres Quadrat]]
+
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.
Spätestens nun sollte die gleiche Frage aufkommen, wie bei jedem, der zum ersten Mal versucht, eine Tapete an die Wand zu bringen: "Wie
+
<br>
 
+
Nach so viel Vorwissen und Theorie kommen wir nun endlich zur Praxis!
rum muss ich das Ding da befestigen?". Immerhin müssen wir uns nicht um den Leim kümmern, das erledigt unsere Grafikkarte bzw. OpenGL
+
<br>
 
+
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:
für uns ;).
+
<source lang="pascal">
 
+
procedure TMyProgram.DrawScene;
Wer noch nie 3D programmiert hat wird im ersten Moment vielleicht fälschlicherweise denken, dass man die Position der Textur per
 
 
 
Weltkoordinaten definiert. Das hätte allerdings fatale Folgen sobald sich das Objekt im Raum bewegt. Wir müssten die Position jedes mal
 
 
 
neu berechnen. Um eben dieses Problem zu umgehen, geht man in der 3D-Programmierung einen anderen Weg. Man vergibt einfach für jede
 
 
 
Ecke eines Objektes eine Koordinate der Textur. OpenGL errechnet dann aus diesen Texturkoordinaten das Stück der Textur, das dann über
 
 
 
das gesamt Objekt projiziert wird.
 
 
 
Die Rede ist hierbei vom so genannten UV-Mapping, welches vor allem bei Anfängern ein leichtes Gefühl des Unbehagens auslösen sollte.
 
 
 
Es ist jedoch nur halb so wild, wenn man es halbwegs verstanden hat.
 
 
 
Betrachten wir gleich mal das Bild rechts und versuchen, uns in die Problematik hineinzuversetzen. Da Texturen unterschiedliche Größen
 
 
 
haben können wurden so genannte Texturkoordinaten eingeführt. Es ist total egal wie groß eine Textur ist, 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.
 
 
 
Wir brauchen also das UV-Mapping nicht verändern, selbst wenn ein Objekt eine neue Textur mit anderer Größe erhält.
 
 
 
[[Bild:Tutimg_lektion4_texuv_02.png|thumb|256px|right|Textur und leere Quadratfläche. Eingezeichnet ist das Standardmapping]]
 
Auf diesem Bild sehen wir zum einen die Textur, die wir verwenden wollen, zum anderen das Objekt, auf das "geklebt" werden soll
 
 
 
hintereinander. Natürlich sieht das in der Realität nicht so aus (die Textur würde das gesamte Objekt überdecken). Ich denke aber diese
 
 
 
Darstellung erleichtert das Verstehen ;).
 
 
 
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ührungszeichnen stehen sollte. Es gibt kein falsches UV-Mapping. Man kann tolle Sachen mit diesen  
 
 
 
Textur-Koordinaten machen und so z.&nbsp;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.&nbsp;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  
 
 
 
:).
 
 
 
Sicherlich brennt es einigen von Euch nun bereits unter den Finger und Ihr fragt "Wie kann ich denn die UV-Koordinaten den Eckpunkten
 
 
 
der Fläche zuweisen?". Nun, zunächst funktioniert das ähnlich wie bei allen Dingen unter OpenGL. Wir setzen eine UV-Koordinate und
 
 
 
solange wir nichts verändern werden alle Punkte mit diesen Koordinaten versehen bis wir andere Instruktionen geben. In unserem Fall
 
 
 
müssen wir dies natürlich nach jedem Punkt machen. Die Funktion, die dafür verwendet wird heißt [[glTexCoord]]. Um also eine Textur auf
 
 
 
unserem Quad zu projizieren, benötigen wir folgenden Code:
 
 
 
<pascal>glBegin(GL_QUADS);
 
  glTexCoord2f(0,0); glVertex3f(-1,1,0);  //lo
 
  glTexCoord2f(0,1); glVertex3f(-1,-1,0); //lu
 
  glTexCoord2f(1,1); glVertex3f(1,-1,0);  //ru
 
  glTexCoord2f(1,0); glVertex3f(1,1,0);  //ro
 
glEnd;</pascal>
 
 
 
Das klappt doch wunderbar oder? Damit wir die Textur aber auch wirklich (bzw. überhaupt) genießen können müssen wir Texturing mit Hilfe
 
 
 
von '''glEnable''' und dem Token '''GL_TEXTURE_2D''' aktivieren. In unserem Fall wo das gesamte Projekt(ein Quad) texturiert werden
 
 
 
soll, könnt ihr den Aufruf direkt in eure GL-Initialisierung schreiben, ansonsten gehört selbiger direkt vor die zu texturierenden
 
 
 
Flächen in die Renderschleife.
 
glEnable(GL_TEXTURE_2D);
 
Mit Hilfe von glDisable und demselben Token ist es dann auch möglich Objekte zu Zeichnen, die über keine Texturen verfügen. Andernfalls
 
 
 
würde nämlich die zuletzt gesetzte Textur und die Texturkoordinaten des letzten glTexCoord-Aufrufs verwendet werden.
 
 
 
Doch eine Kleinigkeit habe ich Euch nun doch noch verschwiegen! Wir müssen natürlich noch die richtige Textur setzen damit OpenGL
 
 
 
überhaupt weiß, was auf diesem Quad gezeichnet werden soll. Dies ist an sich noch relativ einfach, allerdings müssen wir diese auch
 
 
 
noch in den Speicher bekommen, damit sie überhaupt zur Verfügung steht. Nun wird's theoretisch ;).
 
 
 
===Tapeten besorgen===
 
Ich werde jetzt nicht näher darauf eingehen, wie man Texturen erstellt. Vielleicht gibt's ja einige Photoshopexperten unter Euch, die
 
 
 
Lust haben einige Ihrer Tricks den anderen in Form eines Tutorials zu zeigen.
 
 
 
Zunächst einmal müssen wir uns die eigentlichen Bilddaten besorgen. Wir werden das jetzt in diesem Tutorial mit [[SDL]] machen es gibt
 
 
 
jedoch auch die Möglichkeit die Daten manuell zu laden das könnt Ihr hier nachlesen:
 
* [[TGA Bilder laden]]
 
Es gibt allerdings auch Texturloader die Euch die nächsten Kapitel abnehmen und das alles für Euch machen. glBitmap ist so ein Loader.
 
 
 
Mehr dazu erfahrt Ihr in dem [[Glbitmap_loader|glBitmap]]-Artikel.
 
 
 
Bei SDL rufen wir nur [[IMG_Load]] auf und prüfen dann ob das Laden erfolgreich war. Hierbei sei erwähnt, dass es unter Linux zu
 
 
 
Problemen führen kann, wenn ein Programm nicht aus einer Konsole heraus gestartet wurde. In diesem Fall sind die Pfade zu den Texturen
 
 
 
nämlich falsch gesetzt und das Laden würde fehlschlagen. Abhilfe schafft man durch das Verwenden von absoluten Pfaden.
 
 
 
<pascal>uses SDL, SDL_Image;
 
 
 
var
 
  tex : PSDL_Surface;
 
 
begin
 
begin
   tex := IMG_Load(PChar(ExtractFileDir(paramStr(0))+'/wiki.jpg'));
+
   glClear(GL_COLOR_BUFFER_BIT OR GL_DEPTH_BUFFER_BIT); //Buffer löschen
  if assigned(tex) then
 
  begin</pascal>
 
 
 
Das war es dann auch schon... Wir haben die Textur im Speicher. Doch was nun?
 
 
 
===Texturen richtig zubereitet===
 
Nachdem sich unsere Textur nun im Speicher des Computers befindet, geht es darum daraus auch eine richtige Textur zu machen, damit wir
 
 
 
diese in OpenGL anzeigen können. Bisher befinden sich ja nur die Rohdaten im Speicher. Hierfür teilen wir OpenGL mit, dass wir eine
 
 
 
neue Textur erzeugen wollen:
 
 
 
<pascal>glGenTextures(1, @TexID);</pascal>
 
 
 
TexID ist in diesem Fall ein gluInt, kann aber genauso gut ein Array davon sein, um mehrere Texturen zu erzeugen. Genau dafür wird dann
 
 
 
auch der erste Parameter verwendet, der OpenGL mitteilt wieviele Texturen in dieses Array geschrieben werden sollen. In unserem Fall
 
 
 
ist dies eben nur ein Element. Aber was ist ist das für ein Wert in TexID? OpenGL verwaltet die Texturen anhand eindeutiger Namen.
 
 
 
[[glGenTextures]] ermittelt einen oder mehrere bisher ungenutzte Namen und schreibt diese in TexID. Durch TexID können wir unsere
 
 
 
Textur ab sofort also eindeutig identifizieren.
 
 
 
<pascal>glBindTexture(GL_TEXTURE_2D, TexID);</pascal>
 
 
 
Wir teilen OpenGL mit, dass sich von nun an alle Änderungen und Anweisungen, die sich auf Texturen beziehen auf die Textur TexID
 
 
 
beziehen.<br>
 
Die folgenden beiden Zeilen sind zwar nicht wirklich nötig, um eine Textur zu erzeugen aber glaubt mir, sie werden ansonsten
 
 
 
potthässlich aussehen. Wir werden in einem anderen Tutorial näher auf dessen Bedeutung eingehen, nämlich den so genannten
 
 
 
Texturfiltern. Die momentane Einstellung ist leicht rechenlastig jedoch auch von recht guter Qualität. Ihr werdet anfangs keine
 
 
 
Probleme mit der Geschwindigkeit bekommen ;).
 
 
 
<pascal>glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);</pascal>
 
 
 
Zu guter Letzt müssen wir die Bildinformationen irgendwie an OpenGL übergeben. Dies übernimmt die Funktion [[glTexImage2D]]:
 
 
 
<pascal>glTexImage2D(GL_TEXTURE_2D, 0, 3, tex^.w, tex^.h,0, GL_RGB, GL_UNSIGNED_BYTE, tex^.pixels);</pascal>
 
 
 
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.<br>
 
Mit Hilfe dieser Funktion sollten nur Texturen der Größe 2^n x 2^n erzeugt werden. Andernfalls werdet Ihr die Textur nicht in Ihrer
 
 
 
vollen Schönheit, d.h. überhaupt nicht betrachten können. Es gibt jedoch Möglichkeiten Texturen zu laden, die nicht die Größe 2^n
 
 
 
entsprechen. Die Funktion [[gluBuild2DMipmaps]] bietet hier beispielsweise eine Alternative.<br>
 
Das war es auch schon. Wer mehr über die einzelnen Parameter und Befehle wissen will ist herzlich eingeladen in unserem Wiki
 
 
 
umherzustöbern und sein Wissen zu erweitern um später selbst vielleicht einmal ein paar Artikel im Wiki zu veröffentlichen.
 
 
 
Die Daten im Arbeitsspeicher brauchen wir nun nicht mehr. Bei SDL geben wir sie so frei:
 
 
 
<pascal>SDL_FreeSurface(tex);</pascal>
 
 
 
Fassen wir das ganze bei SDL nochmal zusammen:
 
<pascal>var
 
  tex : PSDL_Surface;
 
begin
 
  tex := IMG_Load('./wiki.jpg');
 
  if assigned(tex) then
 
  begin
 
    glGenTextures(1, @TexID);
 
    glBindTexture(GL_TEXTURE_2D, TexID);
 
 
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
 
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 
 
    // Achtung! Einige Bildformate erwarten statt GL_RGB, GL_BGR. Diese Konstante fehlt in den Standard-Headern
 
    glTexImage2D(GL_TEXTURE_2D, 0, 3, tex^.w, tex^.h,0, GL_RGB, GL_UNSIGNED_BYTE, tex^.pixels);
 
 
 
    SDL_FreeSurface(tex);
 
  end;</pascal>
 
 
 
Nun kommt aber bitte nicht auf die Idee die Textur in euerer Hauptschleife wieder und wieder neu zu laden. Es reicht die Textur einmal
 
 
 
zu laden und von da an steht sie einem solange zur Verfügung bis man gedenkt sie wieder aus dem Grafikkartenspeicher zu entfernen.<br>
 
Das übernimmt die Funktion [[glDeleteTextures]]. glDeleteTextures funktioniert ähnlich wie glGenTextures, nur dass die Texturen
 
 
 
entfernt werden. Der erste Parameter gibt die Anzahl der zu löschenden Texturen an, während der zweite Parameter den Namen der Textur
 
 
 
bzw. ein Array der Namen mehrerer Texturen verlangt.<br>
 
Das ist doch für den Anfang nicht schlecht. Ihr solltet nun in der Lage sein, zumindest einfache Objekte zu texturieren. Das ist
 
 
 
eigentlich das gesamte Grundprinzip. Natürlich gestaltet es sich schwieriger ein komplexeres Objekt mit UV-Koordinaten zu versehen als
 
 
 
ein Quad. An der Technik selbst ändert sich aber nur wenig.
 
 
 
An dieser Stelle noch eine Anmerkung zu den Texturformaten. Man sollte immer darauf achten, dass die verwendeten Texturen als
 
 
 
Kantenlänge eine 2er-Potenz besitzen. Also z.B. 64x128, 256x1024 oder 32x32. Dies liegt daran, dass ältere Grafikkarten nur diese
 
 
 
Formate unterstützen. Erst die neueren Generationen können auch sogenannte ''non power of two'' texturen darstellen. <br>
 
Falls ihr also einmal nur ein weißes Viereck seht, wo eigentlich eine Textur sein müßte, dann prüft ob ihr auch tatsächlich ein
 
 
 
korrektes Format verwendet.
 
 
 
Wir werden nun einige Spielereien zeigen, die man mit Texturen machen kann damit Ihr ein Gefühl dafür bekommt, wie man ein Problem
 
 
 
elegant umschiffen kann!
 
 
 
==Die Rückkehr der Matrizen==
 
===Texturen===
 
Tja... und da ich ein Sadist bin [Anm. des Lektors: Ooooh ja!] werden wir uns nun nochmals den Matrizen zuwenden! Dachtet Ihr etwa, Ihr
 
 
 
seid die Dinger schon wieder los? Ich habe Euch im letzten Tutorial angedroht, dass es nicht nur eine Matrix für die "Welt" gibt,
 
 
 
sondern auch noch weitere. Unter anderem eben auch für Texturen.
 
 
 
Die Texturmatrix funktioniert streng genommen genauso wie auch die Worldmatrix. Solange wir sie aktiv haben, wird sie von jedem
 
 
 
Matrixbefehl berücksichtigt! Einziger wirklicher Unterschied ist, dass sie nicht die Position oder die Form eines Objektes beeinflusst,
 
 
 
sondern nur das Rendern der Textur selbst. Hö? Was meint der Kerl bloß damit?! Nun... stellt Euch vor, Ihr habt ein Quad und wollt
 
 
 
darauf eine Textur bewegen, so dass es aussieht, als würde sie sich von rechts nach links bewegen! Was wir jedoch nicht wollen ist,
 
 
 
dass sich das Objekt bewegt, sondern nur, das was darauf zu sehen ist. Stellt Euch vor, Ihr schaut aus einem Fenster und seht einen
 
 
 
wolkigen Himmel, der Wind bläst die Wolken von einer Himmelsrichtung zur anderen. Ihr könntet so z.B. das Fenster als Quad nehmen und
 
 
 
dann die Textur darauf zeichnen und glaubt mir, die Illusion würde auffliegen, sobald sich das Fenster mit der Textur bewegen soll.
 
 
 
Nein, stattdessen bewegen wir nur die Textur auf dem Quad und zwar ohne das UV-Mapping anzutasten. Wir beeinflussen einfach die Art,
 
 
 
wie die Textur auf das Quad projiziert werden soll. Dafür dient die Texturmatrix.
 
 
 
Stellen wir uns mal vor, dass wir unser Quad zeichnen und eine Variable X haben, die wir bei jedem Rendervorgang leicht erhöhen. Dies
 
 
 
soll die Bewegung darstellen. Alles was wir nun tun müssen ist, die Texturmatrix entsprechend anzupassen.
 
 
 
<pascal>glMatrixMode(GL_TEXTURE);
 
 
   glLoadIdentity;
 
   glLoadIdentity;
   glTranslatef(x,0,0);
+
   gltranslatef(0,0,-5); //Scene in den Bildschirm hinein verschieben, damit wir überhaupt etwas zu sehen bekommen.
glMatrixMode(GL_MODELVIEW);
 
</pascal>
 
  
Das ist bereits der ganze Spuk! Wir teilen OpenGL durch glMatrixMode mit, dass sich ab sofort alle Veränderungen der Matrix nur noch
+
  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;
 +
</source>
 +
<br>
 +
<br>
  
auf die Texturmatrix beziehen sollen. Danach setzen wir diese sofort auf ihren Standardwert zurück. Der Grund hierfür sollte Euch von
+
<ul>
 +
<li>(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.</li>
 +
<li>(2) Das kennen wir schon vom Laden der Textur!</li>
 +
<li>(3) Die Textur wird so auf dem Viereck abgebildet, wie sie in der Datei gespeichert ist.(Erklärung im voranstehenden Abschnitt)</li>
 +
</ul>
 +
<br>
  
der Worldmatrix noch in Erinnerung sein. Anschließend ändern wir die Position der Textur auf dem Quad. Würden wir X jedes Mal um 1
+
==Die Texturmatrix==
 
+
===Nen Wasserfall gefällig?===
Einheit erhöhen, so würde diese Operation ohne Effekt bleiben, da wir die Textur immer um ihre ganze Größe nach links projizieren
+
<br>
 
+
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!<br>
würden. Würden wir X z.&nbsp;B. bei jedem Vorgang um 0.01 erhöhen, so würde die Textur sich langsam von rechts nach links bewegen. Ich
+
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:
 
 
denke, Ihr könnt bereits erahnen, welche Parameter dafür verwendet werden, um eine Textur von unten nach oben zu bewegen ;).
 
 
 
Wichtig ist auf jeden Fall, dass Ihr anschließend mit glMatrixMode wieder die Worldmatrix aktiviert, da sonst alle weiteren
 
 
 
Matrixmanipulationen auf die Texturmatrix angewandt werden würden. Denkt nicht, dass die Texturmatrix damit deaktiviert wird! Ab sofort
 
 
 
wird auf das Objekt sowohl die World- als auch die Texturmatrix angewendet. Seht Ihr? Das Ganze ist doch gar nicht ganz so schlimm. Es
 
 
 
versteht sich auch von selbst, dass Ihr glRotate und glScale ebenfalls darauf anwenden könnt, natürlich auch nach den gleichen Regeln
 
 
 
auf die Worldmatrix. Experimentiert am Besten auch etwas mit diesen Einstellungen herum!
 
 
 
==Wir tapezieren unsere Welt mal anders==
 
===Am Fließband===
 
Eigentlich sollte an dieser Stelle bereits Schluss sein. Ich wurde allerdings während des Schreibens des Tutorials nach einer Sache
 
 
 
gefragt und möchte die Chance nutzen, diese Frage zu beantworten und gleichzeitig das angewandte Wissen der vorhergehenden Lektion
 
 
 
etwas zu vertiefen.
 
 
 
Wir haben folgende Problematik: Wir brauchen eine animierte Textur auf einem Objekt. Dies könnte z.&nbsp;B. eine bewegte Lavamasse sein
 
 
 
oder irgendwas Glibbriges, was am Boden wabbelt. Oder eben in unserem Fall eine Folge von Zahlen, die wie ein Countdown aufgelistet
 
 
 
werden. Sicherlich könnte nun jemand von Euch auf die Idee kommen, viele einzelne Texturen zu laden und diese in einem Array zu
 
 
 
speichern. Dies mag auch durchaus sinnvoll sein nicht jedoch, wenn es sich um kleine Bilder handelt (bei uns z.&nbsp;B. 32x32 Pixel).
 
 
 
Auch bei Bitmap-Fonts würde man nie auf die Idee kommen, für jeden Buchstaben eine einzelne Textur zu verwenden, sondern vielmehr eine
 
 
 
Textur mit allen Buchstaben darauf erstellen, da dies u.a. den Ladevorgang erheblich beschleunigt! Klingt einleuchtend oder? Aber wie
 
 
 
sollen wir dem Programm mitteilen, welcher Teil der Textur auf welche "Ecke" geklebt werden soll? Nun... auch hierbei heißt des Lösungs
 
 
 
Rätsel [Anm. des Lektors: Er macht's schon wieder. Herrlich...] UV-Mapping!
 
 
 
[[Bild:Tutimg_lektion4_numbers.gif]]
 
 
 
Dies ist unsere Textur! Sie hat eine Gesamtlänge von 256x32 Bildpunkten und wie man leicht sehen kann, soll sie aus 8 Teilstücken
 
 
 
bestehen. Die V-Koordinate können wir in diesem Fall getrost vernachlässigen, weil sie in diesem Fall immer konstant sein wird, weil
 
 
 
wir die ganze Höhe der Textur verwenden wollen. Sie wäre nur dann interessant, wenn wir z.&nbsp;B. noch eine zweite Reihe darunter
 
 
 
setzen würden. Ich denke jedoch, dass jemand der das gleich folgende verstanden hat, sofort eine Lösung für diese "Problematik" finden
 
 
 
wird ;).
 
 
 
[[Bild:Tutimg_lektion4_numbers2.gif]]
 
 
 
Wichtig ist, dass wir uns bewusst werden, dass eine Textur beim UV-Mapping immer von 0 bis 1 reicht. Um nun die einzelnen Bilder aus
 
 
 
einer Textur auf ein Objekt zu setzen, müssen wir nichts anderes machen, als zu errechnen, an welcher Stelle ein Bild anfängt und wo es
 
 
 
aufhört. Nur zur Kontrolle, damit es auch jeder begreift: Würden wir nur die erste Hälfte der Textur auf ein Objekt kleben, so müsste
 
 
 
unsere U-Koordinate von 0 bis 0.5 reichen. Die zweite Hälfte hingegen von 0.5 - 1.0. Soweit klingt es doch noch alles logisch oder?
 
 
 
Genauso müssen wir auch vorgehen, wenn wir einzelne Bilder auf einem Quad abbilden wollen. In unserem Fall müsste die U-Koordinate von
 
 
 
0 bis 1/8 reichen. Das zweite Bildchen hingegen von 1/8 bis 2/8 etc. D.&nbsp;h. wir wissen, dass jedes unserer Bilder 1/8 "Einheiten"
 
 
 
lang ist! Und somit haben wir ja bereits eine Lösung für unser Problem. Um das ganze dynamisch auszudrücken: Wir brauchen nur die Größe
 
 
 
der Textur durch die Anzahl der Bilder zu teilen. Bevor jemand einen Denkfehler macht: Es ist hierbei ganz egal, wie groß die Textur
 
 
 
wirklich ist (hier 256x32 Pixel). Dank OpenGL errechnen wir das UV-Mapping ja in absoluten Größen.
 
Nun der Code:
 
 
 
<pascal>PicLength:= 1 / PicCount;
 
PicPos:=Round(Pic)*PicLength;
 
glBegin(GL_QUADS);
 
  glTexCoord2f(PicPos,   1); glVertex3f(-1,-1,0);
 
  glTexCoord2f(PicPos + PicLength, 1); glVertex3f(1,-1,0);
 
  glTexCoord2f(PicPos + PicLength, 0); glVertex3f(1,1,0);
 
  glTexCoord2f(PicPos,   0); glVertex3f(-1,1,0);
 
glEnd;
 
 
 
Pic:=Pic + MovementValue;
 
</pascal>
 
 
 
Pic ist in diesem Fall eine Variable vom Typ Single, die langsam erhöht wird. Wir runden den Wert hier, so dass beim Erreichen eines
 
 
 
jeden ganzzahligen Wertes das nächste Bild angezeigt wird. Wir multiplizieren die Nummer des anzuzeigenden Bildes mit der Breite eines
 
  
Bildes, um die Anfangsposition zu erhalten und addieren dann noch eine volle Bildbreite dazu, um die Endposition zu erhalten. Hört sich
+
<source lang="pascal">
 
+
procedure TMyProgram.UpdateWorld(Seconds: TGLFloat);
gewaltig gefährlich an, liegt aber mehr an meinem mangelnden Ausdruck als an der Schwierigkeit dieses Problems ;).
 
 
 
Im Sample werdet Ihr noch sehen, was passiert, wenn man den Wert nicht rundet. Man erhält in diesem Fall einen "flüssigen"
 
 
 
Bildübergang. Letztendlich gibt es viele Möglichkeiten, solche Ideen zu implementieren. Nehmt dies einfach als kleineren Gedankenschub
 
 
 
und vor allem: Werdet Euch bewusst, was genau dort passiert! Eine Menge toller Dinge lassen sich mit einem guten UV-Mapping erzielen.
 
Wer noch etwas umherexperimentieren will kann gern versuchen selbes Ziel mit Hilfe der Texturenmatrix zu erreichen. Die Lösung ist
 
 
 
verblüffend einfach.
 
 
 
===Terraforming mal anders===
 
Relativ lange habe ich nach einem guten Beispiel für folgende Problematik gesucht: Ich wollte Euch die UV-Koordinaten etwas näher
 
 
 
bringen und Euch zeigen, wofür man sie einsetzen kann. Irgendwie wollte mir nichts Interessantes aus meinem Kopf entspringen bis ich
 
 
 
irgendwann in einigen alten Programmen von mir rumgewühlt habe und einen alten Terrainrenderer von mir fand. Schon war die Idee da! Wir
 
 
 
schreiben ein kleines Programm, das eine ganz simple, unoptimierte Landschaft rendern wird. Das hört sich sicherlich Anfangs relativ
 
 
 
gewaltig an, mit etwas Verständnis für die oberen Probleme sollte dies jedoch kein Problem für Euch sein.
 
 
 
Zuvor allerdings ein paar Gedankenspiele. Zunächst widmen wir uns kurz der Texturiering. Wie würde die simpelste Landschaft aussehen,
 
 
 
die wir uns vorstellen können? Richtig! Sie wäre ein einfaches, flach liegendes Quadrat mit einer Textur überzogen, der Wand aus
 
 
 
unserem ersten Versuch sehr ähnlich! Dies hat jedoch einen klitzekleinen Nachteil: Wir könnten keine Höhenstufen einbauen und ohne die
 
 
 
wäre die Landschaft nur halb so realistisch. Denn wir wollen versuchen, folgende Szene zu zaubern:
 
 
 
[[Bild:Tutimg_lektion4_landscape.gif|thumb|256px|none|eine Lanschaft]]
 
Um allerdings Höhen einzubinden, müssen wir die Landschaft in viele kleinen Quads unterteilen, die natürlich an Ihren Eckpunkten
 
 
 
unterschiedliche Höhen haben, sich jedoch jeweils einige Punkte teilen. Es versteht sich von selbst, dass diese die gleiche Höhe haben
 
 
 
müssen, damit die Landschaft auch durchgängig ist und nicht irgendwelche mysteriösen Löcher darauf erscheinen ;).
 
Auf folgendem Screenshot kann man dies deutlich erkennen:
 
 
 
[[Bild:Tutimg_lektion4_landwire.gif|thumb|256px|left|Gitter Ansicht der Landschaft]]
 
Technisch gesehen ist das Ganze einfach zu realisieren, da wir nur begreifen müssen, dass alle Quads nebeneinander liegen und sich -
 
 
 
bis auf die äußeren alle einen Eckpunkt teilen. Nun müssen wir beim Rendern jeden Eckpunkt nur noch vom angrenzenden Quad lesen und
 
 
 
fertig ist die Landschaft. Ich will da nicht näher drauf eingehen, weil es sich hier nicht um ein Tutorial zur Landschaftsgestaltung
 
 
 
handelt. Der Code sollte sich eigentlich von selbst erklären.
 
 
 
Vielmehr sollten wir uns einer anderen Problematik widmen! Nämlich wie zur Hölle texturieren wir die Landschaft so, dass sie nicht zur
 
 
 
Marke "augenfeindlich" gehört?
 
 
 
[[Bild:Tutimg_lektion4_landscapeerror.gif|thumb|256px|right|wiederkehrende Landschaft]]
 
Wenn Euer erster Gedanke dabei "Boah, cool!" ist, dann gibt's eins auf die Finger! Das wollen wir doch nicht... wie sieht den die
 
 
 
Landschaft aus. Eine immer wiederkehrende Landschaft -> man erkennt sofort, dass wir hierbei auf jedes Quad die gleiche Textur geklebt
 
 
 
haben. Doch wie schaffen wir es nun, dass wir eine Textur über alle Quads ziehen, so dass die ganze Landschaft mit einer Textur
 
 
 
überzogen ist anstatt nur über ein einzelnes Feld?
 
Nun, des Lösungs Geheimnis [Anm. des Lektors: Ich liebe ihn dafür! Andere lösen Rätsel. Er geheimnist Lösungen] sind eben unsere
 
 
 
UV-Koordinaten und einige pfiffige Köpfe unter Euch sollten bereits einen ersten Verdacht haben. Denn die erste Idee sollte es sein,
 
 
 
den linken unteren Punkt eine UV-Koordinate von (0/0) zu geben und der rechten oberen (1/1).
 
Alles was wir nun also machen müssen, ist, uns die entsprechenden UV-Koordinaten für die Quads dazwischen auszurechnen und sie dann den
 
 
 
Punkten zuzuweisen. Dafür benötigen wir zunächst die Breite eines Quads. Mit normaler Logik lässt sich folgende Aussage aufstellen.
 
<pascal>qw:=1 / XCount
 
qh:=1 / YCount;
 
</pascal>
 
Ein Quad benötigt die Länge von 1 durch die Anzahl der Quads in einer Reihe oder Spalte. Genauso verhält es sich auch mit der Höhe. Nun
 
 
 
brauchen wir beim Rendern nur noch dem jeweiligen Quad in einer For-Schleife die entsprechende Koordinate zuzuweisen:
 
<pascal>U:=1 / XCount;
 
V:=1 / YCount;
 
 
 
for y:=0 to YCount-1 do
 
 
begin
 
begin
   glPushMatrix;
+
   x:=x+1*Seconds;
  for x:=0 to XCount-1 do
 
  begin
 
    glBegin(GL_QUADS);
 
      glTexCoord2f(U*x,    V*(y+1));
 
      glVertex3f(0,  Map[x,y+1],  0);
 
      glTexCoord2f(U*x,    V*y);   
 
      glVertex3f(0,  Map[x,y],  1);
 
      glTexCoord2f(U*(x+1), V*y);   
 
      glVertex3f(1,  Map[x+1,y],  1);
 
      glTexCoord2f(U*(x+1), V*(y+1)); 
 
      glVertex3f(1, Map[x+1,y+1],  0);
 
    glEnd;
 
    glTranslatef(1,0,0);
 
  end;
 
  glPopMatrix;
 
  glTranslatef(0,0,-1);
 
 
end;
 
end;
</pascal>
+
</source>
Das sieht nach einem herben Stück Arbeit aus oder? Aber wenn Ihr Euch das ganze mal aufzeichnet und im Kopf durchspielt, wird der
 
  
Groschen fallen. Denkt mal etwas darüber nach! Wenn es dann doch noch Probleme mit dem Verständnis geben sollte, wird das nächste
+
<source lang="pascal">
 +
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
 +
</source> 
 +
<br>
  
Kapitel hoffentlich jedes Missverständnis aus dem Wege räumen ;).
+
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.
 
+
<br>
==Nachwort==
+
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? <!--Kostenlose Texturen findest du zu Hauf im Internet (da ich aber keine finden konnte die unter der GPL steht ist hier auch keine dabei) - such einfach mal mit google ... oder mach dir halt selbst welche :D-->
Okay... ich hoffe Ihr fühlt Euch nach der Abarbeitung dieses Tutorials genauso wie ich, nachdem ich es für Euch geschrieben habe...  
+
<br>
 
 
nämlich elend. Irgendwie wurde das immer mehr und ich sagte mir immer wieder "nein, dass ist nicht genug. Das muss genauer und
 
 
 
anschaulicher werden". Dies ist auch der Grund dafür, warum dieses eines der größten Tutorials mit den meisten Bildern usw geworden
 
 
 
ist. Erst sollte es noch etwas mehr werden und dann in mehrere Tutorials aufgespaltet werden, allerdings glaube ich mit diesem Tutorial
 
 
 
einen guten Mittelweg zwischen Information und Totlabern gefunden zu haben. Lasst es mich wissen, wie Ihr dazu steht!<br>
 
Und bevor die Kritiker gleich wieder alle aus Ihren Löchern kommen und mir sagen, dass einige Bereich einfach als gegeben angenommen
 
 
 
werden; denen sei nur gesagt, dass wir u.&nbsp;a. auch versuchen Rücksicht auf Leute zu nehmen, die noch nie 3D programmiert haben und
 
 
 
vielleicht Eurem Wissen nicht standhalten können. Daher halte ich es hier für wichtiger, Grundlagen wie UV-Koordinaten und den Einsatz
 
 
 
von Texturen zu erklären, als ihnen tausende von Zeilen um die Ohren zu hauen, wie sie die Bytes von der Festplatte in den
 
 
 
Arbeitsspeicher laden können. Um jedoch keine Wissenslöcher offen zu lassen: Ihr könnt Euch sicher sein, dass spezielle Tutorials
 
 
 
folgen werden, die sich speziell mit dem Laden verschiedener Formate beschäftigen und die genaue Interna (was im Hintergrund abläuft)
 
 
 
zu erklären versuchen. Auch das Alpha Blending wird hier mehr zu "Show-Zwecken" verwendet und wird zusammen mit dem Z-Buffer genauer
 
 
 
erklärt werden! <br>
 
Also bloß keine falsche Hektik! Ich weiß ... einige von Euch sind recht ungeduldig, aber ständiges Nachfragen und Drängeln führt zu
 
 
 
nichts. Versucht Fragen lieber ins Forum zu setzen, damit dort ein wenig Leben reinkommt. Denn wenn es dort belebter wird, kommen auch
 
  
schneller neue Leute und vielleicht sind ja auch welche dabei, die dann das DGL-Team entlasten können. Also nutzt bitte das Forum,
 
  
anstatt andauernd per ICQ oder Mail irgendwelche Fragen zu stellen! Die Antwort wird auch nicht viel länger auf sich warten lassen. Im
 
  
Forum jedoch ist alles dokumentiert und auch anderen zugänglich, so dass ich nicht die gleiche Frage bis zu 5 mal am Tag beantworten
 
  
muss. Sorry aber das geht einem auf die Nerven und vor allem auf die Zeit ;). Schließlich sind wir keine Maschinen sondern Menschen,
+
// bisherige kritik:
  
die auch ein privates Leben haben ^__-.
+
öhmm.... was heißt 'if assigned(tex)'? gucken obs jetz auch belegt is? also so wie if (string<>'')
  
Glaubt uns, wir investieren einen sehr großen Teil unserer Zeit in dieses Projekt und solch ein Tutorial lässt sich nicht binnen
+
'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.
  
weniger Tage anfertigen. Jeder der so etwas bereits einmal gemacht hat, wird wissen was ich meine. Es muss ein Konzept her, es müssen
+
'6. Jetzt endlich wird die Textur in den Grafikspeicher eingeladen.' da würd ich {4} davor schreiben, damit ma weiß wo du grad bist
  
Samples geschrieben, Screenshots gemacht werden und ein halbwegs verständlicher Text her. Und nichtsdestotrotz soll es am Ende auch
+
//Kritik ende
  
noch passen, einem möglichst großen Publikum Wissen vermitteln, am Besten von Schreib- und Sprachfehlern befreit und in einem halbwegs
 
  
ansehnlichen HTML-Dokument präsentiert werden. Ein langer Weg ;).
 
Okay, in diesem Sinne, bis bald ;)!
 
btw: Vielen Dank an Magellan für die Bereitstellung und Integration seiner "besonderen Lernleistung".
 
  
Euer<br>
 
'''Phobeus'''
 
  
 
{{TUTORIAL_NAVIGATION|[[Tutorial Lektion 3]]|[[Tutorial Lektion 5]]}}
 
{{TUTORIAL_NAVIGATION|[[Tutorial Lektion 3]]|[[Tutorial Lektion 5]]}}

Aktuelle Version vom 10. März 2009, 20:10 Uhr

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.