
A ComfyUI workflow that re-skins a photographed image sequence into a completely different material world, while keeping the original architecture, perspective and camera motion intact.
Built for a 4-wall projection piece: the output tiles seamlessly on the X axis, so the left and right edges of every frame match up when the image is wrapped around a room.
This workflow produced one of the scenes in Liquid Horizons (2026), a 360ยฐ looped panoramic projection in a 4.2 ร 4.2 m enclosed room, exhibited at F|X|R Mรฉdialabor, Veszprรฉm. Released as-is, with the settings that were actually used.
(Magyar leรญrรกs: GitHub โ README.hu.md)



The stairwell’s arches, openings and depth layering survive the transfer โ they just become tissue instead of concrete.
1. What this actually does
It is not img2img with a high denoise and hope. Four independent systems constrain the render, and each one is there to solve a specific failure mode:
| System | Nodes | Solves |
|---|---|---|
| Structure lock | MiDaS depth + LineArt โ 2ร ControlNet | keeps the real geometry so the AI can’t invent a new room |
| Style injection | IPAdapter (reference image) + text prompt | supplies the material/palette that the prompt alone can’t describe |
| Temporal coherence | AnimateDiff (looped uniform context) | stops the per-frame flicker you get from frame-by-frame img2img |
| Seamless tiling | circular padding on model + VAE, x_only | left/right edges match for wrap-around projection |
Plus a sequenced batch loader so a 5000-frame shot can be rendered 64 frames at a time without running out of VRAM, and an image-sequence saver so the result stays as frames (no video encoding, no generation loss) for the edit.
2. Pipeline, in order

3. Why each block is in there
3.1 Sequenced loader (the part everyone gets wrong)
PrimitiveInt is set to increment. Its value goes through MathExpression: a * 64 into the loader’s skip_first_frames. frame_load_cap is 64.
So each time you press Queue, ComfyUI renders the next 64 frames:
| Queue # | PrimitiveInt | skip_first_frames | frames rendered |
|---|---|---|---|
| 1 | 0 | 0 | 0โ63 |
| 2 | 1 | 64 | 64โ127 |
| 3 | 2 | 128 | 128โ191 |
Combined with Auto Queue this walks through an arbitrarily long sequence at constant VRAM.
Reset
PrimitiveIntto 0 before starting a new sequence, otherwise you silently resume from wherever the last job stopped. If you changeframe_load_cap, change the* 64multiplier to match โ they must be identical or you skip or duplicate frames.
The loader also does the first downscale (1920ร1080), and ImageScale takes it to the actual render resolution of 768ร432. SD 1.5 is trained at 512ยฒ, so 768ร432 is roughly the largest 16:9 frame you can push before geometry starts repeating; the upscaler recovers the resolution at the end.
3.2 Two ControlNets, deliberately unequal
| ControlNet | Preprocessor | Strength | start โ end | Job |
|---|---|---|---|---|
control_v11f1p_sd15_depth | MiDaS (bg_threshold 0.1, res 768) | 0.55 | 0.10 โ 0.80 | volume, spatial layering, where things are in space |
control_v11p_sd15_canny | LineArt (res 768) | 0.65 | 0.20 โ 1.00 | edges: stair nosings, arch outlines, window openings |
Two design decisions worth copying:
- The canny ControlNet is fed LineArt, not Canny. Canny on a mossy, noisy concrete photo produces thousands of texture edges, and the model dutifully draws every one of them โ you get a traced photo. LineArt returns clean structural contours and ignores surface grain, which is exactly what you want when the surface is about to be replaced anyway.
- Neither starts at 0.0. Depth kicks in at 10 %, lineart at 20 %. The first steps are left free so the model can commit to the material before structure is imposed. Start both at 0 and the output looks like painted concrete instead of tissue.
- Depth ends at 0.80 so the last 20 % of sampling is unconstrained โ that is where the organic detail actually forms.
ScaledSoftControlNetWeights (0.825) feeds both loaders. It ramps ControlNet influence across the UNet blocks instead of applying it flat, which is the main defence against the “AI traced over my photo” look.
3.3 IPAdapter โ the actual style transfer
ip-adapter-plus_sd15 + CLIP-ViT-H-14, weight 0.8, ease in-out, end_at 0.8, embeds scaling V only.
The text prompt can describe what (fleshy tendons, Voronoi cells, turquoise veins) but it cannot pin down the exact palette, the sheen on the bubbles, or the specific way the membranes catch light. The reference image does that. Weight 0.8 is high โ this workflow is style-led, the prompt only steers.
ease in-out weights the reference more in the middle of sampling, less at the start and end, which keeps it from fighting the ControlNets early or muddying the fine detail late.
PrepImageForClipVision (LANCZOS, center crop, 0.01 sharpening) is not optional โ CLIP Vision wants 224ยฒ, and feeding it a raw 5504ร3072 image without a proper centre crop throws away most of the reference.
3.4 Seamless X tiling for 4-wall projection
Three nodes work together:
Apply Circular Padding Modelโ swaps the UNet’s convolutions to circular paddingApply Circular Padding VAEโ same for the VAE, so the encode/decode doesn’t re-introduce a seamCircularVAEDecodewithx_onlyโ restricts the wrap to the horizontal axis
Horizontal only, because the image wraps around four walls but the floor and ceiling are not continuous. Enable Y as well and you get a visible smear where the ceiling tries to meet the floor.
Both padding nodes must be active โ patching only the model leaves a faint seam that the VAE reintroduces at decode, and it is very hard to spot until it is projected two metres wide.
3.5 Motion
ADE_AnimateDiffLoaderWithContextโmm_sd_v15_v2.ckpt, beta scheduleavg(sqrt_linear,linear), motion_scale 0.8ADE_LoopedUniformContextOptionsโ context length 16, stride 1, overlap 8, fuseflatADE_AnimateDiffLoRALoaderโv2_lora_ZoomIn.ckptat 0.45
AnimateDiff here is not generating the motion โ the source footage already has a moving camera. Its job is temporal coherence: the 16-frame context window with 8 frames of overlap means every frame is denoised with knowledge of its neighbours, so the tissue textures stay attached to the same surfaces instead of boiling.
motion_scale 0.8 (below the default 1.0) deliberately damps AnimateDiff’s own motion so it doesn’t fight the camera move that is already in the plate.
The ZoomIn motion LoRA at 0.45 adds a slow inward push on top of the source camera move. It is subtle at that weight โ a drift, not a zoom. Set it to 0 if your source already moves enough.
3.6 Sampler settings
35 steps ยท cfg 8.5 ยท dpmpp_2m ยท karras ยท denoise 0.90
Note that the latent comes from VAEEncode of the source frames, not from an empty latent โ this is img2img. denoise 0.90 leaves 10 % of the original image in the mix, which is enough to anchor the lighting and contrast to the plate without letting the concrete show through.
cfg 8.5 is high for SD 1.5, but it is compensating for two ControlNets and an IPAdapter all pulling in different directions.
more_details LoRA at 0.6/0.6 is on the base model โ it is there because the render resolution (768ร432) is low and the model needs help producing texture that survives a 4ร upscale.
3.7 Output
Image Save (WAS Node Suite) writes a numbered JPG sequence at quality 100, 5-digit padding: styleTransfer_00001.jpg, styleTransfer_00002.jpg, โฆ
Frames, not video, on purpose: – the batch loader produces the shot in chunks; a video node would produce a pile of clips to splice – no inter-frame compression before the edit – a failed chunk can be re-rendered and dropped in without touching the rest
4x-UltraSharp runs before the save, taking 768ร432 to 3072ร1728.
4. Requirements
Custom nodes
| Pack | Used for |
|---|---|
| ComfyUI-AnimateDiff-Evolved | ADE_AnimateDiffLoaderWithContext, ADE_LoopedUniformContextOptions, ADE_AnimateDiffLoRALoader |
| ComfyUI-Advanced-ControlNet | ControlNetLoaderAdvanced, ScaledSoftControlNetWeights |
| ComfyUI_IPAdapter_plus | IPAdapterAdvanced, IPAdapterModelLoader, PrepImageForClipVision |
| comfyui_controlnet_aux | MiDaS-DepthMapPreprocessor, LineArtPreprocessor |
| ComfyUI-KJNodes | LoadVideosFromFolder, ImageUpscaleWithModelBatched |
| comfyui-pytorch360convert | Apply Circular Padding Model, Apply Circular Padding VAE |
| ComfyUI-seamless-tiling | CircularVAEDecode |
| WAS Node Suite | Image Save |
| ComfyUI-Custom-Scripts | MathExpression\|pysssss |
Models
| Folder | File |
|---|---|
models/checkpoints/SD1.5/ | dreamshaper_8.safetensors |
models/vae/ | vae-ft-mse-840000-ema-pruned.safetensors |
models/controlnet/ | control_v11f1p_sd15_depth.pth |
models/controlnet/ | control_v11p_sd15_canny.pth |
models/animatediff_models/ | mm_sd_v15_v2.ckpt |
models/animatediff_motion_lora/ | v2_lora_ZoomIn.ckpt |
models/loras/ | more_details.safetensors |
models/ipadapter/ | ip-adapter-plus_sd15.safetensors |
models/clip_vision/ | CLIP-ViT-H-14-laion2B-s32B-b79K.safetensors |
models/upscale_models/ | 4x-UltraSharp.safetensors |
The MiDaS preprocessor downloads its own weights on first run.
The checkpoint path in the workflow is
SD1.5\dreamshaper_8.safetensorsโ it expects aSD1.5subfolder insidemodels/checkpoints/. Either create it or re-pick the checkpoint in the node.
5. Setup
ComfyUI/โโโ input/โ โโโ stairway_seq/ <- your source frames (jpg or png, numbered)โ โโโ neuronWithBubbles_ref.jpeg <- the style referenceโโโ output/ โโโ styleTransfer/ <- created on first save
All paths in the workflow are relative to the ComfyUI root, so it loads and runs on any machine without editing. If your LoadVideosFromFolder build insists on an absolute path, replace input/stairway_seq with the full path โ that is the only node that may need it.
6. Running it
- Drop the source frames into
ComfyUI/input/stairway_seq/. - Load
LCM_ip_ctrl_transfer.json. - Set
PrimitiveIntto 0. - Queue once. Check the first 64 frames before committing to the whole shot.
- Happy with it? Turn on Auto Queue and let it walk the sequence.
- Reset
PrimitiveIntto 0 before the next shot.
Do a lookdev pass first. Bypass the batch stepping, set frame_load_cap to 16, and iterate on prompt / IPAdapter weight / denoise until a 16-frame chunk looks right. A full shot at these settings is not something you want to discover was wrong at frame 3000.
7. What to tune
| Parameter | Node | Value | Effect |
|---|---|---|---|
denoise | KSampler | 0.90 | โ keeps more of the original photo, โ more invention. Below ~0.75 the concrete starts showing. |
weight | IPAdapterAdvanced | 0.80 | how hard the reference image drives the look. 0.5โ0.6 lets the prompt lead instead. |
strength | ControlNet (depth) | 0.55 | โ locks volume harder, โ lets the geometry melt |
strength | ControlNet (lineart) | 0.65 | โ preserves edges, โ softens architecture into organic mass |
start_percent | both ControlNets | 0.10 / 0.20 | โ gives the style more freedom early โ the single most effective knob |
motion_scale | AnimateDiff | 0.80 | โ more independent motion, โ follows the plate more closely |
strength | ZoomIn motion LoRA | 0.45 | the slow inward drift. 0 disables it. |
context_length / overlap | LoopedUniformContext | 16 / 8 | โ overlap = smoother but slower; 8/16 is the usual sweet spot |
frame_load_cap + a * N | loader + MathExpression | 64 | chunk size โ must match each other |
8. Prompt variations
Four Note nodes in the workflow carry the prompt variants that were tried. The active one is the oxygen bubble version; the others are kept because they change the result substantially:
- base โ fleshy tendons, Zaha Hadid fluid architecture, Voronoi cellular pattern, metal datacables, turquoise veins, hazy blue void, dreamcore / Y2K surrealism
- macro โ pushes toward close-up biological texture, subsurface scattering, matte non-glowing bio-hardware
- oxygen bubbles (active) โ adds iridescent translucent bubbles trapped in the tissue, chromatic aberration, prismatic reflections
Recurring negatives: grainy, noise, grit, dots, dither, low-res textures (1.5) and glow, neon, vibrant lights (1.4). The anti-glow weight matters โ SD 1.5 will make anything described as “organic network” glow like a screensaver unless you actively suppress it.
9. Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Output looks like a traced photo | ControlNets too strong, or starting at 0.0 | raise start_percent, lower lineart strength |
| Textures boil / flicker between frames | AnimateDiff context too short or overlap too small | raise overlap to 8+, check the motion model actually loaded |
| Visible vertical seam when wrapped | only one circular padding node active | both Apply Circular Padding Model and VAE must be on |
| Frames skipped or duplicated | frame_load_cap โ MathExpression multiplier | make them identical |
| Second run starts mid-sequence | PrimitiveInt not reset | set it back to 0 |
| Everything glows neon | SD 1.5 default behaviour for organic prompts | keep the anti-glow negative weights up |
| OOM | 64-frame chunks at 768ร432 need real VRAM | lower frame_load_cap and the multiplier together |
10. License
- Workflow and documentation โ MIT. Use it, change it, ship it commercially, no attribution required (though it is appreciated).
- Example images (
stairway_seq_000000.jpg,neuronWithBubbles_ref.jpeg,stairway_brained_out.jpg) โ CC BY 4.0. Share and adapt with credit. - Models, LoRAs and custom nodes referenced by the workflow are not covered by either โ each has its own terms. Check them before commercial use.
See LICENSE for the full text.
11. Notes
Built and rendered on ComfyUI 0.3.14 / frontend 0.6.0, RTX 4090. Roughly 20 GB VRAM at 64 frames ร 768ร432.
The workflow ships with embed_workflow enabled on the saver, so every output JPG carries the graph that produced it โ drag one into ComfyUI to get the exact settings back.




