Short answer
gdaldem hillshade turns a DEM into a shaded-relief raster by simulating a light source. The output is only trustworthy if three things are right: the DEM is in a projected CRS with matching horizontal and vertical units (so the slope gradients are real), the z-factor correctly reconciles vertical-to-horizontal units, and you have chosen illumination parameters (azimuth, altitude, multidirectional) that suit the terrain. Get those right and the whole thing is a two-command, fully reproducible pipeline: warp, then shade.
What gdaldem hillshade actually computes
Hillshade is the cosine of the angle between the surface normal at each cell and the vector toward a light source. GDAL estimates the surface normal from a 3×3 window of neighbouring cells using a Horn (default) or Zevenbergen–Thorne slope algorithm, then evaluates:
hillshade = 255 × (cos(zenith)·cos(slope) + sin(zenith)·sin(slope)·cos(azimuth − aspect))
Values run 0–255 (0 = full shadow, 255 = fully lit), with 0 typically reserved for NoData. Because the calculation depends on slope and aspect, anything that distorts the gradient — wrong units, wrong CRS, noisy elevations — distorts the shading. The light itself is controlled by two angles:
-azazimuth: compass direction the light comes from. Cartographic default is 315° (upper-left / NW). This convention exists because human perception reads NW-lit relief as raised; lighting from the SE tends to invert the apparent topography (the "relief inversion" illusion).-altaltitude: the light's angle above the horizon, default 45°. Lower altitudes (25–30°) exaggerate subtle relief and lengthen shadows; higher altitudes flatten the image.
The z-factor is where most hillshades go wrong
The z-factor multiplies elevation values so they share units with the planar grid. The logic:
- DEM in metres, grid in metres (e.g. UTM):
z = 1. This is the clean case. - DEM in US survey feet, grid in metres:
z ≈ 0.3048. Without this, vertical relief is overstated ~3.28x and every slope looks like a cliff. - DEM in a geographic CRS (degrees): one degree is not one metre, and the metre-per-degree value changes with latitude. GDAL handles this with
-s(scale): for an EPSG:4326 DEM in metres, a common approximation is-s 111120. This is a workaround, not a fix — scale error grows toward the poles. Reproject to a projected CRS instead.
If your shaded relief looks far too harsh or implausibly flat, check the z-factor before touching the light angles.
A reproducible two-step pipeline
Step 1 — Inspect and reproject
Never assume the DEM's CRS or units. Read them:
gdalinfo dem_source.tif
Look for the CRS, pixel size (Pixel Size), data type (Type=Float32 is what you want), and NoData Value. If the DEM is geographic or in a far-off projection, warp it to the local UTM zone. For a site at ~46°N, 7°E that is UTM zone 32N, EPSG:32632:
gdalwarp \
-t_srs EPSG:32632 \
-r bilinear \
-tr 10 10 \
-dstnodata -9999 \
-co COMPRESS=DEFLATE -co PREDICTOR=2 -co TILED=YES \
dem_source.tif dem_utm32.tif
-r bilinearis appropriate for continuous elevation (usecubicfor smoother results; nevernearfor elevation — it produces the stair-stepping that ruins shading).-tr 10 10sets a deliberate 10 m output resolution; resampling to a wildly different resolution than the source adds noise or loses detail.-dstnodatamakes voids explicit so the shade does not treat them as zero elevation.
Step 2 — Generate the hillshade
gdaldem hillshade \
dem_utm32.tif hillshade.tif \
-z 1 -az 315 -alt 45 \
-compute_edges \
-co COMPRESS=DEFLATE -co TILED=YES
-z 1because both axes are metres now.-compute_edgesshades the outermost row and column instead of leaving a 1-pixel NoData frame — important when you tile or mosaic.- Add
-alg ZevenbergenThornefor smoother terrain; the default Horn algorithm is more robust to noise on rugged DEMs.
Multidirectional for rugged terrain
A single light leaves slopes facing away from it featureless. Multidirectional shading blends illumination from multiple azimuths:
gdaldem hillshade dem_utm32.tif hillshade_multi.tif -multidirectional -compute_edges
This implements the USGS-style multidirectional method (weighted blend of light from ~225°, 270°, 315°, 360°). It recovers detail in steep, multi-aspect terrain at the cost of the crisp directional look. For canyon country and mountains it is usually the better default; for gentle terrain the classic single-direction 315°/45° reads cleaner.
Composite and styling
For presentation, layer the hillshade under a semi-transparent elevation or geology raster. A common recipe in QGIS: load hillshade.tif, set blending mode to Multiply, place a hypsometric-tinted DEM above it at ~50% opacity. To bake a colour-relief blend on the command line:
gdaldem color-relief dem_utm32.tif ramp.txt color.tif
# then blend color.tif over hillshade.tif in your renderer, or with a hillshade-as-alpha approach
Validation
- Visual sanity against known landforms. Ridgelines should be lit on their NW flanks; valleys should fall into shadow. If ridges look like valleys, you have relief inversion — check the azimuth (likely lit from the SE) or a flipped y-axis.
- Histogram check. A healthy hillshade fills most of the 0–255 range. A spike at one value usually means a flat-fill artefact or an integer DEM.
- NoData edges. Open the output and confirm the void areas are NoData, not black (value 0) bleeding into shading.
- Reproducibility. Keep the exact commands in a
Makefileor shell script alongside the source-DEM filename and download date, so the product can be regenerated byte-for-similar from scratch.
Common pitfalls and why they happen
- Banded, stepped relief. The DEM is stored as integers, so each metre of elevation is a flat plateau and the shading carves terraces. Convert to Float32 (
gdal_translate -ot Float32) or source a float DEM. - Stair-stepping after resampling. Nearest-neighbour (
-r near) was used on continuous data. Re-warp withbilinearorcubic. - Over-harsh shading. z-factor too high — usually a feet DEM on a metre grid without
z=0.3048, or a geographic DEM without proper scaling. - A 1-pixel dark border on every tile.
-compute_edgeswas omitted; the edge rows have no full 3×3 neighbourhood and default to NoData. - Inconsistent shading across a wide N–S DEM. It was hillshaded in a geographic CRS, so the degree-to-metre scale drifts with latitude. Reproject first.
Bathyl perspective
A hillshade is the most-shared and least-scrutinised terrain product in any report. We treat it as a derived measurement: reprojected to a low-distortion grid, z-factor verified against the DEM's real units, and produced by a scripted two-step pipeline that records the source DEM and parameters. That way the relief in the figure can be regenerated and defended, not just admired.
Related reading
- GDAL Slope Workflow
- DEM NoData Problems and Edge Effects
- Slope, Aspect, and Hillshade From DEM Data
- Batch Reprojection With GDAL
- Spatial data products