Nodes/ComfyUI-NormalsToDepth/Normal to Depth (Frankot-Chellappa)
ComfyUI Node

Normal to Depth (Frankot-Chellappa)

Turn a normal map into a depth map with a Fourier transform

By EMkrtchyan·Created 9 months ago·Updated 7 months ago· 1
Normal to Depth (Frankot-Chellappa)
  • normal_map
  • depth_map
invert_outputfalse

You've got a normal map and you need a depth map. This node - "Normal to Depth (Frankot-Chellappa)" - is the mathematical bridge between the two, and unlike most things you install in ComfyUI it has no weights, no GPU requirement, and no downloads. It's pure signal-processing math, and it's fast.

What it actually does

A normal map stores, per pixel, the direction the surface is facing: the RGB channels are the X/Y/Z components of that direction, squashed into [0,1]. A depth map stores distance. They're not independent - depth is the integral of surface orientation, so if you know how the surface tilts everywhere, you can reconstruct how high it is everywhere. That integration is exactly this node's job.

Why bother? Normals are easy to come by - ControlNet aux packs, renderers, normal estimators - but a lot of the good downstream stuff wants a depth map: depth ControlNet conditioning, parallax effects, displacement, and bas-relief-style 3D printing. Pulling a depth map out of the normals you already have beats firing up a whole depth estimator.

How it works

The algorithm is the 1988 Frankot-Chellappa method (sounds like a law firm; it's a classic shape-from-shading paper). The node remaps the normal channels to [-1, 1], then derives surface slopes:

p = -nx / nz,   q = -ny / nz

Those are the surface height gradients along X and Y. To turn gradients back into heights, it integrates in the Fourier domain: FFT both gradients, divide by spatial frequency squared (u²+v²), inverse FFT. Dividing by frequency is integrating - it's the frequency-space version of adding up all the slopes - then the result is normalized to [0,1] so it works as an image.

All of it runs on numpy/scipy on your CPU. A 1024² normal map takes a fraction of a second.

The inputs that matter

Only two, which is refreshing:

  • normal_map - an IMAGE. Wire in anything that outputs normals. Values are read as-is, so give it an actual normal map, not a depth map that happens to be colorful.
  • invert_output - BOOLEAN, default off. Depth conventions disagree: some pipelines want white = near, some white = far. If your output looks backwards, flip this.

Output is depth_map, an IMAGE. It wires straight into depth ControlNet, parallax nodes, or a save node.

Installing

ComfyUI Manager → search "ComfyUI-NormalsToDepth", or manually:

cd ComfyUI/custom_nodes
git clone https://github.com/EMkrtchyan/ComfyUI-NormalsToDepth

Then restart ComfyUI. requirements.txt lists scipy, numpy, torch - you already have numpy and torch, and scipy ships with most ComfyUI installs (Manager will grab it if it's missing). No model files, no downloads.

Where people get burned

  • Garbage in, flat garbage out. If the input isn't a real normal map, nz hovers near zero, and the eps guard keeps the math from dividing by zero - but the output will still be noise. Check your source before blaming the node.
  • The green-channel flip. Normal maps come in two conventions: OpenGL (green up) and DirectX (green down). If your map is in the opposite convention from the one the code expects, the surface slopes invert and you get a depth map with smeared, warped features. invert_output won't fix that - flip the green channel upstream.
  • It's the smooth solver. Fourier-domain integration is global, so fine detail gets rounded off. If your normals are clean and you want crisp edges, the pack's sibling Poisson node is the sharper choice; Frankot-Chellappa is what you reach for when the normals are noisy and you'd rather average them into something smooth.

It's a small, quiet pack - the README is basically a title, and you won't hear it talked about anywhere. But the math is standard, it does exactly what it says on the box, and for a niche like this that's more than enough.

CategoryDepthSolvers

Inputs (2)

NameTypeDefaultDescription
normal_mapIMAGE
invert_outputBOOLEANfalse

Outputs (1)

NameTypeDescription
depth_mapIMAGE