Pythonstuff GLSL in English Pythonstuff GLSL auf Deutsch Pythonstuff GLSL Pythonstuff
PythonStuff Home
 

 

Beispiel 4 - GLSL Bumpmap (Normal Map)

Aufbauend auf dem vorigen Beispiel wird die Oberflächen-Normale variiert (Dot3-Bump Mapping). Wie die Normalen zu ändern sind, ist in einer selbst erzeugten Bumpmap hinterlegt.

Das Ergebnis sieht so aus:

Bumpmapped Mapped Torus Bumpmapped Kugel Bumpmapped Kugel (mit einer anderen Textur

Die Texturen befinden sich im Texture Pack 1 und Texture Pack 2 .

Um die Demo ablaufen zu lassen, benötigt man Pyglet und Tristam Macdonald's Modul Shader.py.

Alles findest Du auch auf der Installationsseite.

Programmbeschreibung

Um Dot3-Bumpmapping zu implementieren, soll die Richtung des Normalvektors jedes Oberflächenelements abhängig von einer Bumpmap (oder auch “Normalmap”) geändert werden (mit der entsprechenden Auwirkung auf die Beleuchtungsberechnung).

Eine Bumpmap enthält die Richtungsinformation als RGB-Farbwerte codiert - man kann recht einfach Bumpmaps selbst erzeugen. Eine flache Ebene würde durch Normalenvektoren beschrieben, die gerade nach oben zeigen, in z-Richtung: 0/0/1. Allgemein werden die Vektorkomponenten Werte zwischen -1 und +1 annehmen (genauer: die z-Komponente zwischen 0 und 1, die Normale zeigt niemals in die Oberfläche hinein). Um das als RGB-Wert zu speichern, dessen Komponenten 8 bit breit sind, rechnen wir um:

  1. 1 ⇒ 0, 0 ⇒ 127, 1 ⇒ 255.

Ein Vektor, der gerade nach oben zeigt, hat also den Farbwert R/G/B = 127/127/255: das ist ein Pastell-Blau. Das ist der Grund, warum die meisten Bumpmaps bläulich aussehen. Zur Illustration eine konvexe Halbkugel:

bumpmap_info.jpg

Wir laden das zusätzliche Bild:

texturecnt = 2          # Texturemap0.jpg = Colormap Texturemap1.jpg = Bumpmap

Dann wäre noch eine Taste praktisch, mit der man den Effekt ein- und ausschalten kann (wir nehmen “B”) und machen den jeweiligen Zustand dem Shader Progamm bekannt:

global togglebump
def on_draw():
    ....
    shader.uniformi('togglebump', togglebump )
    ....
def on_key_press(...):
    ....
    elif symbol == key.B:
        togglebump = not togglebump
        print 'Toggle Bumpmap ', togglebump

Nachdem das erledigt ist, kommt die Herausforderung:

bisher war es ausreichend zu wissen, mit welchem Punkt der Textur wir es zu tun haben. Jetzt aber müssen wir wissen, in welche Richtung die Textur auf dem Oberfläche liegt, weil die Bumpmap nicht beliebig verdreht werden kann.

Das nennt man “uv-mapping”, weil wir wissen wollen, in welche Richtung die Textur-Koordinaten (traditionell “u” und “v” genannt) für jede Polygonecke liegen. Dieser Richtungsvektor wird zwischen den Ecken interpoliert. Damit können wir auf jedem Punkt der Oberfläche ein kleines lokales Koordinatensystem aufstellen (dessen “z”-Richtung immer nach oben zeigt) - das ist der berüchtigte “tangent space”.

Wir bringen den Richtungsvektor für “u” in der Vertex-Farbe (gl_Color.rgb) unter. “v” brauchen wir nicht: den “v”-Vektor können wir berechnen, indem wir den Vektor bestimmen, der senkrecht auf “u” und den Normalenvektor “n” (gl_Normal.xyz) steht.

Farbkomponenten haben Werte von 0 bis 255 in OpenGL, also speichern wir den Vektor so:

tangents.extend([ int(round(255 * (0.5 - 0.5 * tx))),
                  int(round(255 * (0.5 - 0.5 * ty))),
                  int(round(255 * (0.5 - 0.5 * tz))) ])

für jede Ecke unseres Polygon-Modells.

Es gibt noch eine weitere Änderung im Beispiel-Programm. Ich habe den eher komplizierten Code für die Kugel (Erinnerung: zwischen “Kugel” und “Torus” wird mit der “F”-Taste umgeschaltet) herausgenommen und durch eine einfachere Lösung ersetzt - dadurch wird das Programm übersichtlicher und auch schneller. Der einzige Nachteil ist die ungleichmässige Grösse der Dreiecke, aus denen sich die Kugel zusammensetzt, aber das ist ein geringer Preis.

Der Code für die Kugelgenerierung ist recht einfach zu verstehen.

GLSL Dot3 Bumpmapping

Nachdem wir den “u”-Vektor in den Polygon-Modellen untergebracht haben, sehen wir uns den Shader an.

Im Vertex-Shader bauen wir das “kleine lokale Koordinatensystem” auf - 3 orthogonale Vectoren:

// Create the Texture Space Matrix
normal    = normalize(gl_NormalMatrix * gl_Normal);
      tangent   = normalize(gl_NormalMatrix * (gl_Color.rgb - 0.5));
      binormal  = cross(normal, tangent);
 mat3 TBNMatrix = mat3(tangent, binormal, normal);

Dieser “T”angent/“B”inormal/“N”ormal Matrix Code ist recht verständlich, wenn man weiss, dass das “Kreuzprodukt” (oder “äusseres” Produkt) den gesuchten Vektor, der 90 Grad zu “u” und “n” steht, liefert.

Um weitere Berechnungen im “tangent space” durchführen zu können, müssen wir leider die Licht- und Auge-Vektoren in dieses Koordinatensystem transformieren. Sorry - klingt furchtbar :-)

vVertex = vec3(gl_ModelViewMatrix * gl_Vertex);
lightDir0 = vec3(gl_LightSource[0].position.xyz - vVertex) * TBNMatrix;
lightDir1 = vec3(gl_LightSource[1].position.xyz - vVertex) * TBNMatrix;
eyeVec    = -vVertex * TBNMatrix;

Sobald alle relevanten Vektoren transformiert sind, können wird die Berechnungen (im Pixel-Shader) wie bisher durchführen.

Die “dot3” Berechnung erfolgt in der Zeile

vec3 norm = normalize( texture2D(my_color_texture[1], gl_TexCoord[0].st).rgb - 0.5);
....
vec3 N = (togglebump != 0) ? normalize(norm) : vec3(0.0, 0.0, 1.0 );

Wir verschaffen uns die Farbe aus der Texturemap1 (0.0 bis 1.0), verschieben sie auf den Bereich -0.5..0.5 und normalisieren (-1.0 to 1.0).

Wenn die “B”umpmap nicht aktiv ist, zeigt der Normalenvektor genau nach oben (x=0,y=0,z=1) - wir erinnern uns, das ist der Zweck des “tangent space” Koordinatensystem !

Mit togglebump != 0 zeigt die Normale in die Richtung, die durch die Bumpmap vorgegeben ist.

Viel Vergnügen !

Aber es gibt noch mehr - sehen wir uns jetzt Beispiel 5 - Parallax Mapping mit GLSL an.

Literatur:


English version, Start
Impressum & Disclaimer