ComfyUI Node

Ball2Envmap

Turn a Photo of a Chrome Ball Into a Lighting Map

By DiffusionLight·Created about a year ago·Updated about a year ago· 23
Ball2Envmap
  • chromeball
  • IMAGE
envmap_height256
anti_aliasing4

If you've ever done product or CG work, you know the trick: drop a shiny chrome ball in the scene, photograph it, and the ball becomes a tiny mirror of the light around it. Unwrapping that ball is how you get an environment map to light your render. Ball2Envmap is the unwrap step for the DiffusionLight pipeline - it takes a photo of the chrome ball and converts it into a proper equirectangular lighting map. No model, no VRAM, no diffusion. It's just geometry.

That's worth spelling out because the name sounds like the whole magic trick. It isn't. In DiffusionLight's full workflow the diffusion part happens upstream: a ControlNet depth pass plus two LoRAs inpaint a plausible reflection into the ball region (that's the actual "DiffusionLight" research - VISTEC's DiffusionLight-Turbo, an official ComfyUI port that runs on lower VRAM than the main repo's ~16GB requirement). Ball2Envmap sits after that, converting each reconstructed ball back into an equirectangular map. It's the same math Blender uses to unwrap a sphere, done on a photo.

How it works

For every pixel of the output map, the node works out the direction the camera ray would bounce off a mirror sphere to hit the light in that direction, then looks that point up in the ball photo with bilinear sampling (torch.nn.functional.grid_sample). The output is a 2:1 equirectangular image - width is exactly twice the height - which is the format 3D apps and IBL tools expect.

The one knob that actually changes quality is anti_aliasing. It's MSAA for the unwrap: the lookup is computed at envmap_height × scale and downsampled, so at 4x you get four samples per output pixel. The default of "4" is a sane balance; go to 8 or 16 if you see shimmering on the highlights at high resolution, and accept it'll be slower.

The inputs that matter

  • chromeball - a crop of the ball region, not the whole photo. Feed it the full frame and you'll get a warped, mostly-useless map. In the reference workflow an ImageCrop node handles this for you.
  • envmap_height (default 256) - vertical resolution of the output. 256 is fine for a quick look; 512 if you're actually lighting a render with the result. Min 1, max 8192.
  • anti_aliasing - "1", "2", "4", "8", "16" (default "4").

The single output is an IMAGE in 2:1 equirectangular form. In the DiffusionLight workflow it feeds ImageBatch, then Exposure2HDR merges the three exposure versions into a real HDR map. You can also grab it straight for preview - just know that until Exposure2HDR combines the bracket, you're looking at a flat LDR version of a scene that had way more range than that.

Installing it

This node ships in the DiffusionLight-ComfyUI pack. Easiest path: ComfyUI Manager → search "DiffusionLight" → Install. Or the manual way:

cd ComfyUI/custom_nodes
git clone https://github.com/DiffusionLight/DiffusionLight-ComfyUI

Then restart ComfyUI. The pack has no requirements.txt - Ball2Envmap only needs numpy and torch, which ComfyUI already ships. That's not true of the whole workflow though: to run the full DiffusionLight pipeline you also need comfyui_controlnet_aux (for the MiDaS depth preprocessor) and four model files the README lists - an SDXL checkpoint (CivitAI 101055), the SDXL depth ControlNet, and DiffusionLight's Exposure and Turbo LoRAs. Skip those and you can still play with this node's math on any ball photo; you just won't get the inpainted reflection.

Gotchas

  • Crop first. Feed it the ball, not the scene. This is the #1 way people get garbage out.
  • The output is 2:1 equirectangular - some viewers will show it stretched or tiled wrong. That's expected; it's a light map, not a picture.
  • No reflection in your source photo means no useful map. The diffusion stage upstream is what invents plausible reflections; if you bypass it and only run this node, you need a real chrome ball in the shot.
Categorysd

Inputs (3)

NameTypeDefaultDescription
chromeballIMAGE
envmap_heightINT2561–8192
anti_aliasingCOMBO45 options: 1, 2, 4, 8, 16

Outputs (1)

NameTypeDescription
IMAGEIMAGE