Kapitel 9b: Texturierung

Texturen (textures) trennen das Erscheinungsbild einer Oberflaeche (Material, Farbe, Muster, vorberechnetes Licht) von der Geometrie: ein 2D-Bild wird ueber Texturkoordinaten auf das Polygon abgebildet. Behandelt werden UV-Koordinaten, Wrapping, Filterung, Mipmaps und der OpenGL-Textur-Workflow.

Ueberblick

Statt jedes Detail eines Objekts durch aufwendige Geometrie zu modellieren, speichert man das Erscheinungsbild als Textur (texture), also ein meist 2D-Bild, getrennt von der Geometrie. Beim Rendern projiziert der Rasterizer / Fragment Shader die Textur auf die Oberflaeche des Polygons.

Motivation (Why textures?):

  • Rendering detaillierter Geometrie wird teuer.
  • Modellieren und Animieren detaillierter Geometrie ist aufwendig.
  • Besserer Workflow: Material von Geometrie trennen.
  • Erlaubt das Vorberechnen / Simulieren von Beleuchtung (precompute / simulate lighting).

Roter Faden (OpenGL-Workflow):

  1. Wie werden Koordinaten zugeordnet? (Texturkoordinaten)
  2. Wie wird eine Textur in OpenGL definiert? (glGenTextures ... glTexImage2D)
  3. Wie wird ueber die [0,1]-Grenze hinaus abgetastet? (Wrapping)
  4. Wie wird beim Vergroessern/Verkleinern gefiltert? (Filtering, Mipmaps)
  5. Wie werden mehrere Texturen kombiniert? (Multitexturing)
  6. Wie greift der Shader darauf zu? (Sampler)

Texturkoordinaten (texture coordinates)

Ein Texel (texel, texture element) ist das einzelne Bildelement einer Textur, analog zum Pixel im Ausgabebild. Ueber die Texturkoordinate wird der passende Texel gefunden.

Texturkoordinaten liegen im normierten Bereich

(s,t)[0,1]×[0,1].(s,t) \in [0,1] \times [0,1].

Wichtig (typische Falle): Bei Texturen verwendet man nicht x,yx,y, sondern s,ts,t (oder u,vu,v). Der Wertebereich ist normiert auf [0,1][0,1], unabhaengig von der tatsaechlichen Pixelaufloesung der Textur. Texturkoordinaten sind "just another vertex attribute", also nur ein weiteres Vertex-Attribut. Waehrend der Rasterisierung wird die Koordinate pro Fragment interpoliert.

Die Ablaufkette von der Koordinate zur Farbe hat drei Stufen:

(s,t) parameter space    texture image space (Texel-Index)    texel color(s,t)\ \text{parameter space} \;\longrightarrow\; \text{texture image space (Texel-Index)} \;\longrightarrow\; \text{texel color}

Beispiel (Koordinatenumrechnung): Parameterraum (s,t)=(0.32, 0.29)(s,t) = (0.32,\ 0.29) wird auf den Texel-Index (81, 74)(81,\ 74) abgebildet, dort liegt die Texel-Farbe (0.9, 0.8, 0.7)(0.9,\ 0.8,\ 0.7). (Die zugehoerige Objektraum-Position ist (x,y,z)=(2.3, 7.1, 88.2)(x,y,z)=(-2.3,\ 7.1,\ 88.2).)

Beispiel (8x8-Raster): In einem 8x8-Gitter markiert die normierte Koordinate (2/8, 3/8)(2/8,\ 3/8) die untere linke Ecke der Zelle mit Index (2,3)(2,3). Ecke unten links =(0,0)=(0,0), Ecke oben rechts =(1,1)=(1,1).

Texture Atlas und Wahl der Koordinaten

Ein Texture Atlas (Texturatlas) fasst mehrere Teiltexturen bzw. das gesamte UV-Layout eines Objekts in einem Texturbild zusammen. Aus Effizienzgruenden verwendet man oft ein Mapping pro Objekt (weniger Bindungswechsel).

Texturkoordinaten werden zugewiesen (1) von Hand oder ueber automatische Projektionen oder (2) mit Modellierungswerkzeugen und interaktiver Platzierung.

Wichtig (typische Falle): OpenGL trianguliert Polygone (ein Viereck wird in zwei Dreiecke zerlegt). Dadurch kann die Textur an der Diagonale sichtbar verzerrt (deformiert) erscheinen. Bei der Wahl der Texturkoordinaten beachten.

Texturdefinition in OpenGL

Der Ablauf ist analog zu VBOs: ID generieren, binden, Daten hochladen, spaeter loeschen.

GLuint texID;
glGenTextures(1, &texID);              // ID erzeugen
glBindTexture(GL_TEXTURE_2D, texID);   // Textur aktivieren / binden
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 512, 512, 0, GL_RGBA, GL_FLOAT, mydata);
// ... spaeter:
glDeleteTextures(1, &texID);           // Textur loeschen

Die Argumente von glTexImage2D (aus dem Beispiel S. 16):

ArgumentWertBedeutung
targetGL_TEXTURE_2DTexturziel (auch 1D/3D)
level0Mipmap-Stufe (0 = volle Aufloesung)
internalFormatGL_RGBAwie OpenGL die Textur intern speichert
width, height512, 512Groesse (moeglichst 2m2^m)
border0deprecated, seit OpenGL 3.1 immer 0
formatGL_RGBAFormat des uebergebenen Daten-Arrays
typeGL_FLOATDatentyp im Array (z.B. GL_UNSIGNED_BYTE)
datamydatarohe Texturdaten im Hauptspeicher

Wichtig (typische Falle): internalFormat (interne Speicherung durch OpenGL, z.B. GL_ALPHA, GL_RGB, GL_RGBA, GL_RG16) und format/type (Beschreibung des uebergebenen Client-Arrays) sind verschiedene Dinge und werden gern verwechselt.

Wichtig: glTexImage2D laedt keine Bilddateien ("no image loader"). data sind bereits dekodierte Rohdaten. Das Laden/Dekodieren muss vorher geschehen.

Zweierpotenzen (power-of-two)

Fuer width/height/depth sollte man Werte der Form 2m2^m verwenden. Mipmaps werden immer aus 2n×2n2^n \times 2^n-Texturen erzeugt; bei anderen Groessen skaliert OpenGL intern.

Texturkoordinaten als VBO

Texturkoordinaten sind nur ein weiteres VBO, definiert mit glVertexAttribPointer, aktiviert mit glEnableVertexAttribArray. Der index wird im Shader wieder aufgegriffen: layout(location = index) in vec2 texCoords;.

Texture Wrapping (Wiederholung / Umlauf)

Wrapping regelt, was passiert, wenn Texturkoordinaten ausserhalb [0,1]×[0,1][0,1] \times [0,1] liegen. Gesetzt per glTexParameteri mit GL_TEXTURE_WRAP_{S,T,R} (pro Koordinate s, t, r).

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
ModusVerhalten ausserhalb [0,1]
GL_CLAMP_TO_EDGEWerte <0<0 oder >1>1 werden auf den Rand geklemmt; der Randtexel wird wiederholt / gestreckt
GL_REPEATnur der Nachkommaanteil wird verwendet, die Textur wird gekachelt / wiederholt

Wrapping kann pro Richtung unterschiedlich sein (z.B. REPEAT fuer s, CLAMP_TO_EDGE fuer t): dann wird horizontal gekachelt, vertikal geklemmt.

Texture Filtering (Filterung)

Filterung behandelt das Missverhaeltnis zwischen Texel- und Pixelaufloesung. Es gibt zwei Faelle:

  • Magnification (Vergroesserung): ein Texel wird auf mehr als ein Pixel abgebildet (Textur zu klein / Kamera zu nah).
  • Minification (Verkleinerung): viele Texel werden auf dasselbe Pixel abgebildet (Textur zu gross / Flaeche weit entfernt).

Gesetzt per glTexParameteri mit dem Filtertyp GL_TEXTURE_MIN_FILTER oder GL_TEXTURE_MAG_FILTER.

Filtermodi

  • GL_NEAREST (Point Filter): der naechstgelegene Texel wird verwendet.
  • GL_LINEAR: linearer Durchschnitt der vier naechstgelegenen Texel = bilineare Interpolation.
FallGL_NEARESTGL_LINEAR
MagnificationTexel repliziert, blockig/pixeligweiche Uebergaenge (bilinear)
Minificationexakte Koordinate, Aliasing/Flimmernbilinear, etwas glatter
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);

Wichtig (typische Falle): Nur der MIN_FILTER (Minification) kennt Mipmap-Filtermodi. Der MAG_FILTER (Magnification) akzeptiert ausschliesslich GL_NEAREST oder GL_LINEAR, keine Mipmap-Modi. Einen Mipmap-Modus fuer GL_TEXTURE_MAG_FILTER anzugeben ist ein haeufiger Fehler.

Mipmaps und trilineare Interpolation

Eine Textur ist eine abgetastete Funktion. Korrekt waere, die Farbe eines Ausgabefragments als normalisiertes Integral ueber alle vom Fragment abgedeckten Texel zu berechnen. In der Ferne (Minification) deckt ein Pixel viele Texel ab, und das Nachschlagen aller ist zu rechenintensiv.

Mipmaps approximieren dieses Integral durch vorgemittelte Texel in verschiedenen Aufloesungsstufen (Levels):

  • Level 0 = volle Aufloesung.
  • Jede weitere Stufe hat halbe Kantenlaenge (Breite/Hoehe geteilt durch 2).

Der zusaetzliche Speicher der vollen Mipmap-Pyramide ist gering:

k=0(14)k=43,\sum_{k=0}^{\infty}\left(\tfrac{1}{4}\right)^k = \tfrac{4}{3},

also ca. +1/3 (33%) gegenueber dem Original. Ein 2n2^n-Bild wird intern als 2n+12^{n+1}-Bild dargestellt.

Erzeugung fuer die aktuell gebundene Textur:

glGenerateMipmap(GL_TEXTURE_2D);

(alternativ ueber den level-Parameter von glTexImage2D).

Trilineare Interpolation

Die Minification-Filtermodi haben die Form

GL_{NEAREST,LINEAR}_MIPMAP_{NEAREST,LINEAR}.\texttt{GL\_\{NEAREST,LINEAR\}\_MIPMAP\_\{NEAREST,LINEAR\}}.
  • 1. Teil (vor MIPMAP): Interpolation innerhalb einer Mipmap-Stufe.
  • 2. Teil (nach MIPMAP): Interpolation zwischen zwei Stufen.

Wichtig (typische Falle): Trilinear = bilinear innerhalb einer Stufe plus linear zwischen zwei Stufen = GL_LINEAR_MIPMAP_LINEAR. Der Vorteil von Mipmaps (Anti-Aliasing) ist am deutlichsten bei Bewegung sichtbar (weniger Flimmern).

Multitexturing

Texturen muessen an Texture (Mapping) Units (TMU) auf der GPU gebunden werden. Eine TMU hat eine eigene Texture Engine, um Texturen auf 3D-Geometrie abzubilden. Im Allgemeinen gilt: Anzahl der Texturen \gg Anzahl verfuegbarer Texture Units (m32m \approx 32). Zur Einordnung: eine nvidia RTX 5080 hat 336 TMUs und verarbeitet ca. 879 Gigatexel/s.

Aktivierung: erst die Unit waehlen (glActiveTexture), dann die Textur binden (glBindTexture):

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, myFirstTexture);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, mySecondTexture);

Die GL_TEXTUREi sind fortlaufend, daher ist glActiveTexture(GL_TEXTURE0 + 1) aequivalent zu glActiveTexture(GL_TEXTURE1).

Texturen in den Shadern

Texturierung betrifft beide Shader-Stufen:

  • Vertex Shader: berechnet/reicht Texturkoordinaten durch (an eine out-Variable).
  • Fragment Shader: fragt die interpolierten Koordinaten ab, liest den Farbwert der Textur und verwendet ihn (z.B. fuers Shading).

Im Fragment Shader heisst eine Textur Sampler (sampler1D, sampler2D, sampler3D). Zugriff ueber texture(texID, texCoord).

Vertex Shader:

layout(location = 2) in vec2 texcoords;
out vec2 tc;
// do something with texcoords //
tc = texcoords;

Fragment Shader (additive Kombination zweier Texturen = Multitexturing im Shader):

out vec4 color;
in vec2 tc0;
in vec2 tc1;
uniform sampler2D tex0;
uniform sampler2D tex1;
void main(){
    color = texture(tex0, tc0) + texture(tex1, tc1);
}

Sampler-Uniforms setzen: Texture Unit, nicht ID!

glUniform1i(glGetUniformLocation(programID, "tex0"), 0);
glUniform1i(glGetUniformLocation(programID, "tex1"), 1);

Wichtig (zentrale, explizit hervorgehobene Prüfungsfalle): Das zweite Argument von glUniform1i ist die Texture Unit (hier 0 bzw. 1), NICHT die Textur-ID (der von glGenTextures zurueckgegebene Name). Der Sampler zeigt auf die Unit, an die die Textur zuvor mit glActiveTexture + glBindTexture gebunden wurde.

Worked Example: vollstaendiger Ablauf

Create (Textur erstellen):

GLuint texID;
glGenTextures(1, &texID);                                        // 1. ID erzeugen
glBindTexture(GL_TEXTURE_2D, texID);                             // 2. binden
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 512, 512, 0,
             GL_RGBA, GL_UNSIGNED_BYTE, mydata);                 // 3. Daten senden
glGenerateMipmap(GL_TEXTURE_2D);                                 //    (optional) Mipmaps
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);    // 4. Parameter
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

Usage (Textur verwenden, pro Frame/Draw):

glActiveTexture(GL_TEXTURE0);                                    // 1. Unit aktivieren
glBindTexture(GL_TEXTURE_2D, texID);                            // 2. Textur binden
glUniform1i(glGetUniformLocation(programID, "tex0"), 0);        //    Sampler -> Unit 0
// 3. Texturkoordinaten im Vertex Shader (in -> out)
// 4. texture(tex0, tc) im Fragment Shader

Prüfungsrelevanz

  • s,ts,t / u,vu,v statt x,yx,y, Wertebereich normiert [0,1]×[0,1][0,1] \times [0,1].
  • Ablaufkette parameter space \to image space (Texel-Index) \to texel color nachvollziehen.
  • CLAMP_TO_EDGE vs REPEAT, und dass Wrapping pro Richtung (s, t, r) getrennt gesetzt wird.
  • Magnification vs Minification ueber das Verhaeltnis Texel:Pixel definieren.
  • GL_NEAREST vs GL_LINEAR (bilinear = Mittel der 4 naechsten Texel).
  • Mipmaps: warum (Integral-Approximation), Level 0 = voll, halbe Kantenlaenge pro Stufe, ca. +1/3 Speicher, 2n×2n2^n \times 2^n.
  • Trilinear = GL_LINEAR_MIPMAP_LINEAR; Namensschema GL_{...}_MIPMAP_{...} deuten koennen.
  • MAG_FILTER hat keine Mipmap-Modi.
  • glUniform1i erwartet die Texture Unit, nicht die Textur-ID.
  • Reihenfolge der Create-/Usage-Checklisten (ideale Prüfungsfrage).

Typische Fehler

  • x,yx,y-Notation oder falscher Wertebereich statt s,t[0,1]s,t \in [0,1].
  • Texel (Textur-Element) mit Pixel (Ausgabebild) verwechseln.
  • Mipmap-Filtermodus faelschlich fuer GL_TEXTURE_MAG_FILTER angeben (nur NEAREST/LINEAR erlaubt).
  • Texture Unit vs Textur-ID bei glUniform1i verwechseln (haeufigste Fehlerquelle).
  • internalFormat und format/type gleichsetzen.
  • Vergessen, dass glTexImage2D keinen Bildlader hat (Rohdaten noetig).
  • Nicht-Zweierpotenz-Groessen bei Mipmaps (interne Skalierung).
  • Deformation durch Triangulierung an der Diagonale uebersehen.
  • border 0\neq 0 verwenden (seit OpenGL 3.1 muss er 0 sein).

Glossar

DeutschEnglish
Texturtexture
Texel / Texturelementtexel (texture element)
Texturkoordinaten (s,t / u,v)texture coordinates
UV-MappingUV mapping
Texturatlastexture atlas
Textur-Umlauf / Wiederholungtexture wrapping
auf Rand klemmenclamp to edge (GL_CLAMP_TO_EDGE)
kacheln / wiederholenrepeat (GL_REPEAT)
Texturfilterungtexture filtering
Vergroesserungmagnification
Verkleinerungminification
Naechster-Texel-Filter (Point Filter)GL_NEAREST
bilineare InterpolationGL_LINEAR
Mipmap / Aufloesungspyramidemipmap
Mipmap-Stufemipmap level
trilineare Interpolationtrilinear interpolation (GL_LINEAR_MIPMAP_LINEAR)
Zweierpotenzpower-of-two
Multitexturingmultitexturing
Texture Mapping Unittexture (mapping) unit (TMU)
Samplersampler (sampler2D)
Sampling-Funktiontexture(texID, texCoord)
interne SpeicherforminternalFormat
Format / Datentyp des Arraysformat / type
Textur-ID vs Texture Unittexture ID vs texture unit