Film Grain
The cheapest 'un-AI' in the finishing stack
- image
- mask
- image
- mask
- film_info
The post-processing KB has a whole argument for why film grain is the single cheapest move for breaking the "AI look": real cameras and film have noise, AI output doesn't, and adding a little back is the fastest way to make a generated frame read as captured rather than rendered. x1Film bundles that trick - grain plus an optional vignette - into one node in the MKRShift finishing pack, and it does both properly enough that it's the one I reach for at the end of a grade.
It's part of MKRShift Nodes by Cris K B, under MKRShift Nodes/VFX/Stylize.
How it works
The grain is a procedural additive-noise pass, not a re-gen or a texture lookup. A seeded noise field (film_grain_seed, default 42) is generated at film_grain_size scale, blurred up to the film-grain frequency, and added over the image at film_grain_strength. The film_grain_chroma knob (default 0.35) is the detail most grain nodes get wrong: real film grain isn't just luminance noise, it has a color component - dye clouds in the film stock - so you can mix in chroma noise that reads as "film" rather than "static." If you keep film_grain_strength at 0, the node does nothing to your image - you have to turn it on.
The vignette half is separate and optional: vignette_strength (default 0) darkens the frame edges, with vignette_roundness (default 1.2) controlling how circular vs. rectangular the falloff is. A faint vignette is a classic finishing move - it pulls the eye to the center and kills the "flat AI canvas" feel. Both effects share the node's mask system.
The inputs that matter
Inside the settings_json:
film_grain_strength- 0 to 1.5; realistically 0.1–0.3 is where subtle lives.film_grain_size- the grain's pixel scale (default 32). Smaller = finer grain.film_grain_seed- randomize to get different grain per frame.film_grain_chroma- how much color noise to mix in (default 0.35).vignette_strength/vignette_roundness- the edge darkening.
Optional mask input with mask_feather / invert_mask, and outputs image, mask, and a film_info string.
Installing it
cd ComfyUI/custom_nodes
git clone https://github.com/criskb/MKRShift_Nodes
Restart ComfyUI, or install via ComfyUI Manager (search MKRShift_Nodes). No requirements.txt, no models, no keys.
The trap
Defaults are zero for both effects, so if you load the node and see "nothing happened," that's by design - the pack made the safe default, you supply the intent. The real mistake is grain that's too big or too strong: at film_grain_strength above ~0.4 and a large film_grain_size, it stops reading as film and starts reading as a bad Instagram filter. Start at 0.15 strength, size 32, chroma 0.3, and nudge up only until the "AI smoothness" dies. And for video, re-seed per frame - static grain looks like a dirty sensor, animated grain looks like film.
Inputs (3)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| settings_json | STRING | {"film_grain_strength":0.0,"film_grain_size":32.0,"film_grain_seed":42,"film_grain_chroma":0.35,"vignette_strength":0.0,"vignette_roundness":1.2,"mask_feather":12.0,"invert_mask":false} | — |
| maskopt | MASK | — |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |
| mask | MASK | — |
| film_info | STRING | — |