Nodes/ComfyUI-WanAnimatePreprocessV2/Wan SH Lighting Transfer (V2)
ComfyUI Node

Wan SH Lighting Transfer (V2)

Fit the lighting of one shot and relight another — spherical harmonics, no model required

By Code2Collapse·Created 8 months ago·Updated 8 days ago· 17
Wan SH Lighting Transfer (V2)
  • source_image
  • source_normal
  • source_albedo
  • target_image
  • target_normal
  • target_albedo
  • relit_image
  • sh_coeffs_json
  • shading_map
operationfit_only
rotate_yaw_deg0
intensity1.00

WanSHLightingTransferV2 fits a lighting model to an image and lets you relight something else with it. Spherical harmonics (SH) lighting is the standard trick from graphics and relighting research: for a Lambertian surface, the irradiance from distant lighting can be approximated by just nine SH basis functions per color channel. This node does the least-squares fit, hands you the coefficients as JSON, and can apply them to a target image - or rotate the lights and re-render the source. No neural model, no downloads, pure math over pixels and normals.

Where does it fit in a Wan Animate workflow? Consistency. If your driving footage and your reference character are lit differently, the animated result inherits a lighting mismatch that screams "composite." Being able to say "make the character's lighting match the source shot" - or at least quantify the difference as nine numbers per channel - is a genuinely useful post step. It also slots next to the pack's own DepthPoseCannyCombinedV2, whose normal_map output is exactly the per-pixel surface normals this node wants.

How it works

The math is Basri-Jacobs / Ramamoorthi: I(p) = ρ(p) · Σ L_i · Y_i(n(p)) - pixel intensity is albedo times a weighted sum of the 9 SH basis functions evaluated at the surface normal. The node solves the least-squares system per RGB channel to get the nine L_i coefficients. If no source_albedo is given, it uses intensity/max as a rough albedo prior - fine for relative transfer, less exact for absolute relighting.

Three operations (operation dropdown):

  • fit_only - just compute the coefficients and the shading map. Great for inspection: the shading_map output is the smooth irradiance the fit recovered, and it should visually match the source's broad shading. If it doesn't, your normals are wrong.
  • transfer - fit on source_image/source_normal, then relight target_image/target_normal with those coefficients. This is the headline mode.
  • rotate_lights - rotate the fitted SH by rotate_yaw_deg and re-render. The intensity scalar (default 1) scales overall brightness.

Inputs: source_image and source_normal are required. source_normal is RGB-encoded surface normals - R = nx, G = ny, B = nz (Z+ out of screen), all 0..1 mapping to -1..1. The pack's normal map output follows this encoding. Optional: source_albedo, plus target_image, target_normal, target_albedo for the transfer mode.

Outputs: relit_image, sh_coeffs_json (per-frame [[r9],[g9],[b9]] plus RMS error - the RMS is your "how good was this fit" number), and shading_map.

The inputs a beginner actually sets

  • source_image - the shot whose lighting you want.
  • source_normal - its normal map. From the pack's combined preprocessor, or any normal-map source.
  • operation - start with fit_only and look at shading_map before you try transfer. If the shading map is flat or wrong, no transfer is going to look right.

Gotchas

  • Normals are the whole game. The SH fit can only be as good as the normal map feeding it. Wrong normal encoding (e.g. Z-in instead of Z+), or a normal map computed from a different geometry than the image, gives you a garbage fit and you'll be chasing the wrong coefficients. The rms_err in the JSON is there for exactly this check.
  • Lambertian assumption. This models diffuse, distant lighting. Specular highlights, hard shadows and near light sources aren't captured by 9 SH terms - the fit will average them out. It's a convincing broad relight, not a pixel-perfect one.
  • It's a post-processing fit, not magic. If the target and source have wildly different albedos, you'll need decent albedo inputs for a clean transfer.

Honest verdict: this is the pack's most academic node, and it's here for people who want lighting treated as data rather than eyeballed. If you're relighting faces for character consistency, the classic IC-Light-style single-image relight is more turnkey for stills - but it's a neural model with its own baggage, and this node is deterministic, free, and gives you coefficients you can actually reason about. For the Wan Animate crowd, "fit the source shot's lighting and check the shading map matches" is a solid first step before you even think about training anything.

Install: shared pack path - ComfyUI Manager search "WanAnimatePreprocessV2", or git clone https://github.com/Code2Collapse/ComfyUI-WanAnimatePreprocessV2 + pip install -r requirements.txt + restart. No model downloads.

CategoryWanAnimatePreprocess_V2/Lighting

Inputs (9)

NameTypeDefaultDescription
source_imageIMAGE
source_normalIMAGE
operationCOMBOfit_only3 options: fit_only, transfer, rotate_lights
rotate_yaw_degFLOAT0-180–180
intensityFLOAT1.000–4
source_albedooptIMAGE
target_imageoptIMAGE
target_normaloptIMAGE
target_albedooptIMAGE

Outputs (3)

NameTypeDescription
relit_imageIMAGE
sh_coeffs_jsonSTRING
shading_mapIMAGE