Multipass Composite
An alpha-over that knows about depth, shadows and light wrap
- foreground
- alpha
- background
- relit_foreground
- foreground_depth
- background_depth
- shadow_mask
- composite
- premultiplied_foreground
- holdout_mask
- depth_matte
- comp_info
Dropping a CG element into a plate with a plain alpha-over gives you the tell instantly: the element looks pasted on. Not because the render is bad, but because the three things that make a composite read - the element being occluded by things in front of it, a shadow where it meets the ground, and the plate's light bleeding into its edge - are all missing.
Multipass Composite does those. It's an alpha-over at the core, with depth holdout, background shadow, and light wrap layered on, and it wants scene-linear inputs so that the edges blend in a physically sensible space rather than in gamma.
How it works
The base operation: foreground over background through the alpha. Straightforward.
Depth holdout is the interesting one. Connect foreground_depth and background_depth and pixels where the background is nearer than the foreground get held out - the element goes behind the tree instead of in front of it. It only engages when both are connected, which is a clean way to make it opt-in. Polarity is handled by depth_near_is_white (on by default, so larger values are nearer); if your renderer writes distance-style Z where smaller is nearer, flip it. depth_bias gives you tolerance in depth units - positive keeps the foreground in front when the depths are nearly equal, negative favours the background - with a small default of 0.01 that stops coplanar surfaces from flickering between states.
Shadow is deliberately simple: shadow_mask (1 = full shadow) darkens the background by shadow_strength before the over. Generating the shadow is your problem; compositing it correctly is this node's.
Light wrap adds a blurred version of the background to the foreground's soft edges, which is what makes a subject feel lit by its surroundings - and it's perennially the single most effective "make it look real" knob in compositing. light_wrap is the amount (default 0, off) and light_wrap_radius the box blur radius, with the edge band using half that radius so the wrap hugs the matte rather than smearing across the whole frame.
Premultiplication gets handled properly, which is worth calling out because it's where manual comps go wrong. premultiplied_input divides by alpha before compositing so you don't double-premultiply an already-premultiplied element. alpha_invert uses 1 - alpha - and the tooltip notes it applies after unpremultiplying, which is the correct order and a detail most implementations get backwards.
Inputs and outputs
Required: foreground (sets the output size and batch) and alpha (first channel, 0–1). Optional: background (resized to the foreground; black if unconnected), relit_foreground (swaps in different foreground pixels - e.g. the output of Multipass Relight - reusing the same premultiplication setting), foreground_depth, background_depth, shadow_mask, plus the toggles and values above.
Outputs: composite, premultiplied_foreground, holdout_mask, depth_matte, and comp_info (a JSON string of the settings). holdout_mask and depth_matte are the two that make this node debuggable - when the element vanishes behind something it shouldn't, look at the depth matte rather than the composite, and you'll see immediately whether it's a polarity problem or a bias problem.
Installing Radiance
Manager → search Radiance → install → restart → refresh. Manual:
cd ComfyUI/custom_nodes
git clone https://github.com/fxtd-studios/radiance.git
cd radiance
python -m pip install -r requirements.txt
Windows portable: python_embeded\python.exe. No weights. The pack is GPL-3.0, ~147 visible nodes, and installs OpenEXR, OpenImageIO, OpenColorIO, diffusers and accelerate into ComfyUI's environment.
Troubleshooting
A bright fringe around the element. Premultiplication, nine times out of ten. If the alpha came from a matting node that already premultiplied, turn premultiplied_input on.
The element disappears into the background. Depth holdout with the wrong polarity, or a depth_bias fighting you. Check depth_matte first, then flip depth_near_is_white.
Shadow looks like a flat grey blob. It is one - this node darkens, it doesn't soften or shape. Build the shadow as a pass (estimated or real) and let this node composite it.
Edges look crunchy rather than blended. The inputs probably aren't scene-linear. Compositing gamma-encoded images through a soft matte produces edges that darken or lighten, and no amount of light wrap hides it. Get the foreground into linear before the over.
Light wrap does nothing. background isn't connected, or light_wrap is still at its default of 0. Both are required.
Video wobble at the edges. Per-frame mattes plus light wrap equals crawling edges. Lock the matte where you can and keep light_wrap_radius modest.
Inputs (14)
| Name | Type | Default | Description |
|---|---|---|---|
| foreground | IMAGE | Foreground colour. Sets the output size and batch; its pixels are ignored when relit_foreground is connected. | |
| alpha | IMAGE | Foreground matte, first channel, 0..1 (1 = foreground fully covers the background). | |
| backgroundopt | IMAGE | Background plate, resized to the foreground. Black when not connected. | |
| relit_foregroundopt | IMAGE | Replaces the foreground pixels (for example the relit output of Multipass Relight). Same premultiplication as set by premultiplied_input. | |
| foreground_depthopt | IMAGE | Foreground depth pass, first channel. Used for the depth holdout only when background_depth is also connected. | |
| background_depthopt | IMAGE | Background depth pass in the same units and polarity as foreground_depth. Background pixels nearer than the foreground hold it out. | |
| shadow_maskopt | IMAGE | Shadow cast onto the background, first channel, 1 = full shadow. Darkens the background by shadow_strength before the over. | |
| alpha_invertopt | BOOLEAN | false | Use 1 - alpha as the matte. Applied after unpremultiplying, which still uses the original alpha. |
| premultiplied_inputopt | BOOLEAN | false | Set on when the foreground is already multiplied by alpha; it is divided by alpha first to avoid a double premultiply. |
| depth_near_is_whiteopt | BOOLEAN | true | Depth polarity of both depth passes. On: larger values are nearer. Off: smaller values are nearer (distance style Z). |
| depth_biasopt | FLOAT | 0.010-0.25–0.25 | Depth tolerance in depth-pass units. Positive values keep the foreground in front when depths are nearly equal, negative favours the background. |
| shadow_strengthopt | FLOAT | 0.350–1 | How much shadow_mask darkens the background: 0 = none, 1 = black where the mask is 1. |
| light_wrapopt | FLOAT | 0.000–1 | Amount of blurred background added to the foreground's soft edges. 0 = off; needs background connected. |
| light_wrap_radiusopt | INT | 80–64 | Box blur radius in pixels for the wrapped background. The edge band uses half this radius. |
Outputs (5)
| Name | Type | Description |
|---|---|---|
| composite | IMAGE | — |
| premultiplied_foreground | IMAGE | — |
| holdout_mask | IMAGE | — |
| depth_matte | IMAGE | — |
| comp_info | STRING | — |