Latent Smoke Simulation
Dreamy Dissolves Before the VAE Even Runs
- latent
- mask
- latent
- density_preview
- velocity_preview
Latent Smoke Simulation is the latent-space twin of Image Smoke Simulation, and the differences matter. Instead of smearing pixels, it treats your latent's channels as the smoke density and advects them through a buoyancy-driven velocity field. You run it on a LATENT, get a LATENT back, and only then decode. Because the deformation happens before the VAE, the smoke has a weirdly physical, material quality - it reads less like a Photoshop filter and more like the image was genuinely caught mid-dissolve.
The physics is the same stable-fluids core the whole pack uses: random force injections ("sticks"), vorticity confinement for the billowing curl, semi-Lagrangian advection, plus the smoke extras - buoyancy scaled by temperature, an ambient updraft, and a density field that fades. The difference is what's being transported. The image variant carries RGB color; this one carries the latent's feature channels, and it flattens them so the advection treats the whole sample as one field. If the latent has a noise_mask, that travels with it too.
The input list is long, and honestly most of it you'll leave alone:
smoke_source_mode-imagederives density from the latent's own values,randominjects puffs,maskinjects from aMASKinput (resized to latent resolution). Start withimage.steps- simulation steps, or per-step batch size whenoutput_modeisbatch. Batch mode is a genuinely fun trick here: you get every intermediate frame as anIMAGE, which makes a ready-made dissolve sequence for a video workflow.buoyancyandvorticity- the two that turn "smear" into "smoke." Buoyancy pushes upward, vorticity makes the columns curl and billow.density_fade- how fast the smoke thins out.temperature_strength- inject heat so the smoke rises harder. Off by default.
Outputs are the distorted latent, plus density_preview and velocity_preview images so you can see what the sim decided to do without decoding just to check.
Where it fits
The most natural home for this is img2img: smoke the latent, then feed it to a KSampler at moderate denoise and let the model "heal" the deformation into something believable - drifting cloth, dissolving silhouettes, caustics. It also slots into the same slot as any of the pack's other perturbation nodes, and unlike them it produces motion, not texture. If your render's subject is static and you want implied wind, this is the node to reach for.
Installing
Part of the Skoogeer-Noise pack by ttulttul - one install gets the whole pack:
cd ComfyUI/custom_nodes
git clone https://github.com/ttulttul/Skoogeer-Noise
Restart ComfyUI (or install via Manager, search "Skoogeer-Noise"). Dependencies are torch, numpy>=1.26, einops, pyyaml; no models, no downloads beyond the repo. Keep the folder named exactly Skoogeer-Noise - the module path is custom_nodes.Skoogeer-Noise and a renamed directory breaks the import.
Gotchas
Two things bite people here. First, the sim runs at resolution_scale and on CPU tensors, so big latents with high steps are slow - drop resolution_scale to 0.5 (its default) and accept the chunkier flow. Second, if you're on Flux.2, its latents are patchified into 128 channels in 2x2 blocks, and advecting those directly produces blocky, wrong-correlation artifacts. Unpatchify with the pack's Unpatchify Flux.2 Latent before this node and Patchify Flux.2 Latent after. And if the smoke just doesn't move, check buoyancy - zero buoyancy plus zero updraft is a still image with extra steps.
Inputs (23)
| Name | Type | Default | Description |
|---|---|---|---|
| latent | LATENT | Latent to simulate as smoke (buoyancy-driven advection). | |
| steps | INT | 300–256 | Number of simulation steps (or batch size when output_mode='batch'). |
| output_mode | COMBO | final | When 'batch', returns a batch of intermediate outputs after each step (1..steps). |
| dt | FLOAT | 1.000–4 | Time step size used during advection. |
| resolution_scale | FLOAT | 0.500.25–1 | Internal simulation resolution scale (lower = faster). |
| force_count | INT | 30–64 | Number of random force injections ('sticks') per step. When smoke_source_mode='random', also controls puff count. |
| force_strength | FLOAT | 5.00–64 | Linear force magnitude added to velocity (latent pixels / step). |
| force_radius | FLOAT | 0.100–1 | Normalized radius of influence for stick forces (0.1 = 10% of min(width,height)). |
| swirl_strength | FLOAT | 2.00–64 | Rotational component added around each stick position. |
| velocity_damping | FLOAT | 0.9800–1 | Velocity decay applied each step (lower = more viscous / quickly settles). |
| diffusion | FLOAT | 0.000–1 | Optional velocity diffusion (Gaussian smoothing). |
| vorticity | FLOAT | 0.300–10 | Vorticity confinement strength (adds billowing/swirl detail). |
| buoyancy | FLOAT | 1.500–10 | Strength of upward buoyant force. |
| ambient_updraft | FLOAT | 0.100–10 | Constant upward airflow added each step. |
| density_fade | FLOAT | 0.0100–1 | Density dissipation per step (higher = smoke dissipates faster). |
| temperature_strength | FLOAT | 0.000–10 | Initial temperature injection per step (hotter smoke rises more). |
| cooling_rate | FLOAT | 0.050–1 | Temperature decay per step. |
| smoke_source_strength | FLOAT | 1.000–10 | Amount of smoke density injected each step. |
| smoke_source_radius | FLOAT | 0.100–1 | Radius of smoke injection (normalized). Used as puff radius in random mode; softens the source in image/mask modes. |
| smoke_source_mode | COMBO | image | Smoke source mode: 'image' derives density from the input latent; 'random' injects random puffs; 'mask' injects from a MASK input. |
| seed | INT | 00–18446744073709550000 | Seed controlling random forces and random sources. |
| wrap_mode | COMBO | clamp | How advection samples outside the bounds. |
| maskopt | MASK | Used when smoke_source_mode='mask' to define the emission region (resized to latent resolution). |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| latent | LATENT | — |
| density_preview | IMAGE | — |
| velocity_preview | IMAGE | — |