AO from Height (PBR)
Ambient occlusion without baking — AO from a height map in one node
- height
- ambient_occlusion
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.
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| height | IMAGE | — | |
| strength | FLOAT | 2.000–10 | — |
| radius_px | INT | 121–128 | — |
| directions | COMBO | 3 options: 4, 8, 16 | |
| wrap_edges | BOOLEAN | true | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| ambient_occlusion | IMAGE | — |