11.9 - Heightmaps / Displacement Maps

The previous lessons explained how to model the color of a surface using a “mapping”. “Image texture mapping” performs a “table lookup” to retrieve a surface color from a 2D image; “procedural texture mapping” calculates a surface color based on input values. The next several lessons explain how mappings can be used to model other properties of a surface.

This lesson explains how to use a “mapping” to modify the geometry of a surface.

Note:

Technically WebGL 1.0 does not allow texture2D function calls in vertex shaders (only in fragment shaders). If the WebGL programs in this lesson do not work for you, it is probably because your browser does not support texture2D function calls in vertex shaders. To experiment with the WebGL programs, please use a different browser and/or operating system.

Heightmaps

../_images/height_map_example2.png

An example heightmap (1)

A heightmap is a gray scale image that is used to vary the height of a surface. Given a flat plane, the height of a surface that “floats” above the plane is specified by the colors in the gray scale image. The image values, which are intensities in the range [0.0, 1.0], are considered percentages of a maximum height. The example to the right shows a gray scale image (i.e., a heightmap) with a wireframe representation of the surface it represents.

For a gray-scale image all pixel colors have an equal amount of red, green, and blue. Therefore, any of the three component values can be used to represent a height value. The typical image uses eight bits to represent each color value. Therefore, the number of possible height values is limited to 256 (i.e., 28).

The accuracy of a heightmap is dependent on the number of vertices used to define the initial plane. The more subdivisions in the plane, the higher the resolution of the rendered surface. Trade-offs are made to use the least amount of memory for a desired accuracy and resolution.

A heightmap modifies the geometry of a model. Therefore, the work of a heightmap is performed in a vertex shader (not a fragment shader).

Heightmap Example

../_images/diablo_crop.png

A heightmap (3)

The image to the right is used as a heightmap in the following WebGL program. The lighter areas represent high areas of the surface, while the darker areas represent lower areas. Experiment with the resolution of the plane model and study the vertex shader. The vertex shader changes the geometry of the surface as follows:

  • Get the color of the heightmap at a vertex location using its corresponding texture coordinates.
  • Calculate the height of this vertex by using the color value, which is between 0.0 and 1.0, as a percentage to scale the maximum height.
  • Use the height as the y component of the vertex.

The color of the surface is taken from the heightmap to simplify the example and to emphasize the relationship between the heightmap image and the resulting surface. Typically the color of the surface would come from a different texture map.

Show: Code   Canvas   Run Info
../_static/11_heightmap/heightmap.html

Experiment with a heightmap.

Please use a browser that supports "canvas"
Which Model:
Plane (25 vertices; 32 triangles)
Plane (289 vertices; 512 triangles)
Plane (16,641 vertices; 32,768 triangles)
Maximum height: 1.00
0.0 2.0
Display options:
Wireframe model
Animate
Show: Process information    Warnings    Errors
Open this webgl program in a new tab or window

Heightmap Preparation

Here is a brief list of the steps used to create the example WebGL program above.

In Blender:

  1. Create a mesh plane.
  2. Create texture coordinates for the plane. (At the bottom of the “tools menu” (t), in the “Add plane” panel, check the “”Generate UV’s” checkbox. This must be done immediately after creating the object and before any modifications of the model.)
  3. Place the plane in “edit mode” (tab)
  4. Subdivide the plane to a desired resolution.
  5. Assign a material to every face on the plane.
  6. Create a texture map for the material and attach an appropriate gray-scale image.

In Gimp (image editor):

  1. Create (or find) an appropriate image.
  2. Crop the image to make its aspect ratio be 1, or 1/2, 1/4, 1/8, etc.
  3. Re-size the image to have dimension that are a power of two: 2, 4, 8, 16, 32, 64, 128, 512, 1024, etc.
  4. If texture coordinates outside the range [0.0,1.0] will be used, make the image tileable using “Filters” –> “Map” –> “Make Seamless”

Displacement Maps

A heightmap displaces vertices from a flat (planar) surface. A variation on heightmaps is a displacement map, which displaces vertices from a non-planar surface. The direction of displacement follows a vertex’s normal vector. To keep a surface contiguous after displacement, “smooth normal vectors” must be used. Assuming that a “smooth normal vector” has been normalized to unit length, the vector is scaled by a gray-scale value from the displacement map and added to a vertex’s location. As with heightmaps, the accuracy of displacement maps is dependent on the density of its original triangular mesh.

../_images/displacement.png

Displacement of vertices

Displacement maps work well for non-planar surfaces that “bulge outward,” such as a sphere. However, for surfaces that curve “inward” the displacement can cause vertices to invert their relative locations and flip the orientation of triangles. An example is shown in the diagram to the right. Note that an “outward bulge” stretches the area defined by the surface while an “inward bulge” compresses the area. These distortions may be visually noticeable.

To allow for motion along a vector in the opposite direction of the normal vector, the values from a displacement map can be converted to the range [-1.0,1.0] using the formula color*2.0 - 1.0.

Displacement maps can also be used in fragment shaders to offset the location of a fragment before lighting calculations are performed. This does not change the fragment location in the final image, but it can change the color assigned to the fragment and give the illusion of a rough surface.

Displacement Map Example

../_images/rocks.png

rocks.png

The following WebGL program uses a displacement map on a curved surface using the image to the right as the displacement map. Please experiment with the program.

Please study the vertex shader program which changes the geometry of the surface as follows:

  • Get the color of the heightmap at a vertex location using its corresponding texture coordinates.
  • Multiply the color times the maximum displacement to get the displacement for this vertex. (The color value is a percentage in the range [0.0,1.0].)
  • Scale the vertex’s normal vector by the displacement to get a “displacement vector”.
  • Add the displacement vector to the vertex’s location.
Show: Code   Canvas   Run Info
../_static/11_displacement_map/displacement_map.html

A Displacement Map

Please use a browser that supports "canvas"
Non-planar surface: 16,641 vertices & 32,768 triangles.
Maximum height: 1.00
-1.0 1.0
Display options:
Wireframe model
Animate

Use the mouse wheel to zoom in for a closer look.

Show: Process information    Warnings    Errors
Open this webgl program in a new tab or window

Displacement Map Preparation

Here is a brief list of the steps used to create the example WebGL program above.

In Blender:

  1. Create a mesh plane.
  2. Create texture coordinates for the plane. (At the bottom of the “tools menu” (t), in the
    “Add plane” panel, check the “”Generate UV’s” checkbox. This must be done immediately after creating the object and before any modifications of the model.)
  3. Place the plane in “edit mode” (tab)
  4. Subdivide the plane to a desired resolution.
  5. Add curvature to the mesh surface using “proportional editing.”
  6. Assign a material to every face on the plane.
  7. Create a texture map for the material and attach an appropriate gray-scale image.

In Gimp (image editor):

  1. Create (or find) an appropriate image.
  2. Crop the image to make its aspect ratio be 1, or 1/2, 1/4, 1/8, etc.
  3. Re-size the image to have dimension that are a power of two: 2, 4, 8, 16, 32, 64, 128, 512, 1024, etc.
  4. Convert the image to greyscale.
  5. If texture coordinates outside the range [0.0,1.0] will be used, make the image tileable using “Filters” –> “Map” –> “Make Seamless”

A Fun Example

A displacement map can be used to create a realistic model of the earth using publicly available data from NASA. The image below is a greyscale image that represents a heightmap of the earth.

../_images/GDEM-10km-BW.png

GDEM-10km-BW.png [4]

The following WebGL program renders a sphere using the image above as a displacement map. The image has been stretched at the top and bottom to compensate for compression at the north and south poles. (Use the mouse wheel to zoom in for a closer look.)

The Earth using a Displacement Map

Please use a browser that supports "canvas"
Sphere: 32,514 vertices & 65,024 triangles.
Maximum height: 1.00 -1.0 1.0
Animate

Use the mouse wheel to zoom in for a closer look.

Open this webgl demo program in a new tab or window

Summary

A heightmap is a special case of the more general displacement map concept. Use the example vertex shader implemented for displacement mapping for any future WebGL development.

The WebGL examples in this lesson were written to help you understand displacement mapping – not to create fantastic renderings. Displacement mapping is typically combined with other surface property techniques to create life-like renderings.

A disadvantage to heightmaps is the need for a large number of vertices in a model’s triangular mesh, which increases memory requirements and slows rendering.

Glossary

heightmap
Use a value from a texture map as the “height” of a vertex.
displacement map
Use a value from a texture map to offset the location of a vertex along it’s normal vector.

Self Assessment

    Q-488: Where are heightmaps and displacement maps implemented?

  • vertex shader
  • Correct. They change the 3D geometric location of a vertex, which is put into the gl_Position variable.
  • fragment shader
  • Incorrect.
  • both the vertex shader and the fragment shader
  • Incorrect.
  • JavaScript pre-processing code.
  • Incorrect.

    Q-489: Which of the following modifications to a model’s vertices would produce a traditional heightmap?

  • vec3 new_vertex = vec3(a_Vertex[0], height, a_Vertex[2]);
  • Correct. The y-axis component is changed.
  • vec3 new_vertex = vec3(a_Vertex[0], a_Vertex[1], height);
  • Incorrect. The z-axis component is not changed.
  • vec3 new_vertex = vec3(height, a_Vertex[1], a_Vertex[2]);
  • Incorrect. The x-axis component is not changed.
  • vec3 new_vertex = a_Vertex + height;
  • Incorrect. This changes all three components of a vertex.

    Q-490: What conditions might cause a triangle to flip its orientation when a displacement map is used?

  • the underlying mesh caves inward.
  • Correct. When the vertices are projected to a new location their relative position to each other can cause the counter-clockwise ordering of the vertices to flip. This is only important if the vertex ordering is used in the fragment shader.
  • the underlying mesh bulges outward.
  • Incorrect.
  • the underlying mesh has too few vertices.
  • Incorrect.
  • the underlying mesh has too many vertices.
  • Incorrect.

    Q-491: A heightmap and a displacement map can be implemented with the same vertex shader program if what is true?

  • The normal vectors of the underlying mesh always point in the direction of displacement.
  • Correct. For a flat plane, the normal vectors will be pointing straight up from the surface.
  • The underlying mesh is always planar.
  • Incorrect. Displacement maps are defined for any surface, planar or not.
  • The underlying mesh contains a sufficient number of vertices.
  • Incorrect. The number of vertices determines the fine-grain accuracy of the displacements, but not the code implementation.
  • The image used for the mapping is used for both height displacement and the surface’s color.
  • Incorrect. The examples above used the same image for both displacement and color, but only for the purpose of simplifying the examples.
Next Section - 11.10 - Bump / Normal Maps