📸 Photo Film Grain
The Grain Node That Also Adds the Lens Flaws AI Hides
- images
- IMAGE
Too-smooth skin is the first thing that tells you a render isn't a photo - it's been the top "this is AI" giveaway since 2023 and it's still the first complaint people have. Grain is the classic counter: real film has it, so adding it back makes the flat, sterile output of a diffusion model read as a photograph. That's the whole job of PhotoFilmGrain (📸 Photo Film Grain), and it does more than sprinkle noise. It's a one-node film-emulation pass: grain, halation, vignette, chromatic aberration, and lens distortion, all in a single IMAGE in → IMAGE out.
This is exactly the "subtract polish, add imperfection" move the photorealism people keep landing on. Modern photoreal checkpoints come out too clean - amateur-photography LoRAs exist purely to add grain and imperfect lighting because it paradoxically makes images look more real. This node is that same idea as a deterministic post-process: it works on any model, any seed, and you can tune it while previewing instead of re-rolling gens.
How it works
The grain itself is generated from scratch, per run, and added to the image with simple image + grain * intensity, clamped to 0–1. Pick your noise character with grain_type:
- gaussian - classic white noise. The safe default when you just want texture.
- poisson - photon-simulation noise generated from the actual image via
torch.poisson, so it's luminance-dependent the way real camera noise is. More subtle - the README warns it often wants intensity up at1.0. - perlin - multi-octave fractal noise that clumps into the organic, 35mm-film texture rather than per-pixel specks.
Two details worth knowing. saturation_mix blends colored vs. monochrome noise - at 1.0 you get the faint RGB mottling of color negative film, at 0.0 pure monochrome (typical cinema is 0.4–0.5). And adaptive_grain boosts grain in the shadows, where real film shows it most: the code scales noise by (1 - luma)², so dark areas get visibly more grain. That single knob is what separates this from a flat "noise overlay" node.
The inputs that matter
The grain knobs are the first four: grain_type, grain_intensity (default 0.022, the README's typical value is 0.025), grain_size (1 = per-pixel, higher = coarser clumps, faking higher ISO), and adaptive_grain (0.8–1.5 is the sweet spot for portraits).
Everything else is the "flaw" stack, and it all defaults to off - dial it in while watching the preview:
halation_strength- the red glow bleeding around bright highlights you get on real silver-halide film. Visible from about0.2.vignette_strength- subtle edge darkening;0.15is the author's recommended starting point.chromatic_aberration- RGB fringing on high-contrast edges, a telltale lens flaw AI never produces on its own. Visible from0.5, typical range 0.3–0.8.lens_distortion- barrel (negative) or pincushion (positive) optical warp.
The single IMAGE output wires straight into a Save/Preview node, or into an upscaler if you're finishing at higher res.
Install
No dependencies, no model downloads, no VRAM scare - the whole pack is one Python file using only torch/torchvision and ComfyUI's own model_management. Install it like any custom node:
cd ComfyUI/custom_nodes
git clone https://github.com/tritant/ComfyUI-Advanced-Photo-Grain
# restart ComfyUI
Or in ComfyUI Manager, search "Advanced Photo Grain" and hit install. The node lives under the image/enhancement category.
Gotchas
- The flaw knobs are all zero by default. If your output just looks "grainy," you haven't turned on halation, vignette, or CA - the effects people actually notice. Turn them up and it stops looking like a filter and starts looking like film.
- Poisson looks like almost nothing at 0.022. Crank
grain_intensitytoward 0.1–1.0 for poisson; keep gaussian small or it turns to sand. - It's random every run. There's no seed input - each queue re-rolls fresh grain. That's actually a feature for video (per-frame noise is what real film does; static grain looks like a stuck image sensor), but don't expect bit-identical outputs across runs.
- It runs on the torch device and hands back a normal
IMAGE, so it slots into any pipeline - batch-friendly too, since it processes the wholeimagestensor at once.
Inputs (10)
| Name | Type | Default | Description |
|---|---|---|---|
| images | IMAGE | — | |
| grain_type | COMBO | poisson | 3 options: gaussian, poisson, perlin |
| grain_intensity | FLOAT | 0.0220.001–1 | — |
| grain_size | FLOAT | 1.51–16 | — |
| saturation_mix | FLOAT | 0.220–1 | — |
| adaptive_grain | FLOAT | 0.300–2 | — |
| halation_strength | FLOAT | 0.000–1 | — |
| vignette_strength | FLOAT | 0.000–1 | — |
| chromatic_aberration | FLOAT | 0.00–5 | — |
| lens_distortion | FLOAT | 0.00-0.5–0.5 | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| IMAGE | IMAGE | — |