AO From Height
Ambient occlusion from a heightmap, with no bake farm
- image
- source_mask
- mask
- image
- mask
- ao_info
Ambient occlusion is the difference between a flat material and one that sits in its own shadows - the darkening in creases, under edges, where surfaces almost touch. Normally you bake that in a DCC tool or a real renderer. x1AOFromHeight approximates it from a heightmap in a fraction of a second, which makes it the "good enough" AO for roughing out materials in ComfyUI without leaving the graph.
It's from MKRShift Nodes' Surface/Tech Art branch, and it's worth saying plainly what the author says in the docs: this is a fast artist-driven approximation, not a physically accurate ray-traced solve. It will not win you a lighting bake. It will give you a believable occlusion map for preview, wear, and packing workflows in seconds.
How it works
You feed it a height-like source and it compares each pixel against its local neighborhood over a radius to figure out where the surface recedes - that's the crease signal that becomes occlusion. source_mode decides what counts as height: luma, any RGB channel, value, alpha, or an external source_mask. output_mode lets you choose between ao (bright-open / dark-occluded - the classic) and cavity (the raw cavity signal, for tighter detail-driven work). intensity scales the result, gamma shapes it, invert_height flips the source if your heightmap is inverted. Then normalize_mode (with auto_percentile as the sane default, or manual_range when you need a locked 0–1 mapping) turns the raw signal into a usable map.
Outputs: image (grayscale AO/cavity map, as an image), mask (the scalar map itself - the thing you actually pack or wire onward), and ao_info (settings + the normalization range that was actually used, which is genuinely useful for matching two maps to the same range).
Inputs that matter
source_mode+invert_height- a dark-on-light heightmap vs. light-on-dark flips the whole result; get these right firstradius- neighborhood size; small gives tight crease AO, large gives broad contact shadowsintensity- how hard the occlusion pushesoutput_mode-aofor general occlusion,cavitywhen you want the detail pass
Installing
ComfyUI Manager → search MKRShift Nodes, or:
cd ComfyUI/custom_nodes
git clone https://github.com/criskb/MKRShift_Nodes
Restart ComfyUI. No models, no new dependencies - this is numpy/PIL math on the CPU.
Where people get burned
- Expecting physically-correct AO. It's an approximation. It's for lookdev speed, not for final renders where your renderer's own AO is available.
- Flipped height - a white-on-black heightmap with
invert_heightoff produces inverted occlusion. If your creases come out bright, flip it. - The
maskoutput is the derived scalar map; the optionalmaskinput (withmask_feather/invert_mask) only limits where the output is emitted. Pack-wise, both use the word "mask" and they are not the same thing.
Where it earns its keep: feeding AO into x1PBRPack, seeding wear masks, or checking whether a generated heightmap will hold up before you commit to a real bake. Fast, free, in-graph.
Inputs (16)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| source_mode | COMBO | 7 options: luma, red, green, blue, value, alpha, +1 | |
| output_mode | COMBO | 2 options: ao, cavity | |
| normalize_mode | COMBO | 3 options: auto_percentile, manual_range, auto_range | |
| value_min | FLOAT | 0.000-1–1 | — |
| value_max | FLOAT | 1.000-1–1 | — |
| percentile_low | FLOAT | 2.00–100 | — |
| percentile_high | FLOAT | 98.00–100 | — |
| radius | FLOAT | 8.00–128 | — |
| intensity | FLOAT | 2.000–8 | — |
| gamma | FLOAT | 1.000.1–4 | — |
| invert_height | 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 | — |
| ao_info | STRING | — |