Multipass Relight
Move the key light in comp instead of re-rendering
- albedo
- normal_map
- beauty
- roughness
- metallic
- specular
- ao
- alpha
- shadow_mask
- depth_map
- world_position
- relit
- diffuse_light
- specular_light
- lighting
- alpha
- relight_info
Multipass Relight is the payoff node for anyone who went to the trouble of getting real passes. Give it albedo and normals and it lights the shot again from scratch - Lambert diffuse plus GGX specular - with one light you can place and colour. Change the key direction, warm the sun, kill the specular, all without touching the original render or the generator.
That's the classic comp move, and it only works if the passes mean something. Which is why this node is really a companion to Multipass Estimate (predicts albedo, normals, roughness from a plate with MoGe-2 and Marigold) or Read AOVs (actual renderer passes - always better if you have them). Don't feed it the retired Multipass Extract's outputs; those passes were filters pretending to be measurements, and relighting a fake albedo gives you a fake render.
How it actually works
albedo and normal_map are the only required inputs, and the normals are forgiving: 0..1-encoded maps are decoded to -1..1 automatically, signed passes are used as is, and normal_convention (OpenGL Y-Up or DirectX Y-Down) flips the green channel to match what you rendered. View direction is +Z toward the camera.
Everything else is optional and each one genuinely changes the maths: roughness (defaults to 0.5 when absent, clamped to a 0.045 minimum so you never get a perfect mirror), metallic (0 = dielectric, 1 = metal, where albedo tints the reflection and diffuse drops out), specular (scales the 4% dielectric reflectance, so 1 = 4%), ao (multiplies the ambient term), and shadow_mask (blocks direct light only - ambient survives, which is correct and a thing people forget). alpha passes through and drives output_premultiplied.
The light is two modes. Directional treats light_x/light_y/light_z as a direction with no falloff - your sun or a big soft box, and what you'll use most of the time. Point treats the same three numbers as a position with a soft 1 / (1 + 0.08·d²) falloff, and needs depth: connect world_position (camera-space metres, OpenGL axes) if you have it, otherwise depth_map plus depth_scale and depth_near_is_white to synthesise a Z. light_r/light_g/light_b tint both the direct light and the ambient; intensity scales direct light only; ambient is your flat fill and it's multiplied by AO.
Wire the six outputs where they belong. relit is the composite-ready result, scene-linear. diffuse_light and specular_light are the separated terms, so you can grade one without the other. lighting is the combined light pass. alpha is the matte. relight_info is a string report - worth reading when a result looks wrong, because it tells you what it actually used. mix_with_beauty blends back toward the original (needs beauty connected), which is how you sneak a 30% relight past a director.
Install
Ships with Radiance. In ComfyUI Manager search Radiance, install, restart, refresh. Manual route:
cd ComfyUI/custom_nodes
git clone https://github.com/fxtd-studios/radiance.git
cd radiance
python -m pip install -r requirements.txt
No models of its own - it's maths over the tensors you hand it, so it's fast. The downloads belong to Multipass Estimate, which pulls MoGe-2 geometry and Marigold materials and lighting through diffusers.
Where people get burned
- Estimating an albedo from a beauty and relighting it gives you double lighting, because the original light is baked into the albedo. If all you have is a beauty, you want a relight that estimates the split, not this node.
- Normals that aren't normals. A ControlNet-style normal map from a preprocessor is not a true surface normalisation. It'll render something. It won't be right.
- Point light with no depth falls back to synthetic screen-space Z, so your light moves when the camera does. Use directional unless you've connected real depth.
- Double premultiply. If your albedo is already multiplied by alpha, unpremultiply upstream and leave
output_premultipliedoff, or you'll halo every edge.
Video is the honest caveat: if the passes came from Multipass Estimate they were predicted per frame, so a fixed seed limits flicker rather than removing it. Real AOVs don't have that problem.
Inputs (26)
| Name | Type | Default | Description |
|---|---|---|---|
| albedo | IMAGE | Diffuse albedo (base colour) pass, scene-linear with no lighting baked in. Also used as the specular colour where metallic is 1. | |
| normal_map | IMAGE | Normal pass: 0..1 encoded values are decoded to -1..1 automatically, signed passes are used as is. View direction is +Z toward the camera. | |
| beautyopt | IMAGE | Original beauty render, scene-linear. Only used when mix_with_beauty is above 0. | |
| roughnessopt | IMAGE | Roughness pass, first channel, 0 = mirror, 1 = fully rough (clamped to 0.045 minimum). Defaults to 0.5 when not connected. | |
| metallicopt | IMAGE | Metalness pass, first channel, 0 = dielectric, 1 = metal (no diffuse, albedo tints the reflection). Defaults to 0 when not connected. | |
| specularopt | IMAGE | Dielectric specular level pass, 0..1, scaling the default 4% reflectance at normal incidence (1 = 4%). Defaults to 1 when not connected. | |
| aoopt | IMAGE | Ambient occlusion as renderers write it: 1 = open, 0 = fully occluded. | |
| alphaopt | IMAGE | Coverage matte, first channel, 0..1. Passed through to the alpha output and used for output_premultiplied; defaults to 1. | |
| shadow_maskopt | IMAGE | Shadow pass, first channel: 1 = fully shadowed, 0 = lit. Blocks direct diffuse and specular light but not ambient. | |
| depth_mapopt | IMAGE | Normalised 0..1 depth pass. Only used by a Point light when world_position is not connected, to give pixels a synthetic Z. | |
| world_positionopt | IMAGE | Position pass in camera space metres (OpenGL axes: X right, Y up, +Z toward the camera). Only used by a Point light, and overrides depth_map. | |
| normal_conventionopt | COMBO | OpenGL (Y-Up) | Green channel convention of the normal pass. DirectX (Y-Down) flips the Y component before lighting. |
| light_typeopt | COMBO | Directional | Directional: light_x/y/z is a direction, no falloff. Point: light_x/y/z is a position, with soft distance falloff 1 / (1 + 0.08 d^2). |
| light_xopt | FLOAT | -0.35-10–10 | Light X (+ = screen right). Directional: component of the direction toward the light. Point: position in world_position units, or in screen units (X spans +/- aspect ratio) without it. |
| light_yopt | FLOAT | 0.45-10–10 | Light Y (+ = up). Directional: component of the direction toward the light. Point: position in world_position units, or in screen units (Y spans -1..1) without it. |
| light_zopt | FLOAT | 1.00-10–10 | Light Z (+ = toward the camera). Directional: component of the direction toward the light, so positive values light the front. Point: position along the camera axis. |
| light_ropt | FLOAT | 1.000–8 | Linear red multiplier of the light colour. Also tints the ambient term. |
| light_gopt | FLOAT | 1.000–8 | Linear green multiplier of the light colour. Also tints the ambient term. |
| light_bopt | FLOAT | 1.000–8 | Linear blue multiplier of the light colour. Also tints the ambient term. |
| intensityopt | FLOAT | 1.000–20 | Linear multiplier on the direct light (diffuse and specular). Does not affect ambient. |
| ambientopt | FLOAT | 0.030–4 | Flat fill light level, tinted by the light colour and multiplied by the AO pass. Applied to diffuse only. |
| specular_intensityopt | FLOAT | 1.000–8 | Linear multiplier on the specular term only. 0 gives a purely diffuse relight. |
| depth_scaleopt | FLOAT | 10.00.01–1000 | Z range, in screen units (image height = 2), that the 0..1 depth pass spans around mid grey. Only used for a Point light with depth_map and no world_position. |
| depth_near_is_whiteopt | BOOLEAN | true | Set on when near pixels are white in depth_map. Only used for a Point light with depth_map and no world_position. |
| mix_with_beautyopt | FLOAT | 0.000–1 | Linear blend of the relit result toward the beauty input: 0 = relight only, 1 = original beauty. Needs beauty connected. |
| output_premultipliedopt | BOOLEAN | false | Multiply the relit output (and the beauty mix) by alpha. Off leaves it unpremultiplied. |
Outputs (6)
| Name | Type | Description |
|---|---|---|
| relit | IMAGE | — |
| diffuse_light | IMAGE | — |
| specular_light | IMAGE | — |
| lighting | IMAGE | — |
| alpha | IMAGE | — |
| relight_info | STRING | — |