BD Depth To Shadow Map
Fake believable lighting from a depth image
- depth
- mask
- shadow_map
- normal_map
- cavity_map
- measured_max_curvature
A depth map is boring until you realize it's secretly a surface normal map waiting to happen. BD Depth to Shadow Map does exactly that conversion: feed it a depth image and it computes per-pixel lighting - top-down N·L shading plus cavity-based ambient occlusion - and spits out a greyscale "shadow map" that looks like someone actually lit your character. It's a procedural shader input, not a measurement, and for the job it's built for - feeding a skin shader's shadow channel or a PBR derivation pipeline - that's exactly right.
It was designed for the multi-tone skin pipeline in this pack (there's a zombie variant that cranks the AO for sunken eyes and hollow cheeks), but it works anywhere you have depth and want a lighting guide: game-UV prep, stylized shading, "fake a light rig" on a single image.
How the magic happens
Three ingredients get blended per pixel:
- N·L lighting - it derives a surface normal from the depth gradients and lights it with a direction you control. Default is top-down (
light_y = -1), which is why the forehead and brow ridge catch light while sockets fall into shade. - Cavity AO - the Laplacian of the depth, which is a fancy way of saying "recesses get dark." Eye sockets, the mouth crease, cheek hollows.
ao_radius(8–16 px default) controls what size of hollow counts. - Ambient floor (
ambient, 0.35) - guarantees nothing goes pure black, so the result stays usable as a multiplier.
light_strength and ao_strength mix those first two (both can go >1 for exaggeration), depth_blur smooths noisy depth before normals are computed, and invert_depth exists because estimators disagree on the "brighter = closer" convention - if your shadows look inverted, flip it.
Inputs: depth (IMAGE, R channel used if RGB; brighter = closer, unless you invert) and an optional mask - pixels outside the mask just get the ambient floor, so a skin mask keeps the shadow map from doing anything weird to the background.
Outputs: shadow_map (the greyscale multiplier - wire it into u_image3.R in the skin shader or any luma-based shadow slot), plus normal_map, cavity_map (handy for debugging what the AO is seeing), and measured_max_curvature (a float for tuning).
Installing
It ships in ComfyUI-BrainDead, so: ComfyUI Manager → search "BrainDead" → install, or
cd ComfyUI/custom_nodes
git clone https://github.com/BizaNator/ComfyUI-BrainDead
cd ComfyUI-BrainDead
pip install -r requirements.txt
then restart. No model downloads - this is pure numpy/torch math on whatever depth you feed it. Upstream, BD Lotus-2 Predict gives you the good diffusion-based depth; DepthAnythingV2 or MiDaS from comfyui_controlnet_aux work fine too if the quality bar is lower.
Where people get burned
The depth convention. If your shadows look "scooped out" backwards, invert_depth is the first toggle, not light_y. Second: this is a guide, not ground truth - the output is meant to be multiplied into a shader channel and then tuned by eye, so don't be surprised that ao_strength at 0.6 looks flat and 1.2 looks like a zombie. Both are intended outcomes.
Inputs (10)
| Name | Type | Default | Description |
|---|---|---|---|
| depth | IMAGE | Depth map image. Brighter = closer to camera. Typically from Lotus2 or a depth estimator. If RGB, the R channel is used. | |
| light_x | FLOAT | 0.00-1–1 | Light direction X (image-space). -1=left, +1=right, 0=centered. |
| light_y | FLOAT | -1.00-1–1 | Light direction Y (image-space). -1=TOP (default, top-down lighting), +1=bottom, 0=horizontal. |
| light_strength | FLOAT | 0.700–2 | How much N·L contributes to final shadow. 0=disable top-light, 1=full Lambertian, >1=exaggerated. |
| ao_strength | FLOAT | 0.600–2 | How much cavity AO contributes. 0=disable, 1=normal, >1=exaggerated darkness in recesses (more zombie). This is what creates the sunken-eye / hollow-cheek look. |
| ao_radius | INT | 81–64 | Pixel radius for cavity detection (Laplacian sigma). 8-16 catches eye sockets / cheek hollows. Smaller = fine creases only. Larger = broad hollows. |
| ambient | FLOAT | 0.350–1 | Minimum output value (ambient floor). Prevents pure-black voids. 0.35 = output never goes below 35% grey. Higher = brighter overall. |
| depth_blur | FLOAT | 2.00–16 | Pre-blur applied to depth before normal computation. Higher = smoother lighting (less noisy). 0 = raw depth. Helps if depth has stair-step quantization or noise. |
| invert_depth | BOOLEAN | false | ON: treat darker pixels as closer (Lotus2 convention is the opposite). Toggle if shadows appear inverted (sockets lit, peaks dark). |
| maskopt | MASK | Optional mask. Pixels outside the mask get the ambient floor value (not the computed shadow). Use a skin mask to limit AO to face. |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| shadow_map | IMAGE | Greyscale shadow map (R=G=B). 1.0=fully lit, 0.0=fully shadowed (clamped by ambient floor). Wire to u_image3.R input (or use as a shadow_mult multiplier in any pipeline). |
| normal_map | IMAGE | Derived surface normal visualized as RGB. R=nx, G=ny, B=nz, encoded [-1,+1] → [0,1] (standard tangent-space normal map). Useful for debugging or downstream lighting nodes. |
| cavity_map | IMAGE | Cavity AO term in isolation (R=G=B). 1.0=open surface, 0.0=deepest cavity. Useful for visualizing what AO is detecting. |
| measured_max_curvature | FLOAT | Peak Laplacian magnitude in the depth — useful for tuning ao_strength across characters with different depth scales. |