Wan SH Lighting Transfer (V2)
Fit the lighting of one shot and relight another — spherical harmonics, no model required
- source_image
- source_normal
- source_albedo
- target_image
- target_normal
- target_albedo
- relit_image
- sh_coeffs_json
- shading_map
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: theshading_mapoutput 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 onsource_image/source_normal, then relighttarget_image/target_normalwith those coefficients. This is the headline mode.rotate_lights- rotate the fitted SH byrotate_yaw_degand re-render. Theintensityscalar (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 withfit_onlyand look atshading_mapbefore you trytransfer. 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_errin 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
albedoinputs 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.
Inputs (9)
| Name | Type | Default | Description |
|---|---|---|---|
| source_image | IMAGE | — | |
| source_normal | IMAGE | — | |
| operation | COMBO | fit_only | 3 options: fit_only, transfer, rotate_lights |
| rotate_yaw_deg | FLOAT | 0-180–180 | — |
| intensity | FLOAT | 1.000–4 | — |
| source_albedoopt | IMAGE | — | |
| target_imageopt | IMAGE | — | |
| target_normalopt | IMAGE | — | |
| target_albedoopt | IMAGE | — |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| relit_image | IMAGE | — |
| sh_coeffs_json | STRING | — |
| shading_map | IMAGE | — |