Nodes/ComfyUI-Darkroom/Water Refraction
ComfyUI Node

Water Refraction

A ComfyUI node in AKURATE/Darkroom/Lens with 20 inputs and 2 outputs.

By jeremieLouvaert·Created 5 months ago·Updated a day ago· 96
Water Refraction
  • image
  • mask
  • image
  • grain_deficit
field_width_mm40.0
surfacestanding pool (heavier, whole frame)
water_ml10.0
pour_sweep1.00
sweep_angle45
sample_ms150
settle_ms0
depth_scale1.00
aperture0.020
seed0
mask_min0.15
mask_gamma2.00
sim_resolution0
aperture_samples32
env_strength1.00
dispersionfalse
grain_restore1.00
vary_per_framefalse
CategoryAKURATE/Darkroom/Lens

Inputs (20)

NameTypeDefaultDescription
imageIMAGE
field_width_mmFLOAT40.08–250How wide a patch of screen the frame covers, in millimetres. THE most important control. Refraction displaces at most 0.881x the water depth, so framing a whole tablet (250mm) correctly gives almost nothing. The reference look lives at 20-50mm.
surfaceCOMBOstanding pool (heavier, whole frame)What the water is poured onto. THE biggest look lever, and the two options are different looks rather than two ends of a dial. Standing pool: pouring INTO water that is already there. The disturbance reaches the whole frame, which gives the heavier, more liquid result. Dry screen: the water only exists where it has been poured, so large areas stay completely undistorted and the subject stays very readable. (A free puddle cannot hold more than ~4mm on its own, so the pool option implies a tray or a bezel.)
water_mlFLOAT10.01–60How much water is poured. The intensity dial: more water is deeper water is more displacement. Raising it also shrinks the undistorted area, which is what keeps the subject readable.
pour_sweepFLOAT1.000–1How far the stream is swept while pouring, which is really a CONCENTRATION control. The same water drawn along a longer stroke is shallower, so it displaces and folds less: measured, folding runs 39% at 0, 30% at 0.5 and 24% at 1.0. Low values pile the pour into one deep mound for the heaviest distortion; high values spread it into a longer, gentler disturbance.
sweep_angleFLOAT450–360Direction the stream is swept across the frame.
sample_msFLOAT15020–300When the photograph is taken, measured from the start of the pour. Below the pour duration (100ms) you catch the live event; well above it the water has spread thin and flattened.
settle_msFLOAT00–600Extra time for the surface to calm before the shot. Viscous damping kills short ripples far faster than long ones (a 2.4mm ripple dies in ~72ms, a 12mm one in ~1.8s), so this removes fine chop and keeps the large forms. Costs folding if pushed far.
depth_scaleFLOAT1.000.25–3Multiplies the water depth. The honest artistic knob: anyone wanting more than physics allows is really asking for deeper water, so this scales h and the optics stay exact.
apertureFLOAT0.0200–0.06Lens aperture over camera distance (A/L). A 100mm macro at f/8 from 300mm is about 0.021. This is required physics, not softening: it blends the multiple images at a fold instead of seaming them. 0 gives a pinhole and a hard chrome look.
seedINT00–4294967295Pour seed. Same seed gives the same water.
maskoptMASKModulates how strongly the refraction applies at each output pixel. Mask values run from mask_min (where black) up to full effect (where white), and the warp itself is scaled so partial values do not ghost; the surface sheen and the grain_deficit output follow the same modulation. The water is simulated across the whole frame regardless, so a mask does not make the node faster. With mask_min at 0 a hard mask edge shows a visible seam line; feather by about a quarter of the local displacement, typically tens of pixels at pool depths.
mask_minoptFLOAT0.150–1Effect strength where the mask is black. The mask is remapped to run from this floor up to 1, so the default keeps a subtle refraction everywhere and uses the mask to push intensity where it is white. Set 0 to make the mask a hard gate: black means bitwise-untouched pixels and a zero grain_deficit. Ignored when no mask is connected.
mask_gammaoptFLOAT2.000.25–4Contrast of the mask response, applied before mask_min. Above 1 pulls mid grey mask values toward the floor so the painted highlights stand out clearly against them; below 1 lifts the mids instead. 1 is the plain linear remap. Feathered masks are mostly mid grey, which is why the default adds contrast.
sim_resolutionoptINT00–2560 = AUTO, which is what you want. Auto sizes the fluid grid so each cell is 0.25mm whatever the field width, i.e. about 11 cells across the 2.727mm capillary length. That matters because the ripple at that scale is what supplies the slope that makes a pool FOLD: measured, 7.6 cells gives 2% folding and 10.9 gives 20%. Pinning this to a fixed number is a trap, because dx = field_width / this, so widening the framing silently coarsens the grid and the folds quietly disappear (57.7% at 20mm, 31.3% at 40mm, 2.4% at 80mm, all at 160). Set it by hand only to trade quality for speed deliberately. Cost scales roughly with the square.
aperture_samplesoptINT328–64Rays per pixel across the lens. Lower is faster and noisier at folds.
env_strengthoptFLOAT1.000–3Brightness of the environment reflected off the water by the Fresnel term, strongest at grazing angles on the steep fold lines.
dispersionoptBOOLEANfalseRefract R/G/B at their own refractive indices. Real but subtle, and it triples the optics cost.
grain_restoreoptFLOAT1.000–1.5Put back the fine detail the warp destroyed. The refraction genuinely eats film grain — some of it is real physics (the aperture) and some was the resampler — so this restores it, weighted by a MEASURED deficit map and calibrated so 1.0 lands at the source's own grain level rather than adding on top. 0 leaves the image alone and you can chain the grain_deficit mask into a grain node instead, but then the calibration is yours to find.
vary_per_frameoptBOOLEANfalseRe-pour with a different seed for each batch frame. Off means one surface across the batch.

Outputs (2)

NameTypeDescription
imageIMAGE
grain_deficitMASK