Heightmap
Steal displacement-ready grayscale from anything
- image
- source_mask
- mask
- image
- mask
- heightmap_info
Heightmap is the node that turns any image - or just a channel of it - into the clean grayscale displacement map you'd feed a renderer, a normal-map baker, or a displacement-capable pass. It's the Surface/Maps sibling of x1Heatmap, minus the colors and plus a couple of map-specific knobs. If you've been opening images in Photoshop and manually desaturating + leveling to fake a heightmap, this does it deterministically, in the graph, with one node.
It sits in MKRShift Nodes/Surface/Maps, the pack's tech-art lane for deriving practical PBR-adjacent maps. Same family as x1IridescenceMap and the material nodes: "pull a scalar map out of pixels, normalize it, hand it to the next stage." No models, no downloads - just numpy math that runs in milliseconds.
How it works
Same skeleton as x1Heatmap: pick a source_mode (luma, red, green, blue, max_rgb, saturation, value, alpha, or mask), normalize, then output as grayscale instead of palette-mapped color.
The map-specific extras:
contrast- scales the grayscale ramp after normalization. This is your "does this displacement actually displace" control. A flat, low-contrast map will barely move geometry; crank contrast and the valleys get deeper.blur_radius- smooths the map before output. Genuinely useful here in a way it isn't for preview tools: tiny displacement maps amplify noise into spikes, and a 1–3 px blur is the cheap, honest de-spiker.normalize_modewithmanual_range,auto_range,auto_percentile- same trio as the heatmap.auto_percentile(default 2–98) is the steady choice for a batch of frames; manual range if you need the map to mean the same thing everywhere.
gamma, invert_values, and mask_feather/invert_mask behave exactly as in the heatmap. One difference: Heightmap's optional inputs are source_mask and mask - the first restricts what gets analyzed, the second scissor-limits the final map.
Outputs
All three outputs are worth wiring somewhere: image is the grayscale heightmap, mask is the same values as a MASK tensor (some nodes downstream prefer masks to images), and heightmap_info reports the resolved source, the min/max the normalizer settled on, and mask coverage - read it when your map looks unexpectedly black or blown.
Installing it
Pack standard, no surprises:
cd ComfyUI/custom_nodes
git clone https://github.com/criskb/MKRShift_Nodes
then restart ComfyUI, or grab it via ComfyUI Manager by searching MKRShift_Nodes. No pip requirements, no model files for the map nodes.
Where people get burned
The most common mistake is assuming luma is always the right source. For displacement you often want value or a single channel, because luma's built-in color weighting squashes contrast that one channel preserves. And remember the output is already normalized - if you chain it into a normal-map generator that normalizes again, you're double-stretching and will flatten your shading. When in doubt, read heightmap_info; it tells you exactly what range the map left the node with.
Inputs (15)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| source_mode | COMBO | 9 options: luma, red, green, blue, max_rgb, saturation, +3 | |
| normalize_mode | COMBO | 3 options: manual_range, auto_range, auto_percentile | |
| value_min | FLOAT | 0.0000–1 | — |
| value_max | FLOAT | 1.0000–1 | — |
| percentile_low | FLOAT | 2.00–100 | — |
| percentile_high | FLOAT | 98.00–100 | — |
| gamma | FLOAT | 1.000.1–4 | — |
| contrast | FLOAT | 1.000.1–4 | — |
| blur_radius | FLOAT | 0.00–128 | — |
| invert_values | BOOLEAN | false | — |
| mask_feather | FLOAT | 8.00–256 | — |
| invert_mask | BOOLEAN | false | — |
| source_maskopt | MASK | — | |
| maskopt | MASK | — |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |
| mask | MASK | — |
| heightmap_info | STRING | — |