Short answer
A good hillshade in QGIS comes down to four decisions: the light azimuth (315 degrees by convention), the sun altitude (45 degrees), the vertical exaggeration (Z-factor), and whether your DEM's horizontal and vertical units agree. Get the units right and the rest is fine tuning. The single most common failure — a flat or all-black result — is almost always a Z-factor or CRS-unit mismatch, not a styling problem.
This guide covers the QGIS Hillshade algorithm and gdaldem hillshade directly, with the parameter values that actually matter for geological and terrain mapping, plus how to blend a hillshade under a coloured DEM without muddying either layer.
What a hillshade actually computes
A hillshade is a grayscale raster where each pixel's brightness is the cosine of the angle between the terrain surface normal and a vector pointing at a hypothetical sun. QGIS and GDAL both compute the surface normal from a 3x3 window of elevation values using Horn's method (the same algorithm ESRI uses), deriving the local slope and aspect, then applying the standard illumination equation:
hillshade = 255 * ((cos(zenith) * cos(slope))
+ (sin(zenith) * sin(slope) * cos(azimuth - aspect)))
where zenith = 90 - altitude. The output is 8-bit, 0–255. Because brightness depends on the angle between slope and light, the absolute elevation never appears — only the local gradient does. That is why two DEMs with identical terrain but different vertical units can produce wildly different shading.
The parameters that matter
Azimuth and altitude
Azimuth is the compass direction of the light, measured clockwise from north. The cartographic standard is 315 degrees (northwest). This is not arbitrary: the human visual system assumes light comes from above and slightly left, so northwest lighting reads as raised terrain. Lighting from the southeast (135) triggers relief inversion — ridges appear as valleys and vice versa. If a client says your mountains "look like canyons," check the azimuth first.
Altitude (sun elevation above the horizon) defaults to 45 degrees. Lower altitudes (25–35) exaggerate subtle features and are useful for spotting faults and lineaments, but they push large areas into pure black shadow. Higher altitudes (55–65) flatten the image. For a base relief layer, stay near 45; for structural interpretation, render a second hillshade at a low altitude.
Z-factor — the parameter that breaks most hillshades
The Z-factor multiplies elevation before slope is computed. Its only legitimate uses are vertical exaggeration and unit reconciliation.
- Projected CRS, metres horizontal and vertical (e.g. UTM, EPSG:32633): Z-factor = 1.0. No correction needed.
- Projected CRS, feet horizontal, metres vertical (or any mismatch): set Z-factor to the conversion ratio, e.g. 0.3048.
- Geographic CRS, degrees horizontal, metres vertical (e.g. EPSG:4326): this is the classic trap. A degree of longitude is roughly 111,320 m at the equator but shrinks with latitude. You must scale by
1 / (111320 * cos(latitude)). At 45 degrees latitude that is about1 / 78700 ≈ 1.27e-5. Forget this and your hillshade looks completely flat because the algorithm thinks one pixel is a degree wide and a few hundred metres tall — an essentially flat plane.
The cleaner fix is to reproject the DEM to a projected CRS in metres before shading, which makes Z-factor = 1 and avoids the latitude dependence:
gdalwarp -t_srs EPSG:32633 -r bilinear dem_4326.tif dem_utm.tif
Worked example: gdaldem and the QGIS algorithm
For a UTM DEM in metres, the GDAL command is:
gdaldem hillshade dem_utm.tif hs.tif \
-az 315 -alt 45 -z 1.0 \
-compute_edges -of GTiff \
-co COMPRESS=DEFLATE -co PREDICTOR=2 -co TILED=YES
-compute_edges is worth setting every time: without it, the one-pixel border of the output is set to NoData because the 3x3 window runs off the edge. -multidirectional swaps the single-light model for a weighted combination of light from 225, 270, 315 and 360 degrees — excellent for revealing detail in shaded slopes. Note that -multidirectional and -az are mutually exclusive.
If your DEM is still in geographic degrees and you cannot reproject, pass -s 78700 (the scale at ~45 degrees latitude) instead of computing a tiny Z-factor by hand; gdaldem divides the elevation difference by scale internally.
In the QGIS GUI the same operation lives under Raster ▸ Analysis ▸ Hillshade (or Processing algorithm gdal:hillshade). The native QGIS Processing algorithm Hillshade (qgis:hillshade) exposes Z-factor, azimuth and vertical-angle as parameters and respects the layer CRS. Either is fine; GDAL is faster on large tiles and scriptable, the native one integrates with the Graphical Modeler.
Blending a hillshade under a coloured DEM
A grayscale hillshade alone is rarely the deliverable. The standard cartographic move is to drape a semi-transparent colour relief (a hypsometric ramp) over the hillshade, or vice versa. In QGIS:
- Load the hillshade as the bottom layer, the coloured DEM (Singleband pseudocolor, e.g. a terrain ramp) above it.
- Open the colour layer's Symbology ▸ Layer Rendering and set Blending mode to Multiply, or set the colour layer opacity to ~50–60% and the blending to Normal.
- Multiply darkens where the hillshade is dark and preserves hue elsewhere, giving natural shaded relief without washing out the colours.
An alternative is GDAL's gdaldem color-relief plus a manual composite, but the QGIS blending-mode approach is non-destructive and lets you tune live. Avoid stacking two opaque layers — you will only ever see the top one.
Common pitfalls and why they happen
- All-black output. Usually a low sun altitude on steep terrain, or QGIS applied a min/max stretch that clipped most pixels. Set the hillshade render to Singleband gray with min 0, max 255, no stretch.
- Flat output. Z-factor too small relative to units — the degrees-vs-metres case above. Reproject or scale.
- Banding / terracing. The source DEM was stored as integer metres (1 m vertical resolution) over gentle terrain, so slope is quantised. Resample or smooth the DEM before shading; do not blame the hillshade.
- Seams at tile edges. Mosaicking hillshades after computing them creates visible seams because edge pixels differ. Mosaic the DEM first, then shade once.
- Striping in airborne lidar DEMs. Flight-line artefacts are invisible in the raw elevation but a low-altitude hillshade exposes them instantly. This is a QA feature, not a bug — it tells you the DEM needs cleaning.
Quality checks before delivery
- Confirm the DEM CRS and that horizontal/vertical units agree before shading.
gdalinfo dem.tifshows the CRS and any vertical datum. - Inspect the histogram of the output: a healthy hillshade fills most of the 0–255 range. A spike at 0 means too much terrain is in shadow.
- Compare relief direction against a known landmark — a river should sit in the dark side of its valley walls.
- Render at the intended print scale, not just zoomed in; relief that pops at 1:5,000 can vanish at 1:50,000, where a slightly lower altitude or higher Z-factor helps.
Bathyl perspective
We treat the hillshade as a measured product, not a decoration. Before shipping one we record the source DEM, its CRS, the azimuth/altitude/Z-factor used, and whether multidirectional shading was applied, so the relief can be reproduced exactly. That discipline matters most when a hillshade is doing analytical work — surfacing faults, scarps or lidar artefacts — rather than sitting behind a basemap.
Related reading
- QGIS Slope Analysis Workflow
- QGIS Georeferencing Scanned Maps
- QGIS Processing Modeler for Repeatable Workflows
- GIS and spatial analysis