VizClick NormalCrafter
NormalCrafter without the research-repo pain
- images
- normal_video
Every normal-map preprocessor you've used - Normal BAE in comfyui_controlnet_aux, say - looks at one frame and knows nothing about the one before it. Fine on a still. On a clip the normals shimmer, and everything built on them inherits it. VizClick NormalCrafter feeds the whole RGB batch to NormalCrafter, an ICCV 2025 video model that estimates surface orientation across a sequence instead of per frame.
Why you'd want a normal sequence at all
Normal maps encode which way each surface faces: red, green and blue are the X, Y, Z of a unit vector, which is why the output looks like a weird pastel photo. It's data for a renderer, not something you watch.
Two things make a video pass worth the trouble. Relighting, because normal-map relighters smooth faces into mush precisely when their normals are noisy frame to frame. And 3D/VFX, whether you're texturing a Blender model from a plate or handing a clean sequence to a compositor. One caveat: post-Flux ControlNets dropped normal conditioning almost entirely, so "normals into ControlNet" mostly means SDXL and something like the xinsir union.
And be honest about the ecosystem - NormalCrafter barely registered on r/comfyui. You're not following a crowd here.
How it works
Under the hood it's a Stable Video Diffusion–derived stack: a CLIP image encoder conditions the frames, a spatiotemporal U-Net denoises a video latent, a temporal VAE decoder turns that back into image-space normals, stepping on an Euler discrete scheduler. The paper's Semantic Feature Regularization is a training trick - no DINOv2 gets loaded at inference, so don't go hunting for one.
The node handles the plumbing: resize for inference, run the pipeline over overlapping temporal windows so long clips work, resize back. Not a depth map, not a mesh - surface orientation only.
It also bundles the two upstream Python files it needs (vendor/normalcrafter/), so there's no second git clone. If either goes missing it fetches it from GitHub on first run.
The inputs that matter
images is the whole game: a ComfyUI IMAGE batch of RGB frames, exactly what VHS_LoadVideo hands you. Dimensions get rounded up to multiples of 64 internally.
max_res_dimension (default 512) caps the longest side used for inference. The resize is min(1.0, max_res / longest_side), so it never upscales: a 1280×720 clip at 512 runs inference at 512×320 and scales the normals back up - bigger dimensions, same detail. Setting 2048 on a 720p clip just wastes time; 256 on a 4K clip is how you survive without an OOM.
window_size (14) and time_step_size (10) are the temporal context knobs passed straight to the upstream pipeline. Bigger windows mean more context and more memory. The author warns against assuming bigger is better, and that's right - you want "no flicker" and "no ghosting," and the defaults usually get you there.
decode_chunk_size (4) is what to drop when the VAE decode is what's blowing up, not the U-Net. offload_after (true) parks the pipeline on CPU and empties the CUDA cache afterwards - that helps the next job you queue, not peak memory during inference. seed at 42 is fine; keep it fixed so runs stay comparable while you tune.
Output is one normal_video IMAGE batch. Preview it, feed the pack's stabilizer, or hit VHS_VideoCombine - though the README says plainly that a watchable export should come from the stabilizer, not from here.
Installing it
ComfyUI Manager, search "ComfyUI-VizClick-NormalCrafter," restart. Or manually:
cd ComfyUI/custom_nodes
git clone https://github.com/zuanfilm/ComfyUI-VizClick-NormalCrafter.git
Declared dependencies: diffusers, transformers, accelerate, opencv-python>=4.8. Install them into the same Python that runs ComfyUI, and don't let anything drag a fresh CUDA-specific PyTorch over the one your install already works with.
Then the model, which is the real cost. Grab the whole repo from Yanrui95/NormalCrafter - roughly 4.5 GB - keeping every subfolder intact:
ComfyUI/models/NormalCrafter/
├── model_index.json
├── feature_extractor/
├── image_encoder/
├── scheduler/
├── unet/
└── vae/
All three capitalizations of the folder name work. Skipping the configs and grabbing only the safetensors is the classic mistake: the node checks for seven specific files, including image_encoder/model.fp16.safetensors, and refuses to load without them.
Where people get stuck
"Incomplete NormalCrafter model at …" names the missing paths for you. Nine times out of ten it's a zip that flattened the subfolders, or weights dropped in models/diffusers/. Restart ComfyUI after moving anything.
CUDA out of memory at high resolution is expected, not a bug. Upstream quotes roughly 6 GB at 512×256 and around 20 GB at 1024×576 - that second number is why nobody batches a 4K shot on a 12 GB card. Lower max_res_dimension first, decode_chunk_size second, clip length third.
First run feels hung. The pipeline loads lazily on first execution with nothing on screen to say so. Short clip, defaults, confirm it completes, then scale up.
It's slow, it's a 4.5 GB download, and it isn't a whim purchase. But if you've watched normals crawl through a shot you wanted to relight, one consistent pass is worth the setup.
Inputs (7)
| Name | Type | Default | Description |
|---|---|---|---|
| images | IMAGE | — | |
| seed | INT | 420–18446744073709550000 | — |
| max_res_dimension | INT | 512256–2048 | — |
| window_size | INT | 142–64 | — |
| time_step_size | INT | 101–64 | — |
| decode_chunk_size | INT | 41–64 | — |
| offload_after | BOOLEAN | true | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| normal_video | IMAGE | — |