Example 5 - GLSL Parallax Mapping with PygletBased on the previous example we do Parallax Mapping. What is parallax mapping ?As Wikipedia says: 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. Or - as a picture says more: The result looks like this: In the left picture the red pyramid tops look shifted sideways when they are not oriented towards the viewer. This effect can never be realized by texture mapping alone (even bump mapping just changes the lighting - see the picture to the right for comparison). Even better do the pyramids look if they are upside down (concave) - so you are not irritated by the missing silhouette: Parallax mapping looks best in movement, but here is another one: In the left screenshot you can see the cement between the bricks only in the center of the picture - under steeper angles the bricks hide the cement. In the right screenshot the “parallax height” parameter is set to zero, so you see just the “bump mapped” bricks.
The textures I use in this example are in the Texture Pack 3. To run this demo, you need Pyglet and Tristam Macdonald's Library Shader.py. You find everything on the installation page. Program descriptionTo properly view this effect (and its limits), I added a “zoom” keyboard function: global dist def on_draw(): .... glTranslatef(0.0, 0.0, dist); # instead of fixed "-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 We also need a value for scaling the effect: 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 then add another texture, the “height texture”. This is an 8bit-greyscale image, “black” meaning “lowest” and “white” means “highest”: texturecnt = 3 # Texturemap0.jpg = Colormap Texturemap1.jpg = Bumpmap Texturemap2.jpg = Heightmap That is all there is in the surrounding Python program, so we can concentrate on the GLSL-Effect in the pixel shader. For a bumpmapped pixel we sampled the Colormap: vec4 texColor = vec4(texture2D(my_color_texture[0], gl_TexCoord[0].st).rgb, 1.0); Now we look at the “height” at the position: vec2 coords1 = gl_TexCoord[0].st; float height1 = parallaxheight * (texture2D( my_color_texture[2], coords1).r - 0.5); The value -0.5..+0.5 (after scaling) sampled from the heightmap is multiplied by our value “parallaxheight” - if we set “parallaxheight” to zero by pressing “O” repeatedly, we get a “height1” of zero, meaning “just bumpmap, no parallax effect” (try it !). Now we do not select the color pixel from gl_TexCoord[0].st as in vec4 texColor = vec4(texture2D(my_color_texture[0], gl_TexCoord[0].st).rgb, 1.0); but from gl_TexCoord[0].st + offset1 as in offset1 = height1 * vec2( eye.x, eye.y ); vec4 texColor = vec4(texture2D(my_color_texture[0], gl_TexCoord[0].st + offset1).rgb, 1.0); where the place of sampling is moved away in eye direction (projected to the texture) proportional to the height we just calculated. This follows the original implementation of Tomomichi Kaneko from 2001. There have been various improvements since then.
and more. In my source file I tried to improve the intersection search by doing one interpolation step between height values. So I reduce the artefacts introduced by too steep height changes (a bit): 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); Enjoy ! If you have enjoyed this enough, let's get continue with Example 6 - Vertex Offset Shader for deformable Objects. |