Nodes/ComfyUI-Texture-Simple/AO from Height (PBR)
ComfyUI Node

AO from Height (PBR)

Ambient occlusion without baking — AO from a height map in one node

By gokayfem·Created 2 years ago·Updated about a month ago· 58
AO from Height (PBR)
  • height
  • ambient_occlusion
strength2.00
radius_px12
directions
wrap_edgestrue

Ambient occlusion is the shadow that sells depth - the darkening in creases, corners, and anywhere two surfaces almost touch. Without it a PBR material reads flat, and up to now you had to either bake it in a 3D app or fake it with a multiply. TextureAOFromHeight (listed as "AO from Height (PBR)") does the third thing: it reads the depth out of a height map and generates an AO map from it, entirely on your CPU, with zero model downloads.

It's part of ComfyUI-Texture-Simple, Gökay Aydoğan's offline PBR toolkit. If you've already got a height map (from TextureNormalFromHeight, a displacement source, or even the luminance of your albedo), this node turns it into the supporting map that makes everything else look grounded.

How it works

The node doesn't ray-trace anything. It takes the luminance of your height image, then samples it at a set of offsets - along directions compass points (4, 8, or 16) and at several radii inside radius_px - and compares each shifted copy against the original. Where a shifted sample rises above the local height, it counts as occlusion; deeper offsets contribute less (divided by the square root of the step). All of that is accumulated, scaled by strength, and subtracted from 1.0 so creases go dark and flat areas stay white.

Because it works on offsets rather than geometry, it's cheap - this is a NumPy loop, not a bake. And wrap_edges (on by default) rolls the samples around the border instead of clamping, which means your AO map will tile cleanly right alongside your texture.

The inputs that matter

Only two deserve your attention at first:

  • strength (default 2.0) - how dark the occlusion gets. Start here when it's too subtle or too muddy. The range goes to 10, but past ~4 things start looking burnt.
  • radius_px (default 12) - how far the samples reach. This is in pixels, so scale it with your image resolution: a 2048px texture wants a bigger radius than a 512px one. Crank it up for soft, broad contact shadows.

directions (4/8/16) is a quality knob - 8 is a fine default, 16 only if you see angular stepping. wrap_edges stays on unless you specifically don't want tiling.

How to install

ComfyUI Manager, or by hand:

cd ComfyUI/custom_nodes
git clone https://github.com/gokayfem/ComfyUI-Texture-Simple.git
python -m pip install -r ComfyUI-Texture-Simple/requirements.txt

Restart ComfyUI. Dependencies are just numpy and Pillow - no model files, no network calls, no GPU. You'll find it under texture/PBR.

Wiring it in

Feed a height map (or any image - it uses luminance) into height, and the ambient_occlusion output goes straight into the AO slot of the pack's Texture Viewer Pro, or into the red channel of a TextureChannelPack ORM preset for glTF. There's a subtle trap: this is a screen-space-style approximation of occlusion, not a true baked AO - it reads depth like a heightfield, so deep vertical crevices read fine but overhangs don't. That's the nature of the method, not a bug. If your AO looks harsh, back strength off to ~1 and let the viewer tell you.

Categorytexture/PBR toolkit

Inputs (5)

NameTypeDefaultDescription
heightIMAGE
strengthFLOAT2.000–10
radius_pxINT121–128
directionsCOMBO3 options: 4, 8, 16
wrap_edgesBOOLEANtrue

Outputs (1)

NameTypeDescription
ambient_occlusionIMAGE