ReLight π‘
Fake a three-point lighting rig without regenerating a single pixel
- image
- mask
- image
- mask
- debug_image
There are two different things called "relighting" in this ecosystem, and keeping them straight is half the battle. IC-Light and the Qwen/Flux edit models are diffusion relighters - they re-emit the whole frame, shadows and all, and can hallucinate a logo or alter a texture while they're at it. ReLight is the other kind. It's a single ComfyUI node that paints up to three positionable light sources onto an image with pure math: numpy, Pillow, scipy, zero diffusion passes, zero models to download, deterministic every run. If you want a subject to catch a warm window glow without ever asking a denoiser to re-invent it, this is the node.
That determinism is the whole pitch. Diffusion relighters can't promise a consistent product catalogue; ReLight only promises that, because it modifies illumination and leaves the pixels' structure alone by construction. It's also near-instant, so it's what you reach for when you want to try twenty lighting ideas on a hero image before committing to a generation, or when you want to feed a deliberately lit result into ControlNet. It won't give you physically-correct global illumination or move reflections - it paints light onto what's already there, it doesn't re-render the scene. Know the limit and you won't be disappointed.
How it works
For each light source the node builds a mask centered on light_position_x/light_position_y - a radial falloff that's full strength inside inner_circle_radius and fades linearly to zero at outer_circle_radius, or a directional gradient if you flip on use_gradient_mode (that's your sunset-rays and window-light look). Everything gets softened by mask_blur.
Then it picks a lane. In the default color-correction mode it applies brightness/contrast/saturation/temperature/tint/gamma to the inner zone and a separate set to the outer ring - the v2.1.0 changelog fixed the outer values actually applying, so presets like "Spotlight" behave as designed now. With colored lights on, it does plain additive light: image + color Γ intensity Γ mask, clamped to a valid range.
The "3D" part is where the optional mask input earns its keep. The node runs a Sobel edge detection over your foreground mask, then modulates the light by how its direction lines up with the edge normals - that's how it fakes occlusion. Behind Subject leaves a rim of light around the silhouette (boost with rim_amplification); In Front of Subject lights the subject's face. No ray tracing, but for a static image it reads surprisingly well.
The inputs and outputs that matter
You'll set five things, honestly:
- image - the only required input.
- mask - white = subject, black = background. Needed for the occlusion directions and for
remove_background. - preset - 8 ready-made looks. One gotcha: a preset overrides the widgets on the node, so the values you see may be ignored for whatever the preset defines.
preserve_positioningkeeps your light positions and radii when you pick one. - num_light_sources (1β3) - but note: lights 2 and 3 get their own position, radius and color while reusing Light 1's correction settings in color-correction mode. Set Light 1's first.
- effect_strength - master intensity. If you see nothing, raise this first.
Outputs are three: image (the relit result, which is what you wire into a Save/Preview or into ControlNet), mask (pass-through of your input, black if none connected), and debug_image - a visualization of light positions and masks, produced when show_debug_info is on.
Install
ComfyUI Manager: search "ReLight" and install, then restart. Or the old-fashioned way:
cd path/to/ComfyUI/custom_nodes
git clone https://github.com/EnragedAntelope/comfyui-relight
pip install -r comfyui-relight/requirements.txt
That's it - numpy, Pillow, scipy; torch comes from ComfyUI itself. There are no model files to download. One version catch worth knowing: the node is built on the ComfyUI v3 node schema, which needs ComfyUI 0.3.48+. If the node never shows up after install, update ComfyUI first, not the node. The README also recommends ComfyUI Essentials for proper masks (its RemBG node gives you the clean foreground mask this thing wants).
Common issues
- Subject looks unlit, or the effect is backwards. Your mask is inverted. ReLight expects white = subject; invert it upstream with an InvertMask node.
- No visible effect. Raise
effect_strengthor the per-lightlight_intensity. - Occlusion isn't doing anything.
apply_3d_lightingmust be on and a mask connected; without one it silently degrades to flat lighting. - Node fails to load.
scipyisn't installed -pip install scipyand restart. - Black debug image. Check the ComfyUI console; the debug output only renders for the first image in a batch.
A quick workflow to try: load an image, run RemBG for a mask, drop ReLight in with "Warm Sunset Glow" behind the subject, and preview alongside the debug image - the sample workflow in the repo does exactly this. It's the fastest way to see whether the "fake it with math" school of relighting is for you.
Inputs (51)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | The input image to apply lighting effects to | |
| preset | COMBO | None | Select a preset or None for custom settings. NOTE: a preset overrides the widgets below - the values shown on the node are ignored for whatever the preset defines. The one exception is effect_strength, which scales the preset instead of being replaced by it |
| preserve_positioning | BOOLEAN | false | Keep your own light positions and radii when a preset is selected, instead of letting the preset set them |
| lighting_mode | COMBO | Color Correction | What the light does. Color Correction grades an inner zone and the ring around it. Colored Light adds coloured light on top of the image. Both applies the coloured light first and then the grade |
| mask_shape | COMBO | Radial falloff | Shape of the light mask. Radial falloff is a lamp; Directional gradient is light arriving from one side (sunset rays, window light) |
| subject_interaction | COMBO | None | How the light interacts with the masked subject. None lights the whole frame evenly. The other two need a mask connected |
| remove_background | BOOLEAN | false | Composite the lit result back over the untouched original using the mask, so only the subject is relit. Does not remove anything. Ignored when the light is in front of or behind the subject |
| num_light_sources | INT | 11β3 | Number of light sources (1-3). Lights 2 and 3 have their own position, radius and color, but in color-correction mode they reuse Light 1 correction settings |
| effect_strength | FLOAT | 1.00β5 | Overall intensity multiplier for lighting adjustments/colors, gamma included. 0.0 leaves the image untouched. With a preset active this scales the preset own strength, so 1.0 is the preset as designed. Does not scale rim_amplification or mask_blur - those have their own controls |
| mask_blur | FLOAT | 500β200 | Blur radius for light mask edges (smoother transitions) |
| rim_amplification | FLOAT | 2.00β10 | Intensity boost for the rim highlight along the subject edge. Only used when the light is behind the subject |
| shadow_strength | FLOAT | 0.600β1 | How dark the shadow the subject casts across the background is. 0.0 casts no shadow. Only used when the light is behind the subject |
| shadow_length | FLOAT | 0.350β1 | How far the cast shadow reaches, as a fraction of the image shorter side. Only used when the light is behind the subject |
| light_position_x | FLOAT | 0.500β1 | Light 1: Horizontal position (0=left, 1=right) |
| light_position_y | FLOAT | 0.500β1 | Light 1: Vertical position (0=top, 1=bottom) |
| inner_circle_radius | FLOAT | 0.400β1 | Light 1: Inner radius (strongest effect area) |
| outer_circle_radius | FLOAT | 0.700β1 | Light 1: Outer radius (falloff area) |
| light_color_r | INT | 2550β255 | Light 1: Red color (Colored Light / Both) |
| light_color_g | INT | 2550β255 | Light 1: Green color (Colored Light / Both) |
| light_color_b | INT | 2550β255 | Light 1: Blue color (Colored Light / Both) |
| light_intensity | FLOAT | 1.00β3 | Light 1: Intensity (Colored Light / Both) |
| inner_brightness | FLOAT | 10-100β100 | Light 1: Inner area brightness (Color Correction / Both) |
| inner_contrast | FLOAT | 5-100β100 | Light 1: Inner area contrast (Color Correction / Both) |
| inner_saturation | FLOAT | 5-100β100 | Light 1: Inner area saturation (Color Correction / Both) |
| inner_temperature | FLOAT | 0-100β100 | Light 1: Inner area temperature (-100=cool, 100=warm) |
| inner_tint | FLOAT | 0-100β100 | Light 1: Inner area tint (-100=magenta, 100=green) |
| inner_gamma | FLOAT | 1.000.1β5 | Light 1: Inner area gamma. Above 1.0 brightens midtones, below 1.0 darkens them |
| outer_brightness | FLOAT | -10-100β100 | Light 1: Outer area brightness (Color Correction / Both) |
| outer_contrast | FLOAT | 0-100β100 | Light 1: Outer area contrast (Color Correction / Both) |
| outer_saturation | FLOAT | -10-100β100 | Light 1: Outer area saturation (Color Correction / Both) |
| outer_temperature | FLOAT | 0-100β100 | Light 1: Outer area temperature |
| outer_tint | FLOAT | 0-100β100 | Light 1: Outer area tint |
| outer_gamma | FLOAT | 0.910.1β5 | Light 1: Outer area gamma. Above 1.0 brightens midtones, below 1.0 darkens them |
| maskopt | MASK | Foreground mask (white=subject, black=background). Needed for subject interaction (in front of / behind the subject) and for remove_background compositing. Resized automatically if it does not match the image | |
| debug_output_connectedopt | BOOLEAN | false | Managed by ReLight itself - the node UI hides this and keeps it in step with whether the debug_image output is wired to anything. It exists because a widget value is what ComfyUI hashes to decide a node needs re-running, so without it, wiring the debug output after a run would just replay the cached placeholder. Nothing to set by hand |
| light2_position_xopt | FLOAT | 0.800β1 | Light 2: Horizontal position |
| light2_position_yopt | FLOAT | 0.200β1 | Light 2: Vertical position |
| light2_inner_radiusopt | FLOAT | 0.300β1 | Light 2: Inner radius |
| light2_outer_radiusopt | FLOAT | 0.600β1 | Light 2: Outer radius |
| light2_color_ropt | INT | 1800β255 | Light 2: Red color |
| light2_color_gopt | INT | 1800β255 | Light 2: Green color |
| light2_color_bopt | INT | 2550β255 | Light 2: Blue color |
| light2_intensityopt | FLOAT | 0.70β3 | Light 2: Intensity (Colored Light / Both) |
| light3_position_xopt | FLOAT | 0.300β1 | Light 3: Horizontal position |
| light3_position_yopt | FLOAT | 0.800β1 | Light 3: Vertical position |
| light3_inner_radiusopt | FLOAT | 0.250β1 | Light 3: Inner radius |
| light3_outer_radiusopt | FLOAT | 0.500β1 | Light 3: Outer radius |
| light3_color_ropt | INT | 2550β255 | Light 3: Red color |
| light3_color_gopt | INT | 1500β255 | Light 3: Green color |
| light3_color_bopt | INT | 1200β255 | Light 3: Blue color |
| light3_intensityopt | FLOAT | 0.50β3 | Light 3: Intensity (Colored Light / Both) |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | The relit image |
| mask | MASK | Pass-through of the input mask, normalised to (batch, height, width) and resized to the image (black if none connected) |
| debug_image | IMAGE | Visualization of light positions and masks. Drawn whenever this output is connected to something - there is no toggle to remember |