Nodes/BetterNormalCrafterWrapper/NormalCrafter - Generate Normals
ComfyUI Node

NormalCrafter - Generate Normals

Flicker-free normals from a clip, with the VRAM dials exposed

By kaski23·Created 3 months ago·Updated 27 days ago· 1
NormalCrafter - Generate Normals
  • model
  • images
  • normal_maps
◄max_resolution1024►
◄window_size14►
◄step_size10►
◄chunk_size7►

Per-image normal estimators are fine until the image moves. Run DSINE or Marigold over a video and the normals crawl and pop from frame to frame. That's fatal for the things you'd actually want normals for - relighting a shot, dropping a plate into Unreal, converting to depth, driving a normal-conditioned ControlNet pass.

NormalCrafter - Generate Normals runs the video-native model instead. Feed it a batch of frames in order, get a batch of normal maps back, every frame informed by its neighbours.

How it works

Four stages, in order: CLIP image encode, VAE encode, temporal UNet inference, VAE decode. The CLIP embeddings and the RGB latents are computed for the whole clip first and parked on CPU; the UNet then walks the clip in windows; the normal latents are decoded at the end. Only one heavy component is on the GPU at any moment - the node swaps them in per stage and pushes everything back to CPU afterwards, which is why there's no offload node in this pack and nothing to unload manually.

The windowing is the interesting part. window_size frames go through the UNet at once, the window advances by step_size, and the overlapping tail of each prediction is carried into the next window and cross-faded with a linear ramp. That overlap is what kills the temporal flicker - it's not a post-process smoothing pass, it's baked into how the clip is cut. The UNet runs a single denoising step per window - it's a one-shot predictor, not a sampler - so there's no steps, CFG or seed knob. If your clip is shorter than window_size, the final frame is repeated to fill the window.

Resolution is handled for you. Frames are downscaled - never upscaled - so the longest side is at most max_resolution, padded up to a multiple of 64, then cropped and resized back to your original resolution. If a normal map looks soft, a low max_resolution is the first thing to check. The decoded output is finally clamped, L2-normalised into unit vectors and mapped to 0–1 RGB: one normal map per input frame, in ordinary ComfyUI IMAGE format.

Inputs and output

  • model - the NORMALCRAFTER_MODEL bundle from NormalCrafter - Load.
  • images - your frames as an ordinary IMAGE batch, in temporal order. Shuffle the batch and you'll get incoherent normals. Single images work, but you're paying video-model cost for one frame.
  • max_resolution - default 1024, step 64. The biggest VRAM and speed lever of the three.
  • window_size - default 14, the model's released setting. Bigger window, more temporal context, more UNet VRAM.
  • step_size - default 10, and it must be ≤ window_size. The gap between passes; smaller means more overlap and more passes.
  • chunk_size - default 7, the batch size for CLIP/VAE encode and decode. Lower when VRAM is tight, raise for throughput; it doesn't touch the UNet window.

Output: normal_maps (IMAGE). Save or preview it, feed it to a normal-conditioned ControlNet pass, or export it as an AOV for a relight, a bake, or a game engine.

Install

Manager → search Better NormalCrafter Wrapper, or:

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

Restart, then load the nodes from the NormalCrafter category. Model files download from Hugging Face on first run, so that first execution is slow.

Where people get burned

step_size above window_size is a hard error. The node raises and the graph stops. The wrapper's README says it plainly; ComfyUI's widgets won't stop you from setting 16 and 14.

Tune in the right order. If you OOM, drop max_resolution first - it hits all four stages. window_size is next (it's what the UNet eats), then chunk_size for the encode/decode peaks. Lowering chunk_size when the UNet is the thing blowing up does nothing at all. And plan for system RAM, not just VRAM: only one component is on the GPU at a time, so the full-clip intermediates have to live somewhere.

The output should look like a normal map, not a pretty picture. Mid-gray-dominant, blue/purple with weird angles - that's a vector field, and it looks wrong as an image. What matters is that the shading stays stable when you scrub the clip.

Still images? Use a still-image estimator. The temporal machinery is wasted on one frame - DSINE, Marigold or the ControlNet aux preprocessors are quicker and often sharper.

Worth knowing where this lands: normal conditioning is one of the conditions the post-Flux ControlNet unions quietly dropped - SDXL's xinsir union still covers it. So the realistic destinations for this node's output are a relighting pass, a 3D pipeline, or an SDXL ControlNet workflow, not a Flux or Qwen one.

CategoryNormalCrafter

Inputs (6)

NameTypeDefaultDescription
modelNORMALCRAFTER_MODELNormalCrafter model bundle from the NormalCrafter - Load node.
imagesIMAGEInput image batch or video frames in temporal order. The output contains one normal map per input frame.
max_resolutionINT1024256–2048Maximum length of the input's longest side during inference. Images are never enlarged. Lower values reduce VRAM use and increase speed, at the cost of spatial detail. Output is resized back to the original input resolution.
window_sizeINT142–64Number of consecutive frames processed by the temporal UNet at once. Larger windows provide more temporal context but use more VRAM. NormalCrafter's released default is 14.
step_sizeINT101–64How many frames the temporal window advances each pass. Must be <= window_size. Smaller values create more overlap and usually smoother temporal blending, but require more UNet passes. With window_size 14, the released default is 10 (4-frame regular overlap).
chunk_sizeINT71–64Batch size used for CLIP encoding, VAE encoding and VAE decoding. Lower values reduce peak VRAM; higher values can improve throughput if enough VRAM is available. This does not change the temporal UNet window size.

Outputs (1)

NameTypeDescription
normal_mapsIMAGEPer-frame RGB normal maps in ComfyUI IMAGE format, restored to the input resolution.