Depth to Normal Map
Convert a depth image into a normal map with Sobel gradients
- depth_maps
- IMAGE
You already have a depth map - Depth Anything, MiDaS, whatever spat one out earlier in your graph - and you need a normal map instead. This node is the bridge: feed it a depth image and it derives the normal map mathematically, no second model, no extra download, no re-running a normal estimator over your original photo.
That's worth being clear about up front, because it's easy to conflate the two. ControlNet's own normal preprocessor (Normal BAE) estimates a normal map straight from an RGB photo using a trained network. This node doesn't estimate anything - it converts, taking a depth map you already produced and computing what the surface normals would be if that depth map is accurate. Different job, and a much cheaper one once the depth map already exists.
How it works
Normal maps encode which direction each point on a surface faces, which is a genuinely different thing from depth (how far away it is) even though the two are closely related - a normal map is basically the spatial derivative of a depth map. That's exactly what this node computes: it runs Sobel filters over the depth channel to get X and Y gradients, builds a 3D normal vector from those gradients, and normalizes it to unit length. The Sobel-gradient approach is deliberately resolution-independent, so a depth map at 512px and one at 2048px produce comparable normal detail rather than the effect scaling with pixel count.
Only the first channel of your input image is read for the actual depth values, so a standard greyscale-as-RGB depth map works fine without any extra prep.
The inputs and outputs that matter
depth_maps- the depth image (or batch of them) to convert.normal_intensity(0.01–10, default 1) - how pronounced the normal effect is. Push it up for more visible surface detail, pull it down for something subtler. Higher-res inputs generally want lower intensity since they already carry more inherent detail; low-res ones often need it cranked up to show anything at all.depth_scale(0.02–2, default 1) - scales the depth values before the gradient calculation. One gotcha worth knowing if you're carrying over settings from an old workflow: the author changed the scaling formula at some point, and adepth_scaleof 1.0 today is equivalent to what used to be 0.2. If a saved workflow suddenly looks wildly different, this is why.flip_x(default True) andflip_y(default False) - axis handedness toggles. Different downstream consumers (game engines, ControlNet, various 3D tools) expect normal maps in different conventions, and these two flips are how you match them. If your normal map looks inverted or "inside out" once it's actually being used, this is the first thing to try.
Output is a single IMAGE - the normal map, ready to save, preview, or hand to a relighting workflow.
Installing it
Search cspnodes in ComfyUI Manager, or clone it directly:
cd ComfyUI/custom_nodes
git clone https://github.com/cerspense/ComfyUI_cspnodes
Restart ComfyUI after. The pack ships as one Python file with two hard pip dependencies - diffusers and pymediainfo - pulled in for the video nodes elsewhere in the pack. They're imported unconditionally at the top of that file, which means the whole pack, including this node, fails to load at all if either is missing, even though DepthToNormalMap itself doesn't touch either library. If cspnodes doesn't show up in your node list, check the ComfyUI startup log for an import error before assuming anything's wrong with this specific node - it's almost always one of those two packages missing or broken.
Where people get tripped up
Beyond the depth_scale migration gap above, the most common complaint is a normal map that looks basically flat gray-blue with no visible detail. That's almost always a low-contrast input depth map - the gradients this node computes depend entirely on how much the depth values actually vary across the image. If your source depth map is soft or washed out, either regenerate it with a sharper estimator (Depth Anything v2 Large is the current default choice for ControlNet-grade depth) or run a contrast stretch on it before this node, then compensate with normal_intensity rather than fighting a flat input.
The other one: this node reads only the first color channel of whatever you feed it. If you've done something unusual upstream - like accidentally wiring in a full-color photo instead of a depth map - you'll get a normal map derived from just the red channel's brightness, which won't look like a coherent normal map at all. Double-check the node directly upstream is actually producing a depth map, not a regular image.
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| depth_maps | IMAGE | — | |
| normal_intensity | FLOAT | 1.000.01–10 | — |
| flip_x | BOOLEAN | true | — |
| flip_y | BOOLEAN | false | — |
| depth_scale | FLOAT | 1.000.02–2 | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| IMAGE | IMAGE | — |