Nodes/Advanced Photo Grain/📸 Photo Film Grain
ComfyUI Node

📸 Photo Film Grain

The Grain Node That Also Adds the Lens Flaws AI Hides

By tritant·Created about a year ago·Updated about a month ago· 7
📸 Photo Film Grain
  • images
  • IMAGE
â—„grain_typepoissonâ–ş
â—„grain_intensity0.022â–ş
â—„grain_size1.5â–ş
â—„saturation_mix0.22â–ş
â—„adaptive_grain0.30â–ş
â—„halation_strength0.00â–ş
â—„vignette_strength0.00â–ş
â—„chromatic_aberration0.0â–ş
â—„lens_distortion0.00â–ş

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 at 1.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 about 0.2.
  • vignette_strength - subtle edge darkening; 0.15 is 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 from 0.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_intensity toward 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 whole images tensor at once.
Categoryimage/enhancement

Inputs (10)

NameTypeDefaultDescription
imagesIMAGE—
grain_typeCOMBOpoisson3 options: gaussian, poisson, perlin
grain_intensityFLOAT0.0220.001–1—
grain_sizeFLOAT1.51–16—
saturation_mixFLOAT0.220–1—
adaptive_grainFLOAT0.300–2—
halation_strengthFLOAT0.000–1—
vignette_strengthFLOAT0.000–1—
chromatic_aberrationFLOAT0.00–5—
lens_distortionFLOAT0.00-0.5–0.5—

Outputs (1)

NameTypeDescription
IMAGEIMAGE—