Nodes/ComfyUI-Depth-Visualization/Depth to Surface Normal
ComfyUI Node

Depth to Surface Normal

Turn depth into surface normals that actually survive contact with a renderer

By gokayfem·Created 2 years ago·Updated about a month ago· 70
Depth to Surface Normal
  • depth_map
  • surface_normal
strength1.00
field_of_view60
convention
invert_depthfalse

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, flip invert_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.
Categorydepth/toolkit

Inputs (5)

NameTypeDefaultDescription
depth_mapIMAGE
strengthFLOAT1.000–20
field_of_viewFLOAT601–179
conventionCOMBO2 options: OpenGL (+Y), DirectX (-Y)
invert_depthBOOLEANfalse

Outputs (1)

NameTypeDescription
surface_normalIMAGE