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

 

Beispiel 5 - GLSL Parallax Mapping mit Pyglet

Das vorige Beispiel weiterentwickelt führt uns zu Parallax Mapping.

Was ist Parallax Mapping ?

Aus Wikipedia:

Parallax mapping is implemented by displacing the texture coordinates at a point on the rendered polygon by a function of the view angle in tangent space (the angle relative to the surface normal) and the value of the height map at that point. At steeper view-angles, the texture coordinates are displaced more, giving the illusion of depth due to parallax effects as the view changes.

Die Textur-Koordinaten werden also verschoben, da durch die Parallaxe ein erhöhter Teil der Oberfläche gegenüber dem Untergrund verschoben erscheint.

Ein Bild sagt hier vielleicht mehr:

parallax_method.jpg

Das Ergebnis sieht so aus (links mit, rechts ohne Parallax Mapping):

 mit Parallax Mapping  ohne Parallax Mapping

Die roten Spitzen der Pyramiden sind im linken Bild, wenn sie schräg zum Betrachter liegen, deutlich aus der Mitte ihrer Grundfläche gerückt - ein Effekt, den Texturemapping alleine (auch Bumpmapping ändert nur die Helligkeit - siehe rechtes Bild) nicht bewirken kann.

Noch besser sehen die Pyramiden aus, wenn sie nach innen stehen (konkav) - dann irritiert die fehlende Silhouette nicht:

Parallax Mapping einer Oberfläche mit konkaven Unebenheiten

Parallax mapping sieht am besten in der Bewegung aus, aber hier ist noch ein Versuch, den Effekt sichtbar zu machen:

ziegel_mit_parallaxe.jpg ziegel_ohne_parallaxe.jpg

Im linken Bild kann man den Zement zwischen den Ziegeln nur in der Bildmitte sehen, unter einem schrägen Betrachtungswinkel wird das Grau von den Ziegeln verdeckt.

Im rechten Bild ist der Parameter “parallax height” auf Null gesetzt und damit die Ziegel einfach “bump mapped”, der Zement bleibt überall sichtbar.

Die Texturen für die Konkave Version sind hier: Texture Pack 3.

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

Alles findest Du auch auf der Installationsseite.

Programm Beschreibung

Um den Effekt (und seine Grenzen) genau sehen zu können, habe ich “Zoom” Tasten eingebaut:

global dist
def on_draw():
    ....
    glTranslatef(0.0, 0.0, dist); # statt dem fixen Wert "-3.5"
    ....
def on_key_press(...):
    ....
    elif symbol == key.PLUS:
        dist += 0.5
        print 'Distance now ', dist
    elif symbol == key.MINUS:
        dist -= 0.5
        print 'Distance now ', dist

Wir brauchen auch noch einen Wert um die Effektstärke einzustellen:

global parallaxheight
def on_draw():
    ....
    shader.uniformf('parallaxheight', parallaxheight )
    ....
def on_key_press(...):
    ....
    elif symbol == key.P:
        parallaxheight += 0.01
        print 'Parallax Height now ', parallaxheight
    elif symbol == key.O:
        parallaxheight -= 0.01
        if parallaxheight <= 0.0:
            parallaxheight = 0.0
            print 'Parallax now OFF'
        else:
            print 'Parallax Height now ', parallaxheight

und dann noch eine weitere Textur, die “height texture”. Das ist ein 8-bit-Graustufenbild, “schwarz” bedeutet “tief” und “weiß” bedeutet “höher”:

texturecnt = 3          # Texturemap0.jpg = Colormap Texturemap1.jpg = Bumpmap Texturemap2.jpg = Heightmap

Das ist alles, was im umgebenden Python Programm erforderlich ist, wir können uns auf den GLSL-Effekt im Pixel Shader konzentrieren. Für ein Pixel nur mit Bump-Mapping haben wir die Colormap gelesen:

vec4 texColor = vec4(texture2D(my_color_texture[0], gl_TexCoord[0].st).rgb, 1.0);

Jetzt holen wir uns statt dessen die “Höhe” an dieser Position:

vec2 coords1 = gl_TexCoord[0].st;
float height1 = parallaxheight * (texture2D( my_color_texture[2], coords1).r - 0.5);

Der Wert -0.5..+0.5 (nach dem Skalieren mit 0.5) aus der Heightmap wird mit unserem Wert “parallaxheight” multipliziert - wenn wir “parallaxheight” auf Null setzen, indem wir wiederholt “O” drücken, bekommen wir ein “height1” von Null, was soviel wie “nur Bumpmap, kein Parallaxe-Effekt” bedeutet (ausprobieren!).

Wir lesen jetzt die Farbe nicht an der Stelle gl_TexCoord[0].st, also nicht

vec4 texColor = vec4(texture2D(my_color_texture[0], gl_TexCoord[0].st).rgb, 1.0);

sondern von gl_TexCoord[0].st + offset1, also

offset1  = height1 *  vec2( eye.x, eye.y );
vec4 texColor = vec4(texture2D(my_color_texture[0], gl_TexCoord[0].st + offset1).rgb, 1.0);

wo der Ort in Augenvektor-Richtung (auf die Texturebene projiziert) verschoben ist und zwar proportional zur Höhe, die wir gerade ausgerechnet haben.

Das folgt in etwa der Original-Implementierung von Tomomichi Kaneko aus dem Jahr 2001. Seither hat es zahlreiche Verbesserungsvorschläge gegeben:

  • Die Verschiebung wird begrenzt, weil sie unter pathologischen Winkeln recht gross werden kann
  • Man sucht entlang der Sichtlinie nach höheren Stellen in der Heightmap, um eine eventuelle Verdeckung korrekt darzustellen (“Steep parallax mapping”)
  • Man kann diese Berechnung zusätzlich zum Augenvektor auch für den Lichtvektor durchführen und so sehr detaillierte Schatten berechnen
  • Man kann die Genauigkeit des Schnittpunktes durch Interpolation verbessern

und vieles mehr.

In meinem Programm habe ich versucht, die Suche nach dem Schnittpunkt dadurch zu verbessern, dass ich zwischen Höhenstufen einen Interpolationsschritt berechne. Damit reduziere ich Bildfehler (ein bisschen), die bei schnellen Höhenänderungen auftreten würden:

vec2  coords1 = gl_TexCoord[0].st;
vec2  coords2  = coords1 + offset1;
float height2 = parallaxheight * (texture2D( my_color_texture[2], coords2).r - 0.5);
vec2  newCoords = coords2;
vec2  newCoords = coords1 + (height2/height1) * offset1;
vec4  texColor = vec4(texture2D(my_color_texture[0], newCoords).rgb, 1.0);

Viel Vergnügen!

Wenn Du es genug genossen hast, sollten wir zu Beispiel 6 - Vertex Offset Shader für deformierbare Objekte weitergehen.


English version, Start
Impressum & Disclaimer