ComfyUI Node

Cavity Map

The crease detail your PBR set is missing

By criskb·Created 7 months ago·Updated 5 months ago· 0
Cavity Map
  • image
  • source_mask
  • mask
  • image
  • mask
  • cavity_info
settings_json{"source_mode":"luma","polarity":"concave","normalize_mode":"auto_percentile","value_min":0.0,"value_max":1.0,"percentile_low":2.0,"percentile_high":98.0,"radius":2.5,"gamma":1.0,"contrast":1.35,"blur_radius":0.0,"invert_values":false,"mask_feather":8.0,"invert_mask":false}

A cavity map is the short-range cousin of ambient occlusion - it catches the fine detail, the tight creases and pores, that wide-radius AO smears away. x1CavityMap extracts that detail from an albedo, grayscale, height-like, or mask input, and it's one of the handiest derivations in MKRShift Nodes' Surface/Maps branch. The author's own docs sum up the positioning neatly: it's tighter and more detail-focused than x1AOFromHeight.

The pack overall is young (no impressions, no community chatter I could find), but these map nodes are its most self-consistent section - each one is a "derive a scalar, normalize it, mask it" pipeline that feeds the same downstream pack nodes, so once you've used one, the rest feel familiar.

How it works

Three steps, straight from the author's docs: sample a grayscale source from the chosen channel or mask, compare each pixel against a blurred local average, and extract the difference as your cavity signal. polarity picks what you keep - concave favors dark creases (the classic cavity/dirt response), convex favors bright ridges, and both gives unsigned micro-detail. radius (default 2.5) sets the neighborhood size for the comparison, so it stays short-range by design. Then the usual shaping controls - normalize_mode, gamma, contrast - turn the raw signal into a usable map.

Outputs: image (grayscale map as an image), mask (the scalar map itself), and cavity_info (resolved settings + the normalization range used).

What it's for

The author's list is the right list: a roughness driver (creases read rougher), a wear mask seed (dirt collects in cavities), or an extra channel into x1PBRPack. It's also a nice fast sanity-check on a generated heightmap before you commit to baking.

Inputs that matter

  • polarity - concave is the default and the one you'll reach for first; both is there when you want unsigned detail
  • radius - keep it small; this node's whole point is short-range
  • contrast (default 1.35) - the main "how punchy is the detail" knob
  • source_mode - luma is the sane default; the other channels are for when the creases live in a specific color channel

Installing

ComfyUI Manager → search MKRShift Nodes, or:

cd ComfyUI/custom_nodes
git clone https://github.com/criskb/MKRShift_Nodes

Restart ComfyUI. No model downloads, no new Python deps.

Where people get burned

  • Using it where you want AO. x1AOFromHeight is the broad contact-shadow node; x1CavityMap is the fine-detail one. People reach for cavity first because it's impressive, then wonder why the broad occlusion is missing. They're a pair, not replacements.
  • The mask output is the derived cavity scalar - the optional mask input (with mask_feather) only gates where the output is emitted. Pack-wise, don't wire the output mask back into the input mask expecting a loop to work.
  • A corrupted settings_json silently falls back to defaults, so if your map suddenly goes generic, that's the first suspect.

Feed it a decent albedo and it'll hand you a roughness/wear channel in a second. That's the whole pitch.

CategoryMKRShift Nodes/Surface/Maps

Inputs (4)

NameTypeDefaultDescription
imageIMAGE
settings_jsonSTRING{"source_mode":"luma","polarity":"concave","normalize_mode":"auto_percentile","value_min":0.0,"value_max":1.0,"percentile_low":2.0,"percentile_high":98.0,"radius":2.5,"gamma":1.0,"contrast":1.35,"blur_radius":0.0,"invert_values":false,"mask_feather":8.0,"invert_mask":false}
source_maskoptMASK
maskoptMASK

Outputs (3)

NameTypeDescription
imageIMAGE
maskMASK
cavity_infoSTRING