Control AOV — Multi-Control Fusion (C2C)
One node, every ControlNet pass you need — depth, canny, pose, normal, motion, and an ID matte, fused for a union control
- image
- depth
- canny
- pose
- normal
- id_matte
- blended
- combined
- channel_packed
- depth
- canny
- pose
- normal
- motion
- id_matte
- info
ControlNet changed everything about local generation, and the modern version of the game is stacking conditions - depth for spatial layout, canny for edges, pose for structure, normal for surface, motion for video, an ID matte for identity. That's normally a chain of separate preprocessor packs. ControlAOVC2C collapses it into one node that emits every pass at once, plus a channel-packed image (depth=R, canny=G, pose=B) designed for union ControlNets, plus a preview blend. It's the "all the AOVs, in one stop" node of the C2C Control suite.
The mental model is VFX render passes. In a 3D pipeline you render depth, normals, and ID as separate "arbitrary output variables" and recombine them in comp. This node does the same for ControlNet conditioning: each pass comes out of its own output, so you can feed them to separate ControlNets and stack them for maximum spatial lock, or use the convenience outputs to feed a single union control.
How it works
The node is a fusion hub with two philosophies of preprocessing. Canny and optical-flow motion run internally - OpenCV Canny (no model) and optical flow on your frame batch. Everything else is either vendored (runs inside the pack: DepthAnything v1/v2/v3, MiDaS, Depth Pro, DepthCrafter, NormalCrafter, ViTPose) or delegated to comfyui_controlnet_aux (the depth_anything_*, dwpose, openpose, hed, lineart options). You pick per-pass: leave a pass off and wire in an external map (from your own depth node, a SAM matte, whatever) - the node treats those as passthrough AOVs. Weights per pass (0–2) let you tune how hard each condition bites.
The inputs that matter
- image - source frames; preprocessors run on these.
- depth_model / normal_model / pose_model / id_matte_model / edge_model - per-pass backends, mostly
offby default so you wire in what you need. - run_canny + canny_low / canny_high (100/200) - the internal edge pass.
- run_motion - optical-flow pass, needs ≥2 frames.
- preproc_resolution (512) - resolution handed to delegated preprocessors.
- depth_invert - flip near/far when your source convention doesn't match the ControlNet's.
- The weight inputs (
depth_weight,canny_weight,pose_weight,normal_weight).
Outputs: blended, combined (side-by-side/grid preview), channel_packed, and the separate depth, canny, pose, normal, motion, id_matte passes, plus an info string describing per-pass status.
Installing it
Part of Code2Collapse/ComfyUI-CustomNodePacks. ComfyUI Manager → search "CustomNodePacks", or:
cd ComfyUI/custom_nodes
git clone https://github.com/Code2Collapse/ComfyUI-CustomNodePacks.git
Be ready for model downloads: the vendored depth/normal/pose backends pull weights from HuggingFace on first use, and the delegated options need comfyui_controlnet_aux installed. This is the heaviest node in the pack.
Gotchas
First: the delegated options quietly depend on another pack (comfyui_controlnet_aux) - if you pick dwpose or lineart without it, expect a missing-module error. Prefer the vendored backends to stay self-contained. Second, run_motion needs at least two frames or it has nothing to compute - wire a video batch, not a single image. Third, depth_invert matters more than people think: a "far=bright" depth source fed to a ControlNet expecting "near=bright" gives you inverted spatial cues and garbage structure. And finally, the combined preview is for your eyes, not the model - for actual conditioning use the separate passes or channel_packed, not the pretty blend.
Inputs (27)
| Name | Type | Default | Description |
|---|---|---|---|
| blend_mode | COMBO | screen | How the 'blended' overlay combines passes. screen = least-destructive; linear_dodge clips; multiply darkens. |
| preview_layout | COMBO | horizontal_3 | Layout for the 'combined' output: horizontal_3 = depth | pose | canny; grid_2x2 = depth|pose // canny|original. |
| imageopt | IMAGE | Source frames — preprocessors below run on this. | |
| depth_modelopt | COMBO | off | Depth backend. VENDORED (self-contained, run inside this pack): da_v2/da_v1/midas (transformers), da3 (Depth-Anything-3), depth_pro, depthcrafter (video). The depth_anything_* options delegate to comfyui_controlnet_aux instead. 'off' = wire an external depth map. |
| normal_modelopt | COMBO | off | Normal backend (vendored): sobel_from_depth (no model) or normalcrafter (video). 'off' = wire an external normal map. |
| depth_sizeopt | COMBO | small | DepthAnything backbone: small=ViT-S (fastest, test default) → giant=ViT-G (best). v1 has no giant (falls back to large). Ignored by metric/zoe. |
| depth_custom_ckptopt | STRING | Custom DepthAnything .pth filename in the controlnet_aux model dir — overrides depth_size when set (your own fine-tuned weights). | |
| pose_modelopt | COMBO | off | Pose backend. vitpose = WanV2 ViTPose chain (uses models/detection/*.onnx). dwpose/openpose/... delegate to comfyui_controlnet_aux. |
| id_matte_modelopt | COMBO | off | ID/segmentation matte. sam_auto = automatic SAM segmentation (controlnet_aux SAMPreprocessor). 'off' = wire an external matte. |
| edge_modelopt | COMBO | internal_canny | internal_canny = OpenCV (no model). Others delegate to controlnet_aux. |
| run_cannyopt | BOOLEAN | true | Used only when edge_model = internal_canny. |
| canny_lowopt | INT | 1000–255 | — |
| canny_highopt | INT | 2000–255 | — |
| canny_apertureopt | COMBO | 3 | Sobel aperture for internal Canny (odd 3/5/7). |
| depth_invertopt | BOOLEAN | false | Invert depth (1 - depth). Use when source is 'far=bright' but the ControlNet expects 'near=bright'. |
| preproc_resolutionopt | INT | 51264–4096 | Resolution passed to delegated preprocessors. |
| run_motionopt | BOOLEAN | false | Optical-flow motion-vector pass (needs an image batch ≥ 2 frames). |
| depthopt | IMAGE | Depth map (DepthAnything/DepthCrafter/ZoeDepth). | |
| cannyopt | IMAGE | External edge map; overrides internal Canny if provided. | |
| poseopt | IMAGE | Pose render (DWPose/OpenPose/ViTPose). | |
| normalopt | IMAGE | Surface normals (NormalCrafter). | |
| id_matteopt | IMAGE | Segmentation/ID matte (SAM/cryptomatte-style). | |
| depth_weightopt | FLOAT | 1.000–2 | — |
| canny_weightopt | FLOAT | 1.000–2 | — |
| pose_weightopt | FLOAT | 1.000–2 | — |
| normal_weightopt | FLOAT | 0.000–2 | — |
| match_toopt | COMBO | largest | 5 options: depth, canny, pose, image, largest |
Outputs (10)
| Name | Type | Description |
|---|---|---|
| blended | IMAGE | Single image, all passes OVERLAID via the chosen blend mode. |
| combined | IMAGE | Single image, depth | pose | canny shown SIDE-BY-SIDE (or grid) — the clearest 'all 3 at once' view. |
| channel_packed | IMAGE | depth=R, canny=G, pose=B — lossless packing for union ControlNets. |
| depth | IMAGE | Depth AOV (passthrough). |
| canny | IMAGE | Canny AOV (run internally if 'image' wired). |
| pose | IMAGE | Pose AOV (passthrough). |
| normal | IMAGE | Normal AOV (passthrough). |
| motion | IMAGE | Motion-vector AOV (optical flow, run internally on a frame batch). |
| id_matte | IMAGE | ID / segmentation matte AOV (passthrough). |
| info | STRING | Per-pass STATUS + recommended max-control wiring. |