Beispiel 5 - GLSL Parallax Mapping mit PygletDas 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: Das Ergebnis sieht so aus (links mit, rechts 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 sieht am besten in der Bewegung aus, aber hier ist noch ein Versuch, den Effekt sichtbar zu machen: 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 BeschreibungUm 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:
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. |