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:
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. ProgrammbeschreibungUm 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:
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: 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 BumpmappingNachdem 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: |