Nodes/BetterNormalCrafterWrapper/NormalCrafter Clean - Generate Normals
ComfyUI Node

NormalCrafter Clean - Generate Normals

Video surface normals that don't flicker, without the VRAM gymnastics

By kaski23·Created 2 months ago·Updated 2 months ago· 0
NormalCrafter Clean - Generate Normals
  • model
  • images
  • normal_maps
max_resolution1024
window_size14
step_size10
clip_chunk_size16
vae_encode_chunk_size7
vae_decode_chunk_size4
offload_modestaged
offload_aftertrue
output_sizeoriginal
renormalize_normalstrue

Normal maps are the quieter cousin of depth maps. Depth says "how far away is this pixel"; a normal map says "which way is this surface facing." Run a clip through this node and you get a per-frame normal map that stays consistent across time - no shimmer, no flicker, no per-frame guessing. That temporal consistency is the entire point of NormalCrafter, and this is the ComfyUI node that runs it without making you babysit VRAM.

So why do you care? Relighting is the headline use: relight a shot after the fact, or take green-screen footage into a game engine like Unreal and let the character react to the scene's lighting. Normal maps also convert into depth maps with a bit of calculus if that's the direction you need, and they're a valid ControlNet condition for material-aware generation. If any of that is your workflow, video normals have historically been the fiddly missing piece.

Why this node, not the other one. There was already a NormalCrafter wrapper, but it mixed node UI, model downloads, device state, inference and cleanup into one glob, and it could hand live CUDA tensors back into ComfyUI's graph cache - a great way to trip memory errors you can't trace. This pack is a ground-up rewrite: one model object owns each component exactly once, and ComfyUI always receives a plain CPU tensor. If you've fought the original wrapper's VRAM weirdness, that's the part you'll feel.

How it runs

NormalCrafter takes a Stable Video Diffusion image encoder, VAE and scheduler, plus NormalCrafter's own fine-tuned UNet and temporal VAE. The generate node pushes frames through four explicit stages: CLIP frame embeddings, VAE encoding to RGB latents, sliding-window UNet inference, then VAE decoding back to normal maps. The clever bit is where the tensors live: full-video intermediates stay on CPU, and only the active chunk or window is moved to CUDA. Peak VRAM stays roughly flat no matter how long the clip is.

The released model uses a deterministic one-step scheduler path, which is why there's deliberately no seed input. Same clip in, same normals out - that's a feature for a geometry task, not a bug.

The knobs that actually matter

  • offload_mode - the big one. staged (default) moves one heavy component to GPU at a time and is the low-VRAM path. resident keeps everything on the device and is noticeably faster but wants substantially more VRAM. Start staged, flip to resident if it fits.
  • max_resolution - caps the longest side before inference; input is never upscaled. Lowering this is the cheapest speed/VRAM win.
  • window_size / step_size - defaults 14 and 10 give the 4-frame temporal overlap the model was trained around. Step must never exceed window. Leave them alone until you're chasing artifacts.
  • output_size - original (default) resizes the decoded normals back to your input resolution; processed keeps the working resolution.
  • renormalize_normals - interpolation after resizing can shorten vectors, so this restores unit length. Keep it on unless you're doing a strict A/B against older glue code.

The three *_chunk_size inputs trade transfer overhead against peak VRAM; only touch them if you're tuning. offload_after drops the CUDA allocator cache once generation finishes.

Installing

Install once for the whole pack, then restart ComfyUI:

cd ComfyUI/custom_nodes
git clone https://github.com/kaski23/BetterNormalCrafterWrapper
cd BetterNormalCrafterWrapper
pip install -r requirements.txt

ComfyUI Manager can do the same if you search for BetterNormalCrafterWrapper. Dependencies are standard huggingface fare - diffusers, transformers, accelerate, safetensors - nothing exotic. Wire it as Load → Generate Normals → your relighting/export nodes; the output normal_maps is a normal IMAGE you can save or feed anywhere.

Gotchas

The first Load run downloads multi-gigabyte weights from Hugging Face, so budget disk and patience. A clip shorter than the window gets padded by repeating its final frame and then trimmed back - no filler frames leak into your output. One honest caveat: at time of writing the author had syntax-checked and unit-tested the scheduler but not run the full pipeline against the real weights on a GPU, so treat your first clip as a smoke test and watch torch.cuda.memory_allocated() across repeated runs. If it's stable, you're set.

CategoryNormalCrafter/Clean

Inputs (12)

NameTypeDefaultDescription
modelNORMALCRAFTER_CLEAN_MODEL
imagesIMAGE
max_resolutionINT1024256–2048
window_sizeINT142–32
step_sizeINT101–32
clip_chunk_sizeINT161–128
vae_encode_chunk_sizeINT71–32
vae_decode_chunk_sizeINT41–32
offload_modeCOMBOstaged2 options: staged, resident
offload_afterBOOLEANtrue
output_sizeCOMBOoriginal2 options: original, processed
renormalize_normalsBOOLEANtrue

Outputs (1)

NameTypeDescription
normal_mapsIMAGE