Exposure2HDR
Three Flat Photos In, One Real HDR Out
- exposures
- IMAGE
A single photo can't hold a whole scene's light. A sunlit window blows out to pure white while the shadows under it are pure black - that's the dynamic range problem, and it's exactly the problem an HDR environment map is supposed to solve. Exposure2HDR is the node that solves it for the DiffusionLight pipeline: it takes several bracketed exposures of the same scene and merges them into one floating-point HDR image where the full range survives.
This is the node that makes the whole DiffusionLight concept work. The pipeline runs its chrome-ball inpainting pass three times at different exposures (EV 0, -2.5 and -5 in the reference workflow), unwraps each ball to an environment map, and then hands all three to Exposure2HDR to combine into a single .hdr worth actually lighting something with. Without it you'd have three flat, clipped images and a light map that's as limited as any JPEG.
How it works
The mechanism is classic exposure fusion, kept deliberately simple. Each exposure is first linearized - raised to the gamma power to undo the sRGB-ish encoding so you're working in light units, not display units. Then each is scaled by 1 / 2^EV to bring the different exposures onto the same radiance scale. The node computes a luminance value per pixel (Rec.709 weights - standard stuff), then merges the stack starting from the darkest exposure, using a soft mask that favors whichever exposure actually captured that pixel well instead of clipping.
The key detail: the output is not clamped. HDR values can exceed 1.0, and that's the entire point. That's also why you won't see a useful preview until you tone-map it - see ExposureBracket, which is the inverse of this node, or the pack's percentile tonemap node.
The inputs that matter
- exposures - a batch of IMAGEs, one per exposure, ordered so index 0 is the brightest (highest EV). The reference workflow builds this with an
ImageBatchnode after unwrapping each exposure. - ev_values - a comma-separated string of EVs, default
"0.0,-2.5,-5.0". Each entry maps to the batch index. Don't touch it unless you change the bracket. - gamma - default 2.4, which matches sRGB's transfer curve. Leave it alone unless you know your inputs use a different encoding.
The single output is an IMAGE containing the merged HDR map (floating point, range >1). It feeds DiffusionLightSaveHDR to write a .hdr/.exr/.npy file, or DiffusionLightExposureBracket to preview what's actually in it.
Installing it
Part of the DiffusionLight-ComfyUI pack: ComfyUI Manager → search "DiffusionLight", or git clone https://github.com/DiffusionLight/DiffusionLight-ComfyUI into ComfyUI/custom_nodes, then restart. This node itself has no extra dependencies - numpy and torch are already in ComfyUI. Note the workflow needs more than the pack: comfyui_controlnet_aux plus the SDXL checkpoint, SDXL depth ControlNet, and the two DiffusionLight LoRAs from the README.
Gotchas
- Count and order must match. The number of frames in
exposureshas to equal the number of entries inev_values, and frame i is assumed to be EV i. Change the bracket upstream and forget to update the string, and you'll get an index error at best, a wrong-looking HDR at worst. - If your merged result looks too dark or too hot, check the bracket ordering - a reversed stack merges brightest-first and gives you a muddy mess.
- This node is tuned for environment maps, not general photography. It works on any bracketed stack, but there are sharper HDR-merge tools (Debevec-style radiance weighting) if you're doing photography rather than lighting.
Inputs (3)
| Name | Type | Default | Description |
|---|---|---|---|
| exposures | IMAGE | — | |
| gamma | FLOAT | 2.40-1000–1000 | — |
| ev_values | STRING | 0.0,-2.5,-5.0 | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| IMAGE | IMAGE | — |