Height to Normal Converter
Your height map wants to be a normal map. Let it.
- height
- normal
- format_info
Here's a trick a lot of people don't realize until they're elbow-deep in material extraction: you almost never need to generate a normal map from scratch. If you have a height map, the normal map is just the gradient of that height - a couple of derivative filters and you're done. That's exactly what Height to Normal Converter does, and it's one of the more useful nodes in the TextureAlchemy pack for the plain reason that most AI depth pipelines hand you height, not normals.
In the pack's own recommended workflow it's placed right after the Lotus height output, generating normals from the same data so the material set has both maps without running a second model. That's the whole pitch: free normals, GPU-fast, no extra checkpoint, no VRAM tax.
How it works
The node takes a height image, drops it to grayscale, and runs a gradient operator across it - Sobel, Scharr, or Prewitt - to compute how much the surface rises in x and y at every pixel. Those two slopes become the red and green channels of the normal map; blue encodes "facing the camera," which the math sets automatically. It's the textbook approach, and the source even labels the operators honestly: scharr is the sharpest default, sobel is the standard balanced pick, prewitt is the smooth/simple option. The strength slider scales the slopes before the blue channel is rebuilt - crank it past 1 to exaggerate bumps, drop it toward 0 to flatten.
Two inputs matter beyond that. blur_radius (0–5) pre-blurs the height map with a Gaussian, which is your noise killer: AI depth maps are noisy, and that noise becomes spikes in the normal map, so a small blur radius is often the difference between usable and garbage. And output_format picks which way "up" points - OpenGL for Unity/Blender, DirectX for Unreal/Maya. This is the single most common source of "why does my lighting look inverted" confusion, and it's a dropdown, so you have no excuse.
Outputs are normal (the map) plus a format_info STRING that tells you what format it was written as - handy when you're feeding a pipeline that needs the answer recorded.
The inputs that matter
- method - default
scharris genuinely the best-looking; drop tosobelif you want softer results. - output_format - match your game engine. Get this wrong and the whole map looks wrong even though nothing is broken.
- blur_radius - start at 0 and add 0.5–1.0 only if the output looks spiky.
- strength - 1.0 is faithful; 1.2–1.5 is a common exaggerate setting for game assets.
Installing it
Part of ComfyUI-TextureAlchemy - install once and you get all the nodes. In ComfyUI Manager search "TextureAlchemy", install, restart. Manual:
cd ComfyUI/custom_nodes
git clone https://github.com/amtarr/ComfyUI-TextureAlchemy
Restart and find it under Texture Alchemist → Normal. No extra pip packages, no model downloads - this one is pure torch math. (If you want the AI height source itself, you'll need Kijai's ComfyUI-Lotus separately; the README lists it as the optional upstream.)
Common issues
- "My normal map looks inverted!" Ninety percent of the time that's a format mismatch, not a broken node. Check
output_formatagainst your target engine, or run the pack's Normal Format Validator to see which way your map actually points. - Spiky, noisy output. Bump
blur_radiusto 0.5–1.5 and re-run. AI height maps are the usual culprit. - Barely-there detail. Raise
strength; the map is faithful but if the source height is flat-ish, faithful looks flat.
One honest caveat: a height-derived normal map is only as good as the height map. Fine micro-detail that the height doesn't contain won't magically appear. For that you'd overlay a detail normal - but as a cheap, fast way to round out a PBR set, this node does exactly what it says.
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| height | IMAGE | — | |
| strength | FLOAT | 1.00–10 | Normal map strength/intensity |
| method | COMBO | scharr | Gradient operator: Scharr (best), Sobel (standard), Prewitt (simple) |
| output_format | COMBO | DirectX | OpenGL: Y+ up (Unity, Blender) | DirectX: Y- up (Unreal, Maya) |
| blur_radius | FLOAT | 0.00–5 | Pre-blur height map to reduce noise (0 = no blur) |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| normal | IMAGE | — |
| format_info | STRING | — |