ComfyUI Node

Film Grain (MEC)

Grain that moves like film, not a frozen noise overlay

By Code2Collapse·Created 7 months ago·Updated 2 days ago· 56
Film Grain (MEC)
  • image
  • mask
  • image
  • report
amount25.0
size25.0
roughness50.0
color0
animatetrue
seed0

Why grain at all

Because it's the cheapest single move for making a generated frame read as captured. Real cameras and film have noise; diffusion output doesn't. The KB's post-processing essay counts 526 corpus mentions of film grain in a single 2025–2026 window and concludes it's folk-standard practice - the go-to reply whenever someone asks why their AI video looks fake is "motion blur, grain, camera shake", in that order of priority.

The catch is that a lot of ComfyUI grain nodes are a static noise texture screened over the frame, which is not what film does. Film grain is per-frame: it shimmers. Freeze one field across a 120-frame sequence and your eye locks onto it instantly, and the effect stops selling the illusion and starts looking like a dirty sensor.

How it works

It's a Lightroom / Camera Raw implementation - Amount, Size, Roughness - not a "noise strength" slider bolted onto a Gaussian.

The noise field is generated at a reduced resolution and then upsampled, which is what size controls: bigger value, coarser grain, because the noise starts smaller and gets stretched further. roughness blends in a second, lower-frequency field so the grain isn't one uniform speckle - that's the irregular clumping real emulsion gives you. color at 0 is clean monochrome grain; raise it and the noise is weighted per channel, which is the coloured dye-cloud look. amount is how much of the whole thing shows through.

The calibration constants in the source (_GRAIN_MAX_AMOUNT, the size scale, the coarse-field multiplier) are carried over verbatim from upstream, with the author's own comment that they're "a monitor and a taste" things you tune against a reference render rather than reason about. Translation: 25 is not the same 25 you'd pick in another node. Preview it.

animate is the part that matters for video. On, the noise is reseeded per frame and you get real film shimmer. Off, the same static grain sits on every frame of the batch. seed fixes which field you get - same seed, same grain, reproducible renders.

Inputs and outputs

Required: image, amount (0–100, default 25), size (default 25), roughness (default 50), color (default 0), animate (default on), seed. Optional: mask - grain confined to the mask, feathered by its values, so a soft edge blends your grained region into the clean plate instead of stopping at a line.

Outputs: image and report (a string with the batch count and notes).

Practical placement

Grain goes last, after the upscale, after the grade, after any colour match. If you add grain and then hand the frame to an upscaler, the upscaler treats every grain speck as detail and invents edges around them - you've paid twice for one effect and it looks worse.

Use the mask input more than you'd expect. Grain over a sky or a smooth gradient is where it earns its keep (it also hides 8-bit banding); grain over a face at amount 60 looks like film stock pushed two stops and then scanned badly.

For a batch of still variations, turn animate off so you're comparing layouts rather than noise fields. For video, keep it on - that's the whole point of the node.

Install

ComfyUI Manager → search "CustomNodePacks", or:

cd ComfyUI/custom_nodes
git clone https://github.com/Code2Collapse/ComfyUI-CustomNodePacks

Restart. Only opencv-python, scipy and safetensors are genuinely required, and this node needs none of them - it's pure tensor math, tier 1, no model, no VRAM. Per the README: do not run pip install -r requirements.txt blind, because the pack lists torch/numpy/Pillow that ComfyUI already provides and you can wreck your install that way.

Common issues

  • The grain is invisible. 25/25/50 is deliberately restrained. Go to 45–60 for video, where compression and playback smooth it away anyway.
  • Your video looks like static. size too low, amount too high, and possibly color up so you're adding chroma noise on top of luma noise. Real grain is mostly luminance.
  • The grain won't hold still between runs of the same still. You changed seed. Everything about this node is deterministic; nothing here is random per run unless you ask for it.
CategoryMEC/Frequency

Inputs (8)

NameTypeDefaultDescription
imageIMAGELightroom / Camera Raw-style film grain: Amount, Size, Roughness. Monochrome by default; raise Color for dye-cloud colour grain.
amountFLOAT25.00–100How much grain shows through (Lightroom Amount).
sizeFLOAT25.00–100Grain size — bigger = coarser; noise is generated at reduced resolution then upscaled.
roughnessFLOAT50.00–100Irregularity of the grain: blends a fine and a coarse field, like Lightroom.
colorFLOAT00–1000 = clean monochrome grain. Raise for coloured dye-cloud grain.
animateBOOLEANtrueFresh grain on every frame of the batch — real film shimmer. Off = the same static grain on all frames.
seedINT00–18446744073709550000Same seed, same grain.
maskoptMASKOptional — confine the grain to the mask, feathered by its values (soft edges blend in).

Outputs (2)

NameTypeDescription
imageIMAGE
reportSTRING