Depth to Surface Normal
Turn depth into surface normals that actually survive contact with a renderer
- depth_map
- surface_normal
A normal map tells you which way every surface faces, and it's the depth map's more useful sibling for a lot of jobs: relighting, texture work in 3D packages, even as a ControlNet preprocessor for finer detail control than plain depth gives you. The catch is that the naive "compute normals from a depth map" recipe you've seen copy-pasted around produces normals that look right and behave wrong, because it ignores the camera. Depth to Surface Normal is camera-aware: you give it a field of view, and it computes true camera-space normals instead of flat-image ones.
How it works
The node takes the depth gradient - how fast depth changes in x and y - and turns those slopes into a normalized 3D vector per pixel. The field_of_view input derives the focal length (0.5 * width / tan(fov/2)), which is what makes the result geometrically honest rather than just "gradients dressed up as vectors." strength (default 1) scales the slope before normalization, so it's your relief knob: crank it past 2 and edges get exaggerated; drop toward 0 and everything flattens out.
The output is RGB-encoded the standard way - each normal vector packed as n * 0.5 + 0.5 - so blue-ish means "facing the camera," which is what you expect from a depth-based map.
Inputs and outputs that matter
depth_map- in. Feed it normalized depth (put Depth Normalize in front) for consistent results.strength- how much relief you want. Default 1 is usually right.field_of_view- match your source camera or the 3D app you're exporting to.convention- OpenGL (+Y) vs DirectX (-Y). This is the one that'll bite you. It flips the green channel, and if you feed a DirectX-style map into a Blender/OpenGL renderer, your normals will look inverted or "sunk in."invert_depth- flip if your map is white-far instead of white-near.
Single output: surface_normal (IMAGE). Wire it into relighting nodes, save it as a texture for game assets, or drop it into a normal-ControlNet workflow for conditioning that preserves fine surface detail.
Install
From the ComfyUI-Depth-Visualization pack (gokayfem, also the author of ComfyUI-Texture-Simple - normals work is kind of their lane). ComfyUI Manager: search ComfyUI-Depth-Visualization, install, restart. Manual:
cd ComfyUI/custom_nodes
git clone https://github.com/gokayfem/ComfyUI-Depth-Visualization.git
python -m pip install -r ComfyUI-Depth-Visualization/requirements.txt
Restart, find it under depth/toolkit. Dependencies are just numpy + Pillow; no models, runs on CPU, fully offline.
Where people get burned
- "Why are my normals inverted?" - 90% of the time it's the convention or
invert_depth, not the math. Try the other convention first; if the scene still looks caved-in, flipinvert_depth. - Garbage depth in, garbage normals out. A depth map with speckle produces normals that look like static. Run Depth Cleanup first - it exists precisely to fix this - and normalize before converting.
- FOV mismatch warps the result. If your source depth came from a wide-angle-ish default and you export normals at 60° into a model rendered at a different FOV, relief subtly skews. Match the FOV to whatever's consuming it.
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| depth_map | IMAGE | — | |
| strength | FLOAT | 1.000–20 | — |
| field_of_view | FLOAT | 601–179 | — |
| convention | COMBO | 2 options: OpenGL (+Y), DirectX (-Y) | |
| invert_depth | BOOLEAN | false | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| surface_normal | IMAGE | — |