ComfyUI Node

Film Grain

The film grain that actually looks like film, not noise

By AlbertJBurton·Created 6 months ago·Updated 3 months ago· 1
Film Grain
  • camera_roll
  • camera_roll
  • preview
film_profileNone
rms_granularity8.0
film_size135
emulsion_typeCubic
film_grit0.20
halation0.10
emulsion_softness0.75
blend_modeSoft Light
luminance_peak_bias0.50
algorithmic_octaves2
morphological_variance1.0
temporal_entropy1.00
shadow_dither0.00

The fastest way to tell a generated image from a photo is texture. Too-smooth skin, airbrushed gradients - real film has grain, and grain is a large part of why a shot reads as "shot" instead of "rendered." FilmGrainLab is the step in the comfyui-jbnodes pipeline that puts that texture in, and it's the node people mean when they say the pack's grain engine got good.

One thing to get out of the way immediately: this node takes a camera_roll (the pack's CAMERA type), not a plain image. You feed it the output of CameraLab, not your Load Image node. Wire a raw IMAGE in and it won't connect. That's by design - grain is part of the camera simulation, and the camera_roll carries the film metadata (format, stock, grain settings) the engine needs to size the grain correctly for the "frame."

How it works

The grain engine is pure numpy/torch/scipy, which matters more than it sounds: earlier versions of the pack rendered grain with moderngl/GLSL shaders, which meant OpenGL, X11, and EGL dependencies on your system. That's gone - the README is explicit that no system-level graphics libraries are required anymore. If you have an old install throwing GL errors, update the pack and they'll disappear.

The actual mechanism is layered noise. algorithmic_octaves controls how many noise octaves are stacked (more octaves = finer detail in the grain structure), and morphological_variance sets whether each octave decays (<1, smoother) or grows (>1, more texture). The engine scales grain size to the physical film frame via film_size, so 8x10 sheet film gets much finer relative grain than 135. RMS granularity is the headline number - the tooltip gives you the anchors: 8 = T-MAX 100, 17 = Tri-X 400.

The inputs that matter

You'll touch maybe four of these and leave the rest alone:

  • film_profile - the cheat code. Pick a WOF preset like "Tri-X 400 Push 1 (EI 800)" and it auto-fills the grain sliders with sensible values you can then override.
  • rms_granularity (1–50, default 8) - how much grain. Lower = fine T-Max look, higher = pushed Tri-X look.
  • film_size - 135 / 120 / 4x5 / 8x10. Smaller formats render grain coarser relative to the frame.
  • emulsion_type - Cubic (Tri-X, HP5+, clumpy classic grain) vs Tabular (T-Max, Delta, the fine even stuff) vs Mixed.

The rest is genuinely optional seasoning: halation adds that warm glow around highlights (light piping in the film base), emulsion_softness simulates light scatter in the emulsion, blend_mode changes how grain composites (Soft Light is the safe default), and luminance_peak_bias moves where grain is most visible between shadows and highlights. temporal_entropy is the one people trip on: it's the Z-axis offset of the 3D noise, i.e. it changes the grain seed pattern. Bump it and the grain changes character without touching size or amount.

Outputs are the updated camera_roll (wire this downstream into DeveloperLab) and a preview IMAGE you can actually look at.

Install

It ships inside the pack, so one install covers everything:

cd ComfyUI/custom_nodes
git clone https://github.com/AlbertJBurton/comfyui-jbnodes.git
cd comfyui-jbnodes
pip install -r requirements.txt

Then restart ComfyUI. Or use ComfyUI Manager and search "comfyui-jbnodes" - it's listed there. Dependencies are numpy, torch, colour-science, scipy, and Pillow; the unusual ones (colour-science, scipy) are what Manager needs to fetch for you.

Gotchas

  • It's a CAMERA node, not an IMAGE node. CameraLab first, always.
  • The pack is beta and the author warns of breaking changes between versions. If an old workflow stops loading after an update, that's probably it.
  • The grain runs on CPU-side numpy/scipy and can be slow at high resolution or high precision-style settings - drop algorithmic_octaves if your queue crawls.
CategoryJBNodes

Inputs (14)

NameTypeDefaultDescription
camera_rollCAMERA
film_profileoptCOMBONoneWOF film profile preset. Select a profile to auto-fill grain parameters, then override individual values if needed.
rms_granularityoptFLOAT8.01–50RMS Granularity (8 = T-MAX 100, 17 = Tri-X 400)
film_sizeoptCOMBO135Physical film format. Smaller formats = coarser relative grain
emulsion_typeoptCOMBOCubicCubic = traditional grain (Tri-X, HP5+). Tabular = T-grain (T-Max, Delta)
film_gritoptFLOAT0.200–1Grain edge sharpness. 0 = soft, 1 = harsh/contrasty
halationoptFLOAT0.100–1Light piping in film base. Warm glow around highlights
emulsion_softnessoptFLOAT0.750–1.5Micro-blur simulating light scatter in the emulsion
blend_modeoptCOMBOSoft LightHow grain is composited onto the image
luminance_peak_biasoptFLOAT0.500–1Brightness zone where grain is most visible (0 = shadows, 1 = highlights)
algorithmic_octavesoptINT21–10Number of noise octaves. More = finer detail in grain structure
morphological_varianceoptFLOAT1.00–2Amplitude multiplier per octave. <1 = decay (smoother), >1 = growth (more texture)
temporal_entropyoptFLOAT1.000–23D noise Z-axis offset. Changes the grain seed pattern
shadow_ditheroptFLOAT0.000–2Additive noise in deep shadows to prevent posterization

Outputs (2)

NameTypeDescription
camera_rollCAMERA
previewIMAGE