Heightmap Meshing August 2019
Convert any grayscale heightmap into a 3D triangle mesh.
This is a modern implementation of a nice algorithm from the 1995 paper Fast Polygonal Approximation of Terrains and Height Fields by Garland and Heckbert. The meshes produced by
hmm satisfy the Delaunay condition and can satisfy a specified maximal error or maximal number of triangles or vertices. It's also very fast!
If you've done any 3D game development, 3D printing, or other such things,
you've likely wanted to convert a grayscale heightmap image into a 3D mesh. The
naive way is pretty simple but generates huge meshes with millions of
triangles. After hacking my way through various solutions over the years, I
finally decided I needed to write a good tool for this purpose.
- C++11 or higher
brew install glm # on macOS sudo apt-get install libglm-dev # on Ubuntu / Debian git clone https://github.com/fogleman/hmm.git cd hmm make make install
heightmap meshing utility usage: hmm --zscale=float [options] ... infile outfile.stl options: -z, --zscale z scale relative to x & y (float) -x, --zexagg z exaggeration (float [=1]) -e, --error maximum triangulation error (float [=0.001]) -t, --triangles maximum number of triangles (int [=0]) -p, --points maximum number of vertices (int [=0]) -b, --base solid base height (float [=0]) --invert invert heightmap --blur gaussian blur sigma (int [=0]) --border-size border size in pixels (int [=0]) --border-height border z height (float [=1]) --normal-map path to write normal map png (string [=]) -q, --quiet suppress console output -?, --help print this message
hmm supports a variety of file formats like PNG, JPG, etc. for the input
heightmap. The output is always a binary STL file. The only other required
-z, which specifies how much to scale the Z axis in the output
hmm input.png output.stl -z ZSCALE
You can also provide a maximal allowed error, number of triangles, or number of vertices. (If multiple are specified, the first one reached is used.)
hmm input.png output.stl -z 100 -e 0.001 -t 1000000
Click on the image below to see examples of various command line arguments. You can try these examples yourself with this heightmap: gale.png.
-z parameter defines the distance between a fully black pixel
and a fully white pixel in the vertical Z axis, with units equal to one pixel
width or height. For example, if each pixel in the heightmap represented a 1x1
meter square area, and the vertical range of the heightmap was 100 meters, then
-z 100 should be used.
-x parameter is simply an extra multiplier on top of the provided Z
scale. It is provided as a convenience so you don't have to do multiplication
in your head just to exaggerate by, e.g. 2x, since Z scales are often derived
from real world data and can have strange values like 142.2378.
-e parameter defines the maximum allowed error in the output mesh, as a
percentage of the total mesh height. For example, if
-e 0.01 is used, then no
pixel will have an error of more than 1% of the distance between a fully black
pixel and a fully white pixel. This means that for an 8-bit input image, an
e = 1 / 256 ~= 0.0039 will ensure that no pixel has an error greater
than one full grayscale unit. (It may still be desireable to use a lower value
0.5 / 256.)
-b option is used to create a solid mesh, it defines the height of
the base before the lowest part of the heightmesh appears, as a percentage of
the heightmap's height. For example, if
-z 100 -b 0.5 were used, then the
final mesh would be about 150 units tall (if a fully white pixel exists in the
A border can be added to the mesh with the
--border-height flags. The heightmap will be padded by
before triangulating. The (pre-scaled) Z value of the border can be set with
border-height which defaults to 1.
A Gaussian blur can be applied with the
--blur flag. This is particularly
useful for noisy images.
The heightmap can be inverted with the
--invert flag. This is useful for
A full resolution normal map
can be generated with the
--normal-map argument. This will save a normal map
as an RGB PNG to the specified path. This is useful for rendering higher
resolution bumps and details while using a lower resolution triangle mesh.